changeset 627:a4300639c6e7

Wiki integration. Create task to delete old cookie records. Rework logic upon logout, as session will not be available. Set an attribute on the request instead.
author Brian Neal <bgneal@gmail.com>
date Mon, 12 Nov 2012 15:10:52 -0600
parents a6bc1e2efa63
children c6292e46e617
files sg101/settings/base.py wiki/constants.py wiki/middleware.py wiki/signals.py wiki/tasks.py wiki/tests.py
diffstat 6 files changed, 88 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/sg101/settings/base.py	Wed Nov 07 20:17:33 2012 -0600
+++ b/sg101/settings/base.py	Mon Nov 12 15:10:52 2012 -0600
@@ -244,6 +244,10 @@
         "task": "custom_search.tasks.process_search_queue_task",
         "schedule": crontab(minute='*/20'),
     },
+    "expire_wiki_cookies": {
+        "task": "wiki.tasks.expire_cookies",
+        "schedule": crontab(minute=0, hour=2),
+    },
 }
 
 #######################################################################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/constants.py	Mon Nov 12 15:10:52 2012 -0600
@@ -0,0 +1,6 @@
+"""Constants used throughout the wiki application
+
+"""
+
+SESSION_SET_FLAG = 'wiki_set_cookie'
+SESSION_SET_MEMBER = 'wiki_redis_set_member'
--- a/wiki/middleware.py	Wed Nov 07 20:17:33 2012 -0600
+++ b/wiki/middleware.py	Mon Nov 12 15:10:52 2012 -0600
@@ -12,9 +12,9 @@
 import redis
 
 from core.services import get_redis_connection
+from wiki.constants import SESSION_SET_FLAG, SESSION_SET_MEMBER
 
 
-SESSION_KEY = 'wiki_redis_key'
 logger = logging.getLogger(__name__)
 
 
@@ -68,32 +68,25 @@
 
     # Store the set member name in the session so we can delete it when the
     # user logs out:
-    request.session[SESSION_KEY] = name
+    request.session[SESSION_SET_MEMBER] = name
 
 
-def destroy_wiki_session(request, response):
+def destroy_wiki_session(set_member, response):
     """Destroys the session for the external wiki application.
 
     Delete the external cookie.
-    Deletes the key from the Redis set as this entry is no longer valid.
+    Deletes the member from the Redis set as this entry is no longer valid.
 
     """
     response.delete_cookie(settings.WIKI_COOKIE_NAME,
                            domain=settings.WIKI_COOKIE_DOMAIN)
 
-    try:
-        key = request.session[SESSION_KEY]
-    except KeyError:
-        # Hmmm, perhaps user logged in before this application was installed.
-        return
-
-    conn = get_redis_connection()
-    try:
-        conn.zrem(settings.WIKI_REDIS_SET, key)
-    except redis.RedisError:
-        logger.error("Error deleting wiki cookie key")
-
-    del request.session[SESSION_KEY]
+    if set_member:
+        conn = get_redis_connection()
+        try:
+            conn.zrem(settings.WIKI_REDIS_SET, set_member)
+        except redis.RedisError:
+            logger.error("Error deleting wiki cookie set member")
 
 
 class WikiMiddleware(object):
@@ -107,14 +100,13 @@
 
     def process_response(self, request, response):
 
-        if request.session.get('wiki_set_cookie', False):
-            del request.session['wiki_set_cookie']
+        if request.session.get(SESSION_SET_FLAG, False):
+            del request.session[SESSION_SET_FLAG]
 
             create_wiki_session(request, response)
 
-        elif request.session.get('wiki_delete_cookie', False):
-            del request.session['wiki_delete_cookie']
+        elif hasattr(request, 'wiki_delete_cookie'):
 
-            destroy_wiki_session(request, response)
+            destroy_wiki_session(request.wiki_delete_cookie, response)
 
         return response
--- a/wiki/signals.py	Wed Nov 07 20:17:33 2012 -0600
+++ b/wiki/signals.py	Mon Nov 12 15:10:52 2012 -0600
@@ -5,6 +5,7 @@
 
 from django.contrib.auth.signals import user_logged_in, user_logged_out
 
+from wiki.constants import SESSION_SET_FLAG, SESSION_SET_MEMBER
 
 logger = logging.getLogger(__name__)
 
