~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/support/werkzeug/contrib/sessions.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20080622211713-inlv5k4eifxckelr
ImportĀ upstreamĀ versionĀ 1.7.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
r"""
3
 
    werkzeug.contrib.sessions
4
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~
5
 
 
6
 
    This module contains some helper classes that help one to add session
7
 
    support to a python WSGI application.  For full client-side session
8
 
    storage see :mod:`~werkzeug.contrib.securecookie` which implements a
9
 
    secure, client-side session storage.
10
 
 
11
 
 
12
 
    Application Integration
13
 
    =======================
14
 
 
15
 
    ::
16
 
 
17
 
        from werkzeug.contrib.sessions import SessionMiddleware, \
18
 
             FilesystemSessionStore
19
 
 
20
 
        app = SessionMiddleware(app, FilesystemSessionStore())
21
 
 
22
 
    The current session will then appear in the WSGI environment as
23
 
    `werkzeug.session`.  However it's recommended to not use the middleware
24
 
    but the stores directly in the application.  However for very simple
25
 
    scripts a middleware for sessions could be sufficient.
26
 
 
27
 
    This module does not implement methods or ways to check if a session is
28
 
    expired.  That should be done by a cronjob and storage specific.  For
29
 
    example to prune unused filesystem sessions one could check the modified
30
 
    time of the files.  It sessions are stored in the database the new()
31
 
    method should add an expiration timestamp for the session.
32
 
 
33
 
    For better flexibility it's recommended to not use the middleware but the
34
 
    store and session object directly in the application dispatching::
35
 
 
36
 
        session_store = FilesystemSessionStore()
37
 
 
38
 
        def application(environ, start_response):
39
 
            request = Request(environ)
40
 
            sid = request.cookie.get('cookie_name')
41
 
            if sid is None:
42
 
                request.session = session_store.new()
43
 
            else:
44
 
                request.session = session_store.get(sid)
45
 
            response = get_the_response_object(request)
46
 
            if request.session.should_save:
47
 
                session_store.save(request.session)
48
 
                response.set_cookie('cookie_name', request.session.sid)
49
 
            return response(environ, start_response)
50
 
 
51
 
    :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details.
52
 
    :license: BSD, see LICENSE for more details.
53
 
"""
54
 
import re
55
 
import os
56
 
from os import path
57
 
from time import time
58
 
from random import random
59
 
try:
60
 
    from hashlib import sha1
61
 
except ImportError:
62
 
    from sha import new as sha1
63
 
from cPickle import dump, load, HIGHEST_PROTOCOL
64
 
 
65
 
from werkzeug.utils import ClosingIterator, dump_cookie, parse_cookie
66
 
from werkzeug.datastructures import CallbackDict
67
 
 
68
 
 
69
 
_sha1_re = re.compile(r'^[a-fA-F0-9]{40}$')
70
 
 
71
 
 
72
 
def _urandom():
73
 
    if hasattr(os, 'urandom'):
74
 
        return os.urandom(30)
75
 
    return random()
76
 
 
77
 
 
78
 
def generate_key(salt=None):
79
 
    return sha1('%s%s%s' % (salt, time(), _urandom())).hexdigest()
80
 
 
81
 
 
82
 
class ModificationTrackingDict(CallbackDict):
83
 
    __slots__ = ('modified',)
84
 
 
85
 
    def __init__(self, *args, **kwargs):
86
 
        def on_update(self):
87
 
            self.modified = True
88
 
        self.modified = False
89
 
        CallbackDict.__init__(self, on_update=on_update)
90
 
        dict.update(self, *args, **kwargs)
91
 
 
92
 
    def copy(self):
93
 
        """Create a flat copy of the dict."""
94
 
        missing = object()
95
 
        result = object.__new__(self.__class__)
96
 
        for name in self.__slots__:
97
 
            val = getattr(self, name, missing)
98
 
            if val is not missing:
99
 
                setattr(result, name, val)
100
 
        return result
101
 
 
102
 
    def __copy__(self):
103
 
        return self.copy()
104
 
 
105
 
 
106
 
class Session(ModificationTrackingDict):
107
 
    """Subclass of a dict that keeps track of direct object changes.  Changes
108
 
    in mutable structures are not tracked, for those you have to set
109
 
    `modified` to `True` by hand.
110
 
    """
111
 
    __slots__ = ModificationTrackingDict.__slots__ + ('sid', 'new')
112
 
 
113
 
    def __init__(self, data, sid, new=False):
114
 
        ModificationTrackingDict.__init__(self, data)
115
 
        self.sid = sid
116
 
        self.new = new
117
 
 
118
 
    def __repr__(self):
119
 
        return '<%s %s%s>' % (
120
 
            self.__class__.__name__,
121
 
            dict.__repr__(self),
122
 
            self.should_save and '*' or ''
123
 
        )
124
 
 
125
 
    @property
126
 
    def should_save(self):
127
 
        """True if the session should be saved."""
128
 
        return self.modified or self.new
129
 
 
130
 
 
131
 
class SessionStore(object):
132
 
    """Baseclass for all session stores.  The Werkzeug contrib module does not
133
 
    implement any useful stores besides the filesystem store, application
134
 
    developers are encouraged to create their own stores.
135
 
 
136
 
    :param session_class: The session class to use.  Defaults to
137
 
                          :class:`Session`.
138
 
    """
139
 
 
140
 
    def __init__(self, session_class=None):
141
 
        if session_class is None:
142
 
            session_class = Session
143
 
        self.session_class = session_class
144
 
 
145
 
    def is_valid_key(self, key):
