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