diff options
| -rw-r--r-- | django/bin/daily_cleanup.py | 2 | ||||
| -rw-r--r-- | django/conf/global_settings.py | 14 | ||||
| -rw-r--r-- | django/contrib/comments/views/comments.py | 4 | ||||
| -rw-r--r-- | django/core/handlers/modpython.py | 31 | ||||
| -rw-r--r-- | django/core/handlers/wsgi.py | 31 | ||||
| -rw-r--r-- | django/middleware/admin.py | 13 | ||||
| -rw-r--r-- | django/middleware/sessions.py | 69 | ||||
| -rw-r--r-- | django/models/auth.py | 61 | ||||
| -rw-r--r-- | django/models/core.py | 54 | ||||
| -rw-r--r-- | django/parts/auth/anonymoususers.py | 9 | ||||
| -rw-r--r-- | django/parts/auth/formfields.py | 4 | ||||
| -rw-r--r-- | django/views/auth/login.py | 33 | ||||
| -rw-r--r-- | docs/faq.txt | 6 |
13 files changed, 179 insertions, 152 deletions
diff --git a/django/bin/daily_cleanup.py b/django/bin/daily_cleanup.py index b7235cd10c..6f2e8da4c0 100644 --- a/django/bin/daily_cleanup.py +++ b/django/bin/daily_cleanup.py @@ -7,7 +7,7 @@ DOCUMENTATION_DIRECTORY = '/home/html/documentation/' def clean_up(): # Clean up old database records cursor = db.cursor() - cursor.execute("DELETE FROM auth_sessions WHERE start_time < NOW() - INTERVAL '2 weeks'") + cursor.execute("DELETE FROM core_sessions WHERE expire_date < NOW()") cursor.execute("DELETE FROM registration_challenges WHERE request_date < NOW() - INTERVAL '1 week'") db.commit() diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 54ec8a377b..54fdcf0770 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -48,9 +48,6 @@ DATABASE_HOST = '' # Set to empty string for localhost # Host for sending e-mail. EMAIL_HOST = 'localhost' -# Name of the session cookie. This can be whatever you want. -AUTH_SESSION_COOKIE = 'rizzo' - # List of locations of the template source files, in search order. TEMPLATE_DIRS = () @@ -113,6 +110,14 @@ MIDDLEWARE_CLASSES = ( "django.middleware.doc.XViewMiddleware", ) +############ +# SESSIONS # +############ + +SESSION_COOKIE_NAME = 'hotclub' # Cookie name. This can be whatever you want. +SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks). +SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie. + ######### # CACHE # ######### @@ -121,9 +126,6 @@ MIDDLEWARE_CLASSES = ( # possible values. CACHE_BACKEND = 'simple://' -# Set to a string like ".lawrence.com", or None for a standard domain cookie. -REGISTRATION_COOKIE_DOMAIN = None - #################### # COMMENTS # #################### diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py index c14df2cfdd..33972a4c52 100644 --- a/django/contrib/comments/views/comments.py +++ b/django/contrib/comments/views/comments.py @@ -2,7 +2,7 @@ from django.core import formfields, template_loader, validators from django.core.mail import mail_admins, mail_managers from django.core.exceptions import Http404, ObjectDoesNotExist from django.core.extensions import DjangoContext as Context -from django.models.auth import sessions +from django.models.auth import users from django.models.comments import comments, freecomments from django.models.core import contenttypes from django.parts.auth.formfields import AuthenticationForm @@ -215,7 +215,7 @@ def post_comment(request): # If user gave correct username/password and wasn't already logged in, log them in # so they don't have to enter a username/password again. if manipulator.get_user() and new_data.has_key('password') and manipulator.get_user().check_password(new_data['password']): - sessions.start_web_session(manipulator.get_user_id(), request, response) + request.session[users.SESSION_KEY] = manipulator.get_user_id() if errors or request.POST.has_key('preview'): class CommentFormWrapper(formfields.FormWrapper): def __init__(self, manipulator, new_data, errors, rating_choices): diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index d7d7c768b1..623db959b2 100644 --- a/django/core/handlers/modpython.py +++ b/django/core/handlers/modpython.py @@ -95,29 +95,17 @@ class ModPythonRequest(httpwrappers.HttpRequest): self._raw_post_data = self._req.read() return self._raw_post_data - def _load_session_and_user(self): - from django.models.auth import sessions - from django.conf.settings import AUTH_SESSION_COOKIE - session_cookie = self.COOKIES.get(AUTH_SESSION_COOKIE, '') - try: - self._session = sessions.get_session_from_cookie(session_cookie) - self._user = self._session.get_user() - except sessions.SessionDoesNotExist: - from django.parts.auth import anonymoususers - self._session = None - self._user = anonymoususers.AnonymousUser() - - def _get_session(self): - if not hasattr(self, '_session'): - self._load_session_and_user() - return self._session - - def _set_session(self, session): - self._session = session - def _get_user(self): if not hasattr(self, '_user'): - self._load_session_and_user() + from django.models.auth import users + try: + user_id = self.session[users.SESSION_KEY] + if not user_id: + raise ValueError + self._user = users.get_object(pk=user_id) + except (AttributeError, KeyError, ValueError, users.UserDoesNotExist): + from django.parts.auth import anonymoususers + self._user = anonymoususers.AnonymousUser() return self._user def _set_user(self, user): @@ -130,7 +118,6 @@ class ModPythonRequest(httpwrappers.HttpRequest): META = property(_get_meta) REQUEST = property(_get_request) raw_post_data = property(_get_raw_post_data) - session = property(_get_session, _set_session) user = property(_get_user, _set_user) class ModPythonHandler(BaseHandler): diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 5e39dc152a..f7c71f61d9 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -76,29 +76,17 @@ class WSGIRequest(httpwrappers.HttpRequest): self._raw_post_data = self.environ['wsgi.input'].read(int(self.environ["CONTENT_LENGTH"])) return self._raw_post_data - def _load_session_and_user(self): - from django.models.auth import sessions - from django.conf.settings import AUTH_SESSION_COOKIE - session_cookie = self.COOKIES.get(AUTH_SESSION_COOKIE, '') - try: - self._session = sessions.get_session_from_cookie(session_cookie) - self._user = self._session.get_user() - except sessions.SessionDoesNotExist: - from django.parts.auth import anonymoususers - self._session = None - self._user = anonymoususers.AnonymousUser() - - def _get_session(self): - if not hasattr(self, '_session'): - self._load_session_and_user() - return self._session - - def _set_session(self, session): - self._session = session - def _get_user(self): if not hasattr(self, '_user'): - self._load_session_and_user() + from django.models.auth import users + try: + user_id = self.session[users.SESSION_KEY] + if not user_id: + raise ValueError + self._user = users.get_object(pk=user_id) + except (AttributeError, KeyError, ValueError, users.UserDoesNotExist): + from django.parts.auth import anonymoususers + self._user = anonymoususers.AnonymousUser() return self._user def _set_user(self, user): @@ -110,7 +98,6 @@ class WSGIRequest(httpwrappers.HttpRequest): FILES = property(_get_files) REQUEST = property(_get_request) raw_post_data = property(_get_raw_post_data) - session = property(_get_session, _set_session) user = property(_get_user, _set_user) class WSGIHandler(BaseHandler): diff --git a/django/middleware/admin.py b/django/middleware/admin.py index a977bacdbf..42d83b5be7 100644 --- a/django/middleware/admin.py +++ b/django/middleware/admin.py @@ -1,7 +1,7 @@ from django.utils import httpwrappers from django.core import template_loader from django.core.extensions import DjangoContext as Context -from django.models.auth import sessions, users +from django.models.auth import users from django.views.registration import passwords from django.views.auth.login import logout import base64, md5 @@ -29,14 +29,17 @@ class AdminUserRequired: # Otherwise the password reset would need its own entry in the httpd # conf, which is a little uglier than this. Same goes for the logout # view. + if view_func in (passwords.password_reset, passwords.password_reset_done, logout): return + assert hasattr(request, 'session'), "The admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.middleware.sessions.SessionMiddleware' before %r." % self.__class__.__name__ + # Check for a logged in, valid user if self.user_is_valid(request.user): return - # If this isn't alreay the login page, display it + # If this isn't already the login page, display it if not request.POST.has_key('this_is_the_login_form'): if request.POST: message = "Please log in again, because your session has expired. "\ @@ -64,18 +67,16 @@ class AdminUserRequired: # The user data is correct; log in the user in and continue else: if self.authenticate_user(user, request.POST.get('password', '')): + request.session[users.SESSION_KEY] = user.id if request.POST.has_key('post_data'): post_data = decode_post_data(request.POST['post_data']) if post_data and not post_data.has_key('this_is_the_login_form'): # overwrite request.POST with the saved post_data, and continue request.POST = post_data request.user = user - request.session = sessions.create_session(user.id) return else: - response = httpwrappers.HttpResponseRedirect(request.path) - sessions.start_web_session(user.id, request, response) - return response + return httpwrappers.HttpResponseRedirect(request.path) else: return self.display_login_form(request, ERROR_MESSAGE) diff --git a/django/middleware/sessions.py b/django/middleware/sessions.py new file mode 100644 index 0000000000..41cf3daf02 --- /dev/null +++ b/django/middleware/sessions.py @@ -0,0 +1,69 @@ +from django.conf.settings import SESSION_COOKIE_NAME, SESSION_COOKIE_AGE, SESSION_COOKIE_DOMAIN +from django.models.core import sessions +import datetime + +TEST_COOKIE_NAME = 'testcookie' +TEST_COOKIE_VALUE = 'worked' + +class SessionWrapper(object): + def __init__(self, session_key): + self.session_key = session_key + self.modified = False + + def __getitem__(self, key): + return self._session[key] + + def __setitem__(self, key, value): + self._session[key] = value + self.modified = True + + def __delitem__(self, key): + del self._session[key] + self.modified = True + + def get(self, key, default=None): + return self._session.get(key, default) + + def set_test_cookie(self): + self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE + + def test_cookie_worked(self): + return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE + + def _get_session(self): + # Lazily loads session from storage. + try: + return self._session_cache + except AttributeError: + if self.session_key is None: + self._session_cache = {} + else: + try: + s = sessions.get_object(session_key__exact=self.session_key, + expire_date__gt=datetime.datetime.now()) + self._session_cache = s.get_decoded() + except sessions.SessionDoesNotExist: + self._session_cache = {} + return self._session_cache + + _session = property(_get_session) + +class SessionMiddleware: + def process_view(self, request, view_func, param_dict): + request.session = SessionWrapper(request.COOKIES.get(SESSION_COOKIE_NAME, None)) + + def process_response(self, request, response): + # If request.session was modified, or if response.session was set, save + # those changes and set a session cookie. + try: + modified = request.session.modified + except AttributeError: + modified = False + if modified: + session_key = request.session.session_key or sessions.get_new_session_key() + new_session = sessions.save(session_key, request.session._session, + datetime.datetime.now() + datetime.timedelta(seconds=SESSION_COOKIE_AGE)) + # TODO: Accept variable session length and domain. + response.set_cookie(SESSION_COOKIE_NAME, session_key, + max_age=SESSION_COOKIE_AGE, domain=SESSION_COOKIE_DOMAIN) + return response diff --git a/django/models/auth.py b/django/models/auth.py index 91ed0650ec..9ab403af34 100644 --- a/django/models/auth.py +++ b/django/models/auth.py @@ -44,6 +44,9 @@ class User(meta.Model): help_text="In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."), meta.ManyToManyField(Permission, name='user_permissions', blank=True, filter_interface=meta.HORIZONTAL), ) + module_constants = { + 'SESSION_KEY': '_auth_user_id', + } ordering = ('username',) exceptions = ('SiteProfileNotAvailable',) admin = meta.Admin( @@ -172,64 +175,6 @@ class User(meta.Model): from random import choice return ''.join([choice(allowed_chars) for i in range(length)]) -class Session(meta.Model): - fields = ( - meta.ForeignKey(User), - meta.CharField('session_md5', maxlength=32), - meta.DateTimeField('start_time', auto_now=True), - ) - module_constants = { - 'TEST_COOKIE_NAME': 'testcookie', - 'TEST_COOKIE_VALUE': 'worked', - } - - def __repr__(self): - return "session started at %s" % self.start_time - - def get_cookie(self): - "Returns a tuple of the cookie name and value for this session." - from django.conf.settings import AUTH_SESSION_COOKIE, SECRET_KEY - import md5 - return AUTH_SESSION_COOKIE, self.session_md5 + md5.new(self.session_md5 + SECRET_KEY + 'auth').hexdigest() - - def _module_create_session(user_id): - "Registers a session and returns the session_md5." - from django.conf.settings import SECRET_KEY - import md5, random, sys - # The random module is seeded when this Apache child is created. - # Use person_id and SECRET_KEY as added salt. - session_md5 = md5.new(str(random.randint(user_id, sys.maxint - 1)) + SECRET_KEY).hexdigest() - s = Session(None, user_id, session_md5, None) - s.save() - return s - - def _module_get_session_from_cookie(session_cookie_string): - from django.conf.settings import SECRET_KEY - import md5 - if not session_cookie_string: - raise SessionDoesNotExist - session_md5, tamper_check = session_cookie_string[:32], session_cookie_string[32:] - if md5.new(session_md5 + SECRET_KEY + 'auth').hexdigest() != tamper_check: - raise SessionDoesNotExist - return get_object(session_md5__exact=session_md5, select_related=True) - - def _module_destroy_all_sessions(user_id): - "Destroys all sessions for a user, logging out all computers." - for session in get_list(user_id__exact=user_id): - session.delete() - - def _module_start_web_session(user_id, request, response): - "Sets the necessary cookie in the given HttpResponse object, also updates last login time for user." - from django.models.auth import users - from django.conf.settings import REGISTRATION_COOKIE_DOMAIN - user = users.get_object(pk=user_id) - user.last_login = datetime.datetime.now() - user.save() - session = create_session(user_id) - key, value = session.get_cookie() - cookie_domain = REGISTRATION_COOKIE_DOMAIN or None - response.set_cookie(key, value, domain=cookie_domain) - class Message(meta.Model): fields = ( meta.ForeignKey(User), diff --git a/django/models/core.py b/django/models/core.py index 985dc38179..e94a35b694 100644 --- a/django/models/core.py +++ b/django/models/core.py @@ -103,3 +103,57 @@ class FlatFile(meta.Model): def get_absolute_url(self): return self.url + +import base64, md5, random, sys +import cPickle as pickle + +class Session(meta.Model): + fields = ( + meta.CharField('session_key', maxlength=40, primary_key=True), + meta.TextField('session_data'), + meta.DateTimeField('expire_date'), + ) + module_constants = { + 'base64': base64, + 'md5': md5, + 'pickle': pickle, + 'random': random, + 'sys': sys, + } + + def get_decoded(self): + from django.conf.settings import SECRET_KEY + encoded_data = base64.decodestring(self.session_data) + pickled, tamper_check = encoded_data[:-32], encoded_data[-32:] + if md5.new(pickled + SECRET_KEY).hexdigest() != tamper_check: + from django.core.exceptions import SuspiciousOperation + raise SuspiciousOperation, "User tampered with session cookie." + return pickle.loads(pickled) + + def _module_encode(session_dict): + "Returns the given session dictionary pickled and encoded as a string." + from django.conf.settings import SECRET_KEY + pickled = pickle.dumps(session_dict) + pickled_md5 = md5.new(pickled + SECRET_KEY).hexdigest() + return base64.encodestring(pickled + pickled_md5) + + def _module_get_new_session_key(): + "Returns session key that isn't being used." + from django.conf.settings import SECRET_KEY + # The random module is seeded when this Apache child is created. + # Use person_id and SECRET_KEY as added salt. + while 1: + session_key = md5.new(str(random.randint(0, sys.maxint - 1)) + SECRET_KEY).hexdigest() + try: + get_object(session_key__exact=session_key) + except SessionDoesNotExist: + break + return session_key + + def _module_save(session_key, session_dict, expire_date): + s = Session(session_key, encode(session_dict), expire_date) + if session_dict: + s.save() + else: + s.delete() # Clear sessions with no data. + return s diff --git a/django/parts/auth/anonymoususers.py b/django/parts/auth/anonymoususers.py index 13fa44c434..03f294915c 100644 --- a/django/parts/auth/anonymoususers.py +++ b/django/parts/auth/anonymoususers.py @@ -1,9 +1,4 @@ -""" -Anonymous users -""" - class AnonymousUser: - def __init__(self): pass @@ -40,9 +35,5 @@ class AnonymousUser: def get_and_delete_messages(self): return [] - def add_session(self, session_md5, start_time): - "Creates Session for this User, saves it, and returns the new object" - raise NotImplementedError - def is_anonymous(self): return True diff --git a/django/parts/auth/formfields.py b/django/parts/auth/formfields.py index 3565331c8f..1ad77dbda5 100644 --- a/django/parts/auth/formfields.py +++ b/django/parts/auth/formfields.py @@ -1,4 +1,4 @@ -from django.models.auth import sessions, users +from django.models.auth import users from django.core import formfields, validators class AuthenticationForm(formfields.Manipulator): @@ -23,7 +23,7 @@ class AuthenticationForm(formfields.Manipulator): self.user_cache = None def hasCookiesEnabled(self, field_data, all_data): - if self.request and (not self.request.COOKIES.has_key(sessions.TEST_COOKIE_NAME) or self.request.COOKIES[sessions.TEST_COOKIE_NAME] != sessions.TEST_COOKIE_VALUE): + if self.request and not self.request.test_cookie_worked(): raise validators.ValidationError, "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in." def isValidUser(self, field_data, all_data): diff --git a/django/views/auth/login.py b/django/views/auth/login.py index fdb064d0b0..a8d7d143d2 100644 --- a/django/views/auth/login.py +++ b/django/views/auth/login.py @@ -1,7 +1,7 @@ from django.parts.auth.formfields import AuthenticationForm from django.core import formfields, template_loader from django.core.extensions import DjangoContext as Context -from django.models.auth import sessions +from django.models.auth import users from django.models.core import sites from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect @@ -17,14 +17,12 @@ def login(request): # Light security check -- make sure redirect_to isn't garbage. if not redirect_to or '://' in redirect_to or ' ' in redirect_to: redirect_to = '/accounts/profile/' - response = HttpResponseRedirect(redirect_to) - sessions.start_web_session(manipulator.get_user_id(), request, response) - return response + request.session[users.SESSION_KEY] = manipulator.get_user_id() + return HttpResponseRedirect(redirect_to) else: errors = {} response = HttpResponse() - # Set this cookie as a test to see whether the user accepts cookies - response.set_cookie(sessions.TEST_COOKIE_NAME, sessions.TEST_COOKIE_VALUE) + response.session.set_test_cookie() t = template_loader.get_template('registration/login') c = Context(request, { 'form': formfields.FormWrapper(manipulator, request.POST, errors), @@ -34,28 +32,21 @@ def login(request): response.write(t.render(c)) return response -def logout(request): +def logout(request, next_page=None): "Logs out the user and displays 'You are logged out' message." - if request.session: - # Do a redirect to this page until the session has been cleared. - response = HttpResponseRedirect(request.path) - # Delete the cookie by setting a cookie with an empty value and max_age=0 - response.set_cookie(request.session.get_cookie()[0], '', max_age=0) - request.session.delete() - return response - else: + try: + del request.session[users.SESSION_KEY] + except KeyError: t = template_loader.get_template('registration/logged_out') c = Context(request) return HttpResponse(t.render(c)) + else: + # Do a redirect to this page until the session has been cleared. + return HttpResponseRedirect(next_page or request.path) def logout_then_login(request): "Logs out the user if he is logged in. Then redirects to the log-in page." - response = HttpResponseRedirect('/accounts/login/') - if request.session: - # Delete the cookie by setting a cookie with an empty value and max_age=0 - response.set_cookie(request.session.get_cookie()[0], '', max_age=0) - request.session.delete() - return response + return logout(request, '/accounts/login/') def redirect_to_login(next): "Redirects the user to the login page, passing the given 'next' page" diff --git a/docs/faq.txt b/docs/faq.txt index e3e8ab4162..d5400dd98c 100644 --- a/docs/faq.txt +++ b/docs/faq.txt @@ -310,17 +310,17 @@ The login cookie isn't being set correctly, because the domain of the cookie sent out by Django doesn't match the domain in your browser. Try these two things: - * Set the ``REGISTRATION_COOKIE_DOMAIN`` setting in your admin config file + * Set the ``SESSION_COOKIE_DOMAIN`` setting in your admin config file to match your domain. For example, if you're going to "http://www.mysite.com/admin/" in your browser, in - "myproject.settings.admin" you should set ``REGISTRATION_COOKIE_DOMAIN = + "myproject.settings.admin" you should set ``SESSION_COOKIE_DOMAIN = 'www.mysite.com'``. * Some browsers (Firefox?) don't like to accept cookies from domains that don't have dots in them. If you're running the admin site on "localhost" or another domain that doesn't have a dot in it, try going to "localhost.localdomain" or "127.0.0.1". And set - ``REGISTRATION_COOKIE_DOMAIN`` accordingly. + ``SESSION_COOKIE_DOMAIN`` accordingly. I can't log in. When I enter a valid username and password, it brings up the login page again, with a "Please enter a correct username and password" error. ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
