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