django session key changing upon authentication - django

I have a Django app which records users' product choices for both authenticated users. My intention is to use the request.session.session_key variable to associate anonymous data with a user if they decide to register later, a la this post:
Django storing anonymous user data
However, it seems that the session key changes when the user logs in/ registers so the session key can no longer be associated with the user. Is this the correct behaviour of the Django session framework. Is there a solid way to achieve the functionality I'm looking for?
Any help much appreciated.

In settings.py
SESSION_ENGINE = 'youapp.session_backend'
in directory youapp in file session_backend.py
from django.contrib.sessions.backends.db import SessionStore as DbSessionStore
class SessionStore(DbSessionStore):
def cycle_key(self):
pass
And session not changed after login

While the approach suggested by nnmware may work for this particular case, there is a better one.
Instead of just doing nothing inside cycle_key, we should call the super method and then save the session.
Because if you look inside the original cycle_key function you will see that the data from the old session is copied to the new one, but is not actually saved.
In settings.py
SESSION_ENGINE = 'yourapp.session_backend'
Check that SESSION_ENGINE is pointing at a module (.py file), but not to the backend class!
Now, in your 'yourapp/session_backend.py' do the following:
from django.contrib.sessions.backends.db import SessionStore as DbSessionStore
class SessionStore(DbSessionStore):
def cycle_key(self):
super(SessionStore, self).cycle_key()
self.save()

One of the solutions would also be to update old session data in the Session store:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from django.contrib.sessions.backends.db import SessionStore as DbSessionStore
from shop.models.cart import Cart
class SessionStore(DbSessionStore):
def cycle_key(self):
old_session_key = super(SessionStore, self).session_key
super(SessionStore, self).cycle_key()
self.save()
Cart.objects.filter(session_key=old_session_key).update(session_key=self.session_key)

Related

Turn off user social registration in django-allauth?

I noticed looking through the django-allauth templates there's a signup_closed.html users can be redirected to when user registration is closed or disabled. Does anyone who's familiar with that module know if there's a pre-configured setting that can be set in settings.py to turn off new user registration via existing social apps? Or do I need to configure that myself? I've read the full docs for allauth and I don't see any mention of it. Thanks.
Looks like you need to override is_open_for_signup on your adapter.
See the code.
There is no pre-configured setting but it's easy to make one (this is what I do).
# settings.py
# Point to custom account adapter.
ACCOUNT_ADAPTER = 'myproject.myapp.adapter.CustomAccountAdapter'
# A custom variable we created to tell the CustomAccountAdapter whether to
# allow signups.
ACCOUNT_ALLOW_SIGNUPS = False
# myapp/adapter.py
from django.conf import settings
from allauth.account.adapter import DefaultAccountAdapter
class CustomAccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
"""
Whether to allow sign ups.
"""
allow_signups = super(
CustomAccountAdapter, self).is_open_for_signup(request)
# Override with setting, otherwise default to super.
return getattr(settings, 'ACCOUNT_ALLOW_SIGNUPS', allow_signups)
This is flexible, especially if you have multiple environments (e.g. staging) and want to allow user registration in staging before setting it live in production.
More information at http://django-allauth.readthedocs.io/en/latest/advanced.html#custom-redirects.
You need to subclass allauth.account.adapter.DefaultAccountAdapter to override is_open_for_signup, and then set ACCOUNT_ADAPTER to your class in settings.py

How to Create Session Variables in Selenium/Django Unit Test?

