bgneal@626: """Middleware for wiki integration.
bgneal@626: 
bgneal@626: """
bgneal@626: import datetime
bgneal@626: import hashlib
bgneal@626: import logging
bgneal@626: import random
bgneal@626: import string
bgneal@626: import time
bgneal@626: 
bgneal@626: from django.conf import settings
bgneal@626: import redis
bgneal@626: 
bgneal@626: from core.services import get_redis_connection
bgneal@629: from wiki.constants import SESSION_SET_MEMBER
bgneal@626: 
bgneal@626: 
bgneal@626: logger = logging.getLogger(__name__)
bgneal@626: 
bgneal@626: 
bgneal@626: def cookie_value(user, now):
bgneal@626:     """Creates the value for the external wiki cookie."""
bgneal@626: 
bgneal@626:     # The key part of the cookie is just a string that would make things
bgneal@626:     # difficult for a spoofer; something that can't be easily made up:
bgneal@626: 
bgneal@626:     h = hashlib.sha256()
bgneal@841:     h.update(user.username.encode('utf-8'))
bgneal@841:     h.update(user.email.encode('utf-8'))
bgneal@626:     h.update(now.isoformat())
bgneal@626:     h.update(''.join(random.sample(string.printable, 64)))
bgneal@626:     h.update(settings.SECRET_KEY)
bgneal@626:     key = h.hexdigest()
bgneal@626: 
bgneal@626:     parts = (user.username, user.email, key)
bgneal@626:     return '#'.join(parts)
bgneal@626: 
bgneal@626: 
bgneal@626: def create_wiki_session(request, response):
bgneal@626:     """Sets up the session for the external wiki application.
bgneal@626: 
bgneal@626:     Creates the external cookie for the Wiki.
bgneal@626:     Updates the Redis set so the Wiki can verify the cookie.
bgneal@626: 
bgneal@626:     """
bgneal@626:     now = datetime.datetime.utcnow()
bgneal@626:     value = cookie_value(request.user, now)
bgneal@626:     response.set_cookie(settings.WIKI_COOKIE_NAME,
bgneal@626:             value=value,
bgneal@626:             max_age=settings.WIKI_COOKIE_AGE,
bgneal@626:             domain=settings.WIKI_COOKIE_DOMAIN)
bgneal@626: 
bgneal@626:     # Update a sorted set in Redis with a hash of our cookie and a score
bgneal@626:     # of the current time as a timestamp. This allows us to delete old
bgneal@626:     # entries by score periodically. To verify the cookie, the external wiki
bgneal@626:     # application computes a hash of the cookie value and checks to see if
bgneal@626:     # it is in our Redis set.
bgneal@626: 
bgneal@626:     h = hashlib.sha256()
bgneal@626:     h.update(value)
bgneal@626:     name = h.hexdigest()
bgneal@626:     score = time.mktime(now.utctimetuple())
bgneal@626:     conn = get_redis_connection()
bgneal@626: 
bgneal@626:     try:
bgneal@626:         conn.zadd(settings.WIKI_REDIS_SET, score, name)
bgneal@626:     except redis.RedisError:
bgneal@626:         logger.error("Error adding wiki cookie key")
bgneal@626: 
bgneal@626:     # Store the set member name in the session so we can delete it when the
bgneal@626:     # user logs out:
bgneal@627:     request.session[SESSION_SET_MEMBER] = name
bgneal@626: 
bgneal@626: 
bgneal@627: def destroy_wiki_session(set_member, response):
bgneal@626:     """Destroys the session for the external wiki application.
bgneal@626: 
bgneal@626:     Delete the external cookie.
bgneal@627:     Deletes the member from the Redis set as this entry is no longer valid.
bgneal@626: 
bgneal@626:     """
bgneal@626:     response.delete_cookie(settings.WIKI_COOKIE_NAME,
bgneal@626:                            domain=settings.WIKI_COOKIE_DOMAIN)
bgneal@626: 
bgneal@627:     if set_member:
bgneal@627:         conn = get_redis_connection()
bgneal@627:         try:
bgneal@627:             conn.zrem(settings.WIKI_REDIS_SET, set_member)
bgneal@627:         except redis.RedisError:
bgneal@627:             logger.error("Error deleting wiki cookie set member")
bgneal@626: 
bgneal@626: 
bgneal@626: class WikiMiddleware(object):
bgneal@626:     """
bgneal@633:     Check for flags on the request object to determine when to set or delete an
bgneal@626:     external cookie for the wiki application. When creating a cookie, also
bgneal@626:     set an entry in Redis that the wiki application can validate to prevent
bgneal@626:     spoofing.
bgneal@626: 
bgneal@626:     """
bgneal@626: 
bgneal@626:     def process_response(self, request, response):
bgneal@626: 
bgneal@629:         if hasattr(request, 'wiki_set_cookie'):
bgneal@626:             create_wiki_session(request, response)
bgneal@627:         elif hasattr(request, 'wiki_delete_cookie'):
bgneal@627:             destroy_wiki_session(request.wiki_delete_cookie, response)
bgneal@626: 
bgneal@626:         return response