Code

Ticket #7008: appengine_session_backend.3.jezdez.diff

File appengine_session_backend.3.jezdez.diff, 5.6 KB (added by jezdez, 6 years ago)

Uses session_key as key_name and Session.get_by_key_name() for better performance. Added session_key_prefix to circumvent BadArgumentError.

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