I'm trying to write a functional test that uses Selenium to test a Django view. When the user comes to a page ("page2"), the view that renders that page expects to find a session variable "uid" (user ID). I've read a half dozen articles on how this is supposed to be done but none of them have worked for me. The code below shows how the Django documentation says it should be done but it doesn't work for me either. When I run the test, the view never completes executing and I get a "server error occurred" message. Could someone please tell me what I'm doing wrong? Thank you.
views.py:
from django.shortcuts import render_to_response
def page2(request):
uid = request.session['uid']
return render_to_response('session_tests/page2.html', {'uid': uid})
test.py:
from django.test import LiveServerTestCase
from selenium import webdriver
from django.test.client import Client
class SessionTest(LiveServerTestCase):
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
self.client = Client()
self.session = self.client.session
self.session['uid'] = 1
def tearDown(self):
self.browser.implicitly_wait(3)
self.browser.quit()
def test_session(self):
self.browser.get(self.live_server_url + '/session_tests/page2/')
body = self.browser.find_element_by_tag_name('body')
self.assertIn('Page 2', body.text)
Here's how to solve this problem. James Aylett hinted at the solution when he mentioned the session ID above. jscn showed how to set up a session but he didn't mention the importance of the session key to a solution and he also didn't discuss how to link the session state to Selenium's browser object.
First, you have to understand that when you create a session key/value pair (e.g. 'uid'=1), Django's middleware will create a session key/data/expiration date record in your backend of choice (database, file, etc.). The response object will then send that session key in a cookie back to the client's browser. When the browser sends a subsequent request, it will send a cookie back that contains that key which is then used by the middleware to lookup the user's session items.
Thus, the solution required 1.) finding a way to obtain the session key that is generated when you create a session item and then; 2.) finding a way to pass that key back in a cookie via Selenium's Firefox webdriver browser object. Here's the code that does that:
selenium_test.py:
-----------------
from django.conf import settings
from django.test import LiveServerTestCase
from selenium import webdriver
from django.test.client import Client
import pdb
def create_session_store():
""" Creates a session storage object. """
from django.utils.importlib import import_module
engine = import_module(settings.SESSION_ENGINE)
# Implement a database session store object that will contain the session key.
store = engine.SessionStore()
store.save()
return store
class SeleniumTestCase(LiveServerTestCase):
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
self.client = Client()
def tearDown(self):
self.browser.implicitly_wait(3)
self.browser.quit()
def test_welcome_page(self):
#pdb.set_trace()
# Create a session storage object.
session_store = create_session_store()
# In pdb, you can do 'session_store.session_key' to view the session key just created.
# Create a session object from the session store object.
session_items = session_store
# Add a session key/value pair.
session_items['uid'] = 1
session_items.save()
# Go to the correct domain.
self.browser.get(self.live_server_url)
# Add the session key to the cookie that will be sent back to the server.
self.browser.add_cookie({'name': settings.SESSION_COOKIE_NAME, 'value': session_store.session_key})
# In pdb, do 'self.browser.get_cookies() to verify that it's there.'
# The client sends a request to the view that's expecting the session item.
self.browser.get(self.live_server_url + '/signup/')
body = self.browser.find_element_by_tag_name('body')
self.assertIn('Welcome', body.text)
There are a couple of tickets in Django's bug tracker around this kind of problem, the main one seems to be: https://code.djangoproject.com/ticket/10899 which hasn't had any movement on it for a few months. Basically, you need to do some extra set up to get the session to work properly. Here's what worked for me (may not work as is with your particular set up, as I wasn't using Selenium):
def setUp(self):
from django.conf import settings
engine = import_module(settings.SESSION_ENGINE)
store = engine.SessionStore()
store.save()
self.client.cookies[settings.SESSION_COOKIE_NAME] = store.session_key
Now you should be able to access self.client.session and it should remember any changes you make to it.
Here is my solution for django==2.2.
from importlib import import_module
from django.conf import settings
from django.contrib.auth import get_user_model
# create the session database instance
engine = import_module(settings.SESSION_ENGINE)
session = engine.SessionStore()
# create the user and instantly login
User = get_user_model()
temp_user = User.objects.create(username='admin')
temp_user.set_password('password')
self.client.login(username='admin', password='password')
# get session object and insert data
session = self.client.session
session[key] = value
session.save()
# update selenium instance with sessionID
selenium.add_cookie({'name': 'sessionid', 'value': session._SessionBase__session_key,
'secure': False, 'path': '/'})

Keep Django session data after sign-in?

I recently wrote shopping cart code that depends on the Session object. It seemed the reasonable way to store data for anonymous users.
While doing a bunch of testing, I ran into an annoying problem--when users sign in part way through the checkout process (or simply while browsing for other products), Django issues a new session_key and I lose access to my session data.
Is there a way to keep the old session data? Or is my design approach wrong?
Try writing your own SessionBackend that inherits from existing one and overrides the cycle_key method.
1 In your settings.py:
SESSION_ENGINE = 'my_app.session_backend'
2 my_app.session_backend.py:
from django.contrib.sessions.backends.db import SessionStore as DbSessionStore
class SessionStore(DbSessionStore):
def cycle_key(self):
pass
cycle_key is beeing called in login view after authentication.
Let me now if it works ;)
Instead of disabling the cycle_key() (which is a security measure to avoid session fixation vulnerabilities), you could consider restoring the values through a decorator at the login and logout views. See:
https://stackoverflow.com/a/41849076/146289
I'm trying to do something similar. Django can change the session_key to mitigate session fixation vulnerabilities, so it's not suitable for a foreign key. I want something more permanent. So I'll just put the permanent identifier in request.session['visitor_id']:
from django.utils.crypto import get_random_string
import string
VALID_KEY_CHARS = string.ascii_lowercase + string.digits
def example_view(request):
if not request.session.get('visitor_id'):
self.request.session['visitor_id'] = get_random_string(32, VALID_KEY_CHARS)
# Now code the rest of the view, using the visitor_id instead of
# session_key for keys in your model.
# ...

How to use session in TestCase in Django?

