| 1 | r""" |
| 2 | >>> from django.conf import settings |
| 3 | >>> from django.contrib.sessions.backends.appengine import SessionStore as AppEngineSession |
| 4 | >>> from django.contrib.sessions.backends.base import SessionBase |
| 5 | >>> appengine_session = AppEngineSession() |
| 6 | >>> appengine_session.modified |
| 7 | False |
| 8 | >>> appengine_session['cat'] = "dog" |
| 9 | >>> appengine_session.modified |
| 10 | True |
| 11 | >>> appengine_session.pop('cat') |
| 12 | 'dog' |
| 13 | >>> appengine_session.pop('some key', 'does not exist') |
| 14 | 'does not exist' |
| 15 | >>> appengine_session.save() |
| 16 | >>> appengine_session.exists(appengine_session.session_key) |
| 17 | True |
| 18 | >>> appengine_session.delete(appengine_session.session_key) |
| 19 | >>> appengine_session.exists(appengine_session.session_key) |
| 20 | False |
| 21 | >>> appengine_session.setdefault('foo', 'bar') |
| 22 | 'bar' |
| 23 | >>> appengine_session.setdefault('foo', 'baz') |
| 24 | 'bar' |
| 25 | """ |
| 26 | import md5 |
| 27 | import sys |
| 28 | import base64 |
| 29 | import datetime |
| 30 | import cPickle as pickle |
| 31 | |
| 32 | from django.conf import settings |
| 33 | from django.contrib.sessions.backends.base import SessionBase |
| 34 | from django.core.exceptions import SuspiciousOperation |
| 35 | |
| 36 | # add the weird appengine location (here: MacOSX & Windows) to the pythonpath |
| 37 | # if script is started from the command shell, not needed if you are running |
| 38 | # the your app with dev_appserver.py |
| 39 | if __name__ == '__main__': |
| 40 | sys.path.insert(0, '/usr/local/google_appengine/', ) |
| 41 | |
| 42 | # needed for using datastore on the commandline |
| 43 | from google.appengine.api import apiproxy_stub_map |
| 44 | from google.appengine.api import datastore_file_stub |
| 45 | apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap() |
| 46 | apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', |
| 47 | datastore_file_stub.DatastoreFileStub('whatever', |
| 48 | '/dev/null', '/dev/null')) |
| 49 | |
| 50 | try: |
| 51 | from google.appengine.ext import db |
| 52 | except ImportError: |
| 53 | raise ImportError("The app engine datastore module could not be found. Please add it to your PYTHONPATH (e.g. '/usr/local/google_appengine/' on MacOS)") |
| 54 | |
| 55 | class Session(db.Model): |
| 56 | session_data = db.TextProperty(required=True) |
| 57 | expire_date = db.DateTimeProperty(required=True) |
| 58 | |
| 59 | def get_decoded(self): |
| 60 | encoded_data = base64.decodestring(self.session_data) |
| 61 | pickled, tamper_check = encoded_data[:-32], encoded_data[-32:] |
| 62 | if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: |
| 63 | raise SuspiciousOperation, "User tampered with session cookie." |
| 64 | try: |
| 65 | return pickle.loads(pickled) |
| 66 | # Unpickling can cause a variety of exceptions. If something happens, |
| 67 | # just return an empty dictionary (an empty session). |
| 68 | except: |
| 69 | return {} |
| 70 | |
| 71 | class SessionStore(SessionBase): |
| 72 | """ |
| 73 | Implements appengine session store |
| 74 | """ |
| 75 | def __init__(self, session_key=None): |
| 76 | self.session_key_prefix = 'sessionid:' |
| 77 | super(SessionStore, self).__init__(session_key) |
| 78 | |
| 79 | def _prefix_key(self, session_key=None): |
| 80 | """ |
| 81 | Add prefix to session key to prevent BadArgumentError exceptions from |
| 82 | appengine (Names may not begin with a digit). |
| 83 | """ |
| 84 | if session_key is None: |
| 85 | session_key = self.session_key |
| 86 | return '%s:%s' % (self.session_key_prefix, session_key) |
| 87 | |
| 88 | def load(self): |
| 89 | try: |
| 90 | s = Session.get_by_key_name(self._prefix_key()) |
| 91 | if s and s.expire_date >= datetime.datetime.now(): |
| 92 | return self.decode(s.session_data) |
| 93 | raise SuspiciousOperation |
| 94 | except SuspiciousOperation: |
| 95 | # Create a new session_key for extra security. |
| 96 | self.session_key = self._prefix_key(self._get_new_session_key()) |
| 97 | self._session_cache = {} |
| 98 | |
| 99 | # Save immediately to minimize collision |
| 100 | self.save() |
| 101 | # Ensure the user is notified via a new cookie. |
| 102 | self.modified = True |
| 103 | return {} |
| 104 | |
| 105 | def exists(self, session_key): |
| 106 | if Session.get_by_key_name(self._prefix_key(session_key)): |
| 107 | return True |
| 108 | return False |
| 109 | |
| 110 | def save(self): |
| 111 | Session(key_name = self._prefix_key(self.session_key), |
| 112 | session_data = self.encode(self._session), |
| 113 | expire_date = (datetime.datetime.now() + |
| 114 | datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE)) |
| 115 | ).put() |
| 116 | |
| 117 | def delete(self, session_key): |
| 118 | s = Session.get_by_key_name(self._prefix_key(session_key)) |
| 119 | if s: |
| 120 | s.delete() |
| 121 | |
| 122 | if __name__ == '__main__': |
| 123 | import doctest |
| 124 | doctest.testmod() |