@@ -17,7 +18,7 @@
     """
     logger.info('User login: %s', user.username)
 
-    request.session['wiki_set_cookie'] = True
+    request.session[SESSION_SET_FLAG] = True
 
 
 def logout_callback(sender, request, user, **kwargs):
@@ -25,10 +26,16 @@
 
     Sets a flag for the middleware to delete the external cookie.
 
+    Since the user is about to logout, her session will be wiped out after
+    this function returns. This forces us to set an attribute on the request
+    object so that the response middleware can delete the wiki's cookie.
+
     """
     logger.info('User logout: %s', user.username)
 
-    request.session['wiki_delete_cookie'] = True
+    # Remember what Redis set member to delete by adding an attribute to the
+    # request object:
+    request.wiki_delete_cookie = request.session.get(SESSION_SET_MEMBER)
 
 
 user_logged_in.connect(login_callback, dispatch_uid='wiki.signals.login')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/tasks.py	Mon Nov 12 15:10:52 2012 -0600
@@ -0,0 +1,48 @@
+"""
+Celery tasks for the wiki app.
+
+"""
+import datetime
+import logging
+import time
+
+from celery.task import task
+from django.conf import settings
+import redis
+
+from core.services import get_redis_connection
+
+
+logger = logging.getLogger(__name__)
+
+
+@task
+def expire_cookies():
+    """
+    Periodically run this task to remove expired cookies from the Redis set
+    that is shared between this Django application & the MoinMoin wiki for
+    authentication.
+
+    """
+    now = datetime.datetime.utcnow()
+    cutoff = now - datetime.timedelta(seconds=settings.WIKI_COOKIE_AGE)
+    min_score = time.mktime(cutoff.utctimetuple())
+
+    conn = get_redis_connection()
+
+    set_name = settings.WIKI_REDIS_SET
+    try:
+        count = conn.zcard(set_name)
+    except redis.RedisError:
+        logger.error("Error getting zcard")
+        return
+
+    try:
+        removed = conn.zremrangebyscore(set_name, 0.0, min_score)
+    except redis.RedisError:
+        logger.error("Error removing by score")
+        return
+
+    total = count - removed
+    logger.info("Expire wiki cookies: removed %d, total is now %d",
+            removed, total)
--- a/wiki/tests.py	Wed Nov 07 20:17:33 2012 -0600
+++ b/wiki/tests.py	Mon Nov 12 15:10:52 2012 -0600
@@ -12,7 +12,8 @@
 from django.conf import settings
 
 from core.services import get_redis_connection
-from wiki.middleware import WikiMiddleware, SESSION_KEY
+from wiki.middleware import WikiMiddleware
+from wiki.constants import SESSION_SET_FLAG, SESSION_SET_MEMBER
 
 
 class MiddleWareTestCase(TestCase):
@@ -34,7 +35,7 @@
         request.user = self.user
         response = HttpResponse()
 
-        request.session['wiki_set_cookie'] = True
+        request.session[SESSION_SET_FLAG] = True
         response = self.mw.process_response(request, response)
 
         self.assertIsNone(request.session.get('wiki_set_cookie'))
@@ -50,7 +51,7 @@
             cookie_val = cookie.value
             try:
                 user, email, key = cookie_val.split('#')
-            except KeyError:
+            except ValueError:
                 self.fail('invalid cookie value')
             else:
                 self.assertEqual(user, self.user.username)
@@ -68,16 +69,14 @@
         session_start = datetime.datetime.fromtimestamp(score)
         self.assertLess(now - session_start, datetime.timedelta(seconds=2))
 
-        session_key = request.session.get(SESSION_KEY)
-        self.assertTrue(session_key and session_key == member)
+        session_member = request.session.get(SESSION_SET_MEMBER)
+        self.assertTrue(session_member and session_member == member)
 
         # test the destroy session logic
 
-        request.session['wiki_delete_cookie'] = True
+        request.wiki_delete_cookie = member
         response = self.mw.process_response(request, response)
 
-        self.assertIsNone(request.session.get('wiki_delete_cookie'))
-
         cookie = response.cookies.get(settings.WIKI_COOKIE_NAME)
         self.assertIsNotNone(cookie)
         if cookie:
@@ -88,4 +87,3 @@
             self.assertEqual(cookie['expires'], 'Thu, 01-Jan-1970 00:00:00 GMT')
 
         self.assertEqual(self.conn.zcard(settings.WIKI_REDIS_SET), 0)
-        self.assertIsNone(request.session.get(SESSION_KEY))