I would like to read some session variables from a test (Django TestCase)
How to do that in a clean way ?
def test_add_docs(self):
"""
Test add docs
"""
# I would access to the session here:
self.request.session['documents_to_share_ids'] = [1]
response = self.client.get(reverse(self.document_add_view_id, args=[1]), follow=True)
self.assertEquals(response.status_code, 200)
As of Django 1.7+ this is much easier. Make sure you set the session as a variable instead of referencing it directly.
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
andreaspelme's workaround is only needed in older versions of django. See docs
Unfortunately, this is not a easy as you would hope for at the moment. As you might have noticed, just using self.client.session directly will not work if you have not called other views that has set up the sessions with appropriate session cookies for you. The session store/cookie must then be set up manually, or via other views.
There is an open ticket to make it easier to mock sessions with the test client: https://code.djangoproject.com/ticket/10899
In addition to the workaround in the ticket, there is a trick that can be used if you are using django.contrib.auth. The test clients login() method sets up a session store/cookie that can be used later in the test.
If you have any other views that sets sessions, requesting them will do the trick too (you probably have another view that sets sessions, otherwise your view that reads the sessions will be pretty unusable).
from django.test import TestCase
from django.contrib.auth.models import User
class YourTest(TestCase):
def test_add_docs(self):
# If you already have another user, you might want to use it instead
User.objects.create_superuser('admin', 'foo#foo.com', 'admin')
# self.client.login sets up self.client.session to be usable
self.client.login(username='admin', password='admin')
session = self.client.session
session['documents_to_share_ids'] = [1]
session.save()
response = self.client.get('/') # request.session['documents_to_share_ids'] will be available
If you need to initialize a session for the request in tests to manipulate it directly:
from django.contrib.sessions.middleware import SessionMiddleware
from django.http import HttpRequest
request = HttpRequest()
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
If testing my django project with pytest, I can not see any modifications to the session that are made in the view. (That is because the Sessions middleware doesn't get called.)
I found the following approach to be useful:
from unittest.mock import patch
from django.test import Client
from django.contrib.sessions.backends import db
def test_client_with_session():
client = Client()
session = {} # the session object that will persist throughout the test
with patch.object(db, "SessionStore", return_value=session):
client.post('/url-that-sets-session-key/')
assert session['some_key_set_by_the_view']
client.get('/url-that-reads-session-key/')
This approach has the benefit of not requiring database access.
You should be able to access a Client's session variales through its session property, so I guess self.client.session['documents_to_share_ids'] = [1] should be what you are looking for!

Django's logout function remove locale settings

When I use Django's logout function to log out an authenticated user, it switched locale to en_US, the default one.
from django.contrib.auth import logout
def someview(request):
logout(request)
return HttpResponseRedirect('/')
How to keep user's locale after logged out?
I solved the problem by wrapping the django.contrib.auth.views.logout within a custom view and resetting the session language after logout. There's some code.
I have an app named login with following urls.py:
# myproject/login/urls.py
from django.conf.urls.defaults import patterns
urlpatterns = patterns('myproject.login.views',
...
(r'^logout/$', 'logoutAction'),
...
)
So now URL /logout/ calls a view named logoutAction in views.py. In logoutAction, the old language code is stored temporarily and inserted back to the session after calling Django's contrib.auth.views.logout.
# myproject/login/views.py
...
from django.contrib.auth.views import logout as auth_logout
def logoutAction(request):
# Django auth logout erases all session data, including the user's
# current language. So from the user's point of view, the change is
# somewhat odd when he/she arrives to next page. Lets try to fix this.
# Store the session language temporarily if the language data exists.
# Its possible that it doesn't, for example if session middleware is
# not used. Define language variable and reset to None so it can be
# tested later even if session has not django_language.
language = None
if hasattr(request, 'session'):
if 'django_language' in request.session:
language = request.session['django_language']
# Do logout. This erases session data, including the locale language and
# returns HttpResponseRedirect to the login page. In this case the login
# page is located at '/'
response = auth_logout(request, next_page='/')
# Preserve the session language by setting it again. The language might
# be None and if so, do nothing special.
if language:
request.session['django_language'] = language
# Now user is happy.
return response
The end of the LAN Quake problem :)
You could try explicitly setting the language cookie when the user logs out, there's a bit about how django determines the language setting here in the djangodocs.
I would have presumed that the language setting would have remained as a session value on logging out, maybe it needs to be reinitialised if django completely destroys the session on logging out the user.
You can create a UserProfile model (that has unique foreign key to User) and save the user's language preference there (and any other extra user specific setting). Then on every user login, the locale can be set to the language code saved in the user's UserProfile.
Django has a setting AUTH_PROFILE_MODULE, where you can set a model as an "official" user profile model for django.contrib.auth