| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
- # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
- """
- Creates a session object in your WSGI environment.
- Use like:
- ..code-block:: Python
- environ['paste.session.factory']()
- This will return a dictionary. The contents of this dictionary will
- be saved to disk when the request is completed. The session will be
- created when you first fetch the session dictionary, and a cookie will
- be sent in that case. There's current no way to use sessions without
- cookies, and there's no way to delete a session except to clear its
- data.
- @@: This doesn't do any locking, and may cause problems when a single
- session is accessed concurrently. Also, it loads and saves the
- session for each request, with no caching. Also, sessions aren't
- expired.
- """
- try:
- # Python 3
- from http.cookies import SimpleCookie
- except ImportError:
- # Python 2
- from Cookie import SimpleCookie
- import time
- import random
- import os
- import datetime
- import six
- import threading
- import tempfile
- try:
- import cPickle
- except ImportError:
- import pickle as cPickle
- try:
- from hashlib import md5
- except ImportError:
- from md5 import md5
- from paste import wsgilib
- from paste import request
- class SessionMiddleware(object):
- def __init__(self, application, global_conf=None, **factory_kw):
- self.application = application
- self.factory_kw = factory_kw
- def __call__(self, environ, start_response):
- session_factory = SessionFactory(environ, **self.factory_kw)
- environ['paste.session.factory'] = session_factory
- remember_headers = []
- def session_start_response(status, headers, exc_info=None):
- if not session_factory.created:
- remember_headers[:] = [status, headers]
- return start_response(status, headers)
- headers.append(session_factory.set_cookie_header())
- return start_response(status, headers, exc_info)
- app_iter = self.application(environ, session_start_response)
- def start():
- if session_factory.created and remember_headers:
- # Tricky bastard used the session after start_response
- status, headers = remember_headers
- headers.append(session_factory.set_cookie_header())
- exc = ValueError(
- "You cannot get the session after content from the "
- "app_iter has been returned")
- start_response(status, headers, (exc.__class__, exc, None))
- def close():
- if session_factory.used:
- session_factory.close()
- return wsgilib.add_start_close(app_iter, start, close)
- class SessionFactory(object):
- def __init__(self, environ, cookie_name='_SID_',
- session_class=None,
- session_expiration=60*12, # in minutes
- **session_class_kw):
- self.created = False
- self.used = False
- self.environ = environ
- self.cookie_name = cookie_name
- self.session = None
- self.session_class = session_class or FileSession
- self.session_class_kw = session_class_kw
- self.expiration = session_expiration
- def __call__(self):
- self.used = True
- if self.session is not None:
- return self.session.data()
- cookies = request.get_cookies(self.environ)
- session = None
- if self.cookie_name in cookies:
- self.sid = cookies[self.cookie_name].value
- try:
- session = self.session_class(self.sid, create=False,
- **self.session_class_kw)
- except KeyError:
- # Invalid SID
- pass
- if session is None:
- self.created = True
- self.sid = self.make_sid()
- session = self.session_class(self.sid, create=True,
- **self.session_class_kw)
- session.clean_up()
- self.session = session
- return session.data()
- def has_session(self):
- if self.session is not None:
- return True
- cookies = request.get_cookies(self.environ)
- if cookies.has_key(self.cookie_name):
- return True
- return False
- def make_sid(self):
- # @@: need better algorithm
- return (''.join(['%02d' % x for x in time.localtime(time.time())[:6]])
- + '-' + self.unique_id())
- def unique_id(self, for_object=None):
- """
- Generates an opaque, identifier string that is practically
- guaranteed to be unique. If an object is passed, then its
- id() is incorporated into the generation. Relies on md5 and
- returns a 32 character long string.
- """
- r = [time.time(), random.random()]
- if hasattr(os, 'times'):
- r.append(os.times())
- if for_object is not None:
- r.append(id(for_object))
- content = str(r)
- if six.PY3:
- content = content.encode('utf8')
- md5_hash = md5(content)
- try:
- return md5_hash.hexdigest()
- except AttributeError:
- # Older versions of Python didn't have hexdigest, so we'll
- # do it manually
- hexdigest = []
- for char in md5_hash.digest():
- hexdigest.append('%02x' % ord(char))
- return ''.join(hexdigest)
- def set_cookie_header(self):
- c = SimpleCookie()
- c[self.cookie_name] = self.sid
- c[self.cookie_name]['path'] = '/'
- gmt_expiration_time = time.gmtime(time.time() + (self.expiration * 60))
- c[self.cookie_name]['expires'] = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", gmt_expiration_time)
- name, value = str(c).split(': ', 1)
- return (name, value)
- def close(self):
- if self.session is not None:
- self.session.close()
- last_cleanup = None
- cleaning_up = False
- cleanup_cycle = datetime.timedelta(seconds=15*60) #15 min
- class FileSession(object):
- def __init__(self, sid, create=False, session_file_path=tempfile.gettempdir(),
- chmod=None,
- expiration=2880, # in minutes: 48 hours
- ):
- if chmod and isinstance(chmod, (six.binary_type, six.text_type)):
- chmod = int(chmod, 8)
- self.chmod = chmod
- if not sid:
- # Invalid...
- raise KeyError
- self.session_file_path = session_file_path
- self.sid = sid
- if not create:
- if not os.path.exists(self.filename()):
- raise KeyError
- self._data = None
- self.expiration = expiration
- def filename(self):
- return os.path.join(self.session_file_path, self.sid)
- def data(self):
- if self._data is not None:
- return self._data
- if os.path.exists(self.filename()):
- f = open(self.filename(), 'rb')
- self._data = cPickle.load(f)
- f.close()
- else:
- self._data = {}
- return self._data
- def close(self):
- if self._data is not None:
- filename = self.filename()
- exists = os.path.exists(filename)
- if not self._data:
- if exists:
- os.unlink(filename)
- else:
- f = open(filename, 'wb')
- cPickle.dump(self._data, f)
- f.close()
- if not exists and self.chmod:
- os.chmod(filename, self.chmod)
- def _clean_up(self):
- global cleaning_up
- try:
- exp_time = datetime.timedelta(seconds=self.expiration*60)
- now = datetime.datetime.now()
- #Open every session and check that it isn't too old
- for root, dirs, files in os.walk(self.session_file_path):
- for f in files:
- self._clean_up_file(f, exp_time=exp_time, now=now)
- finally:
- cleaning_up = False
- def _clean_up_file(self, f, exp_time, now):
- t = f.split("-")
- if len(t) != 2:
- return
- t = t[0]
- try:
- sess_time = datetime.datetime(
- int(t[0:4]),
- int(t[4:6]),
- int(t[6:8]),
- int(t[8:10]),
- int(t[10:12]),
- int(t[12:14]))
- except ValueError:
- # Probably not a session file at all
- return
- if sess_time + exp_time < now:
- os.remove(os.path.join(self.session_file_path, f))
- def clean_up(self):
- global last_cleanup, cleanup_cycle, cleaning_up
- now = datetime.datetime.now()
- if cleaning_up:
- return
- if not last_cleanup or last_cleanup + cleanup_cycle < now:
- if not cleaning_up:
- cleaning_up = True
- try:
- last_cleanup = now
- t = threading.Thread(target=self._clean_up)
- t.start()
- except:
- # Normally _clean_up should set cleaning_up
- # to false, but if something goes wrong starting
- # it...
- cleaning_up = False
- raise
- class _NoDefault(object):
- def __repr__(self):
- return '<dynamic default>'
- NoDefault = _NoDefault()
- def make_session_middleware(
- app, global_conf,
- session_expiration=NoDefault,
- expiration=NoDefault,
- cookie_name=NoDefault,
- session_file_path=NoDefault,
- chmod=NoDefault):
- """
- Adds a middleware that handles sessions for your applications.
- The session is a peristent dictionary. To get this dictionary
- in your application, use ``environ['paste.session.factory']()``
- which returns this persistent dictionary.
- Configuration:
- session_expiration:
- The time each session lives, in minutes. This controls
- the cookie expiration. Default 12 hours.
- expiration:
- The time each session lives on disk. Old sessions are
- culled from disk based on this. Default 48 hours.
- cookie_name:
- The cookie name used to track the session. Use different
- names to avoid session clashes.
- session_file_path:
- Sessions are put in this location, default /tmp.
- chmod:
- The octal chmod you want to apply to new sessions (e.g., 660
- to make the sessions group readable/writable)
- Each of these also takes from the global configuration. cookie_name
- and chmod take from session_cookie_name and session_chmod
- """
- if session_expiration is NoDefault:
- session_expiration = global_conf.get('session_expiration', 60*12)
- session_expiration = int(session_expiration)
- if expiration is NoDefault:
- expiration = global_conf.get('expiration', 60*48)
- expiration = int(expiration)
- if cookie_name is NoDefault:
- cookie_name = global_conf.get('session_cookie_name', '_SID_')
- if session_file_path is NoDefault:
- session_file_path = global_conf.get('session_file_path', '/tmp')
- if chmod is NoDefault:
- chmod = global_conf.get('session_chmod', None)
- return SessionMiddleware(
- app, session_expiration=session_expiration,
- expiration=expiration, cookie_name=cookie_name,
- session_file_path=session_file_path, chmod=chmod)
|