146
 
        """Check if a key has the correct format."""
147
 
        return _sha1_re.match(key) is not None
148
 
 
149
 
    def generate_key(self, salt=None):
150
 
        """Simple function that generates a new session key."""
151
 
        return generate_key(salt)
152
 
 
153
 
    def new(self):
154
 
        """Generate a new session."""
155
 
        return self.session_class({}, self.generate_key(), True)
156
 
 
157
 
    def save(self, session):
158
 
        """Save a session."""
159
 
 
160
 
    def save_if_modified(self, session):
161
 
        """Save if a session class wants an update."""
162
 
        if session.should_save:
163
 
            self.save(session)
164
 
 
165
 
    def delete(self, session):
166
 
        """Delete a session."""
167
 
 
168
 
    def get(self, sid):
169
 
        """Get a session for this sid or a new session object.  This method
170
 
        has to check if the session key is valid and create a new session if
171
 
        that wasn't the case.
172
 
        """
173
 
        return self.session_class({}, sid, True)
174
 
 
175
 
 
176
 
class FilesystemSessionStore(SessionStore):
177
 
    """Simple example session store that saves sessions in the filesystem like
178
 
    PHP does.
179
 
 
180
 
    :param path: the path to the folder used for storing the sessions.
181
 
                 If not provided the default temporary directory is used.
182
 
    :param filename_template: a string template used to give the session
183
 
                              a filename.  ``%s`` is replaced with the
184
 
                              session id.
185
 
    :param session_class: The session class to use.  Defaults to
186
 
                          :class:`Session`.
187
 
    """
188
 
 
189
 
    def __init__(self, path=None, filename_template='werkzeug_%s.sess',
190
 
                 session_class=None):
191
 
        SessionStore.__init__(self, session_class)
192
 
        if path is None:
193
 
            from tempfile import gettempdir
194
 
            path = gettempdir()
195
 
        self.path = path
196
 
        self.filename_template = filename_template
197
 
 
198
 
    def get_session_filename(self, sid):
199
 
        return path.join(self.path, self.filename_template % sid)
200
 
 
201
 
    def save(self, session):
202
 
        f = file(self.get_session_filename(session.sid), 'wb')
203
 
        try:
204
 
            dump(dict(session), f, HIGHEST_PROTOCOL)
205
 
        finally:
206
 
            f.close()
207
 
 
208
 
    def delete(self, session):
209
 
        fn = self.get_session_filename(session.sid)
210
 
        try:
211
 
            # Late import because Google Appengine won't allow os.unlink
212
 
            from os import unlink
213
 
            unlink(fn)
214
 
        except OSError:
215
 
            pass
216
 
 
217
 
    def get(self, sid):
218
 
        fn = self.get_session_filename(sid)
219
 
        if not self.is_valid_key(sid) or not path.exists(fn):
220
 
            return self.new()
221
 
        else:
222
 
            f = file(fn, 'rb')
223
 
            try:
224
 
                data = load(f)
225
 
            finally:
226
 
                f.close()
227
 
        return self.session_class(data, sid, False)
228
 
 
229
 
 
230
 
class SessionMiddleware(object):
231
 
    """A simple middleware that puts the session object of a store provided
232
 
    into the WSGI environ.  It automatically sets cookies and restores
233
 
    sessions.
234
 
 
235
 
    However a middleware is not the preferred solution because it won't be as
236
 
    fast as sessions managed by the application itself and will put a key into
237
 
    the WSGI environment only relevant for the application which is against
238
 
    the concept of WSGI.
239
 
 
240
 
    The cookie parameters are the same as for the :func:`~werkzeug.dump_cookie`
241
 
    function just prefixed with ``cookie_``.  Additionally `max_age` is
242
 
    called `cookie_age` and not `cookie_max_age` because of backwards
243
 
    compatibility.
244
 
    """
245
 
 
246
 
    def __init__(self, app, store, cookie_name='session_id',
247
 
                 cookie_age=None, cookie_expires=None, cookie_path='/',
248
 
                 cookie_domain=None, cookie_secure=None,
249
 
                 cookie_httponly=False, environ_key='werkzeug.session'):
250
 
        self.app = app
251
 
        self.store = store
252
 
        self.cookie_name = cookie_name
253
 
        self.cookie_age = cookie_age
254
 
        self.cookie_expires = cookie_expires
255
 
        self.cookie_path = cookie_path
256
 
        self.cookie_domain = cookie_domain
257
 
        self.cookie_secure = cookie_secure
258
 
        self.cookie_httponly = cookie_httponly
259
 
        self.environ_key = environ_key
260
 
 
261
 
    def __call__(self, environ, start_response):
262
 
        cookie = parse_cookie(environ.get('HTTP_COOKIE', ''))
263
 
        sid = cookie.get(self.cookie_name, None)
264
 
        if sid is None:
265
 
            session = self.store.new()
266
 
        else:
267
 
            session = self.store.get(sid)
268
 
        environ[self.environ_key] = session
269
 
 
270
 
        def injecting_start_response(status, headers, exc_info=None):
271
 
            if session.should_save:
272
 
                self.store.save(session)
273
 
                headers.append(('Set-Cookie', dump_cookie(self.cookie_name,
274
 
                                session.sid, self.cookie_age,
275
 
                                self.cookie_expires, self.cookie_path,
276
 
                                self.cookie_domain, self.cookie_secure,
277
 
                                self.cookie_httponly)))
278
 
            return start_response(status, headers, exc_info)
279
 
        return ClosingIterator(self.app(environ, injecting_start_response),
280
 
                               lambda: self.store.save_if_modified(session))