PageRenderTime 39ms CodeModel.GetById 8ms RepoModel.GetById 1ms app.codeStats 0ms

/django/contrib/sessions/backends/base.py

https://bitbucket.org/Squazic/homepage
Python | 310 lines | 255 code | 33 blank | 22 comment | 20 complexity | eddef3dc6ea055e0b27516e0e6a0fdd8 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. import base64
  2. import os
  3. import random
  4. import sys
  5. import time
  6. from datetime import datetime, timedelta
  7. try:
  8. import cPickle as pickle
  9. except ImportError:
  10. import pickle
  11. from django.conf import settings
  12. from django.core.exceptions import SuspiciousOperation
  13. from django.utils.hashcompat import md5_constructor
  14. from django.utils.crypto import constant_time_compare, salted_hmac
  15. # Use the system (hardware-based) random number generator if it exists.
  16. if hasattr(random, 'SystemRandom'):
  17. randrange = random.SystemRandom().randrange
  18. else:
  19. randrange = random.randrange
  20. MAX_SESSION_KEY = 18446744073709551616L # 2 << 63
  21. class CreateError(Exception):
  22. """
  23. Used internally as a consistent exception type to catch from save (see the
  24. docstring for SessionBase.save() for details).
  25. """
  26. pass
  27. class SessionBase(object):
  28. """
  29. Base class for all Session classes.
  30. """
  31. TEST_COOKIE_NAME = 'testcookie'
  32. TEST_COOKIE_VALUE = 'worked'
  33. def __init__(self, session_key=None):
  34. self._session_key = session_key
  35. self.accessed = False
  36. self.modified = False
  37. def __contains__(self, key):
  38. return key in self._session
  39. def __getitem__(self, key):
  40. return self._session[key]
  41. def __setitem__(self, key, value):
  42. self._session[key] = value
  43. self.modified = True
  44. def __delitem__(self, key):
  45. del self._session[key]
  46. self.modified = True
  47. def keys(self):
  48. return self._session.keys()
  49. def items(self):
  50. return self._session.items()
  51. def get(self, key, default=None):
  52. return self._session.get(key, default)
  53. def pop(self, key, *args):
  54. self.modified = self.modified or key in self._session
  55. return self._session.pop(key, *args)
  56. def setdefault(self, key, value):
  57. if key in self._session:
  58. return self._session[key]
  59. else:
  60. self.modified = True
  61. self._session[key] = value
  62. return value
  63. def set_test_cookie(self):
  64. self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
  65. def test_cookie_worked(self):
  66. return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
  67. def delete_test_cookie(self):
  68. del self[self.TEST_COOKIE_NAME]
  69. def _hash(self, value):
  70. key_salt = "django.contrib.sessions" + self.__class__.__name__
  71. return salted_hmac(key_salt, value).hexdigest()
  72. def encode(self, session_dict):
  73. "Returns the given session dictionary pickled and encoded as a string."
  74. pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
  75. hash = self._hash(pickled)
  76. return base64.encodestring(hash + ":" + pickled)
  77. def decode(self, session_data):
  78. encoded_data = base64.decodestring(session_data)
  79. try:
  80. # could produce ValueError if there is no ':'
  81. hash, pickled = encoded_data.split(':', 1)
  82. expected_hash = self._hash(pickled)
  83. if not constant_time_compare(hash, expected_hash):
  84. raise SuspiciousOperation("Session data corrupted")
  85. else:
  86. return pickle.loads(pickled)
  87. except Exception:
  88. # ValueError, SuspiciousOperation, unpickling exceptions
  89. # Fall back to Django 1.2 method
  90. # PendingDeprecationWarning <- here to remind us to
  91. # remove this fallback in Django 1.5
  92. try:
  93. return self._decode_old(session_data)
  94. except Exception:
  95. # Unpickling can cause a variety of exceptions. If something happens,
  96. # just return an empty dictionary (an empty session).
  97. return {}
  98. def _decode_old(self, session_data):
  99. encoded_data = base64.decodestring(session_data)
  100. pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
  101. if not constant_time_compare(md5_constructor(pickled + settings.SECRET_KEY).hexdigest(),
  102. tamper_check):
  103. raise SuspiciousOperation("User tampered with session cookie.")
  104. return pickle.loads(pickled)
  105. def update(self, dict_):
  106. self._session.update(dict_)
  107. self.modified = True
  108. def has_key(self, key):
  109. return self._session.has_key(key)
  110. def values(self):
  111. return self._session.values()
  112. def iterkeys(self):
  113. return self._session.iterkeys()
  114. def itervalues(self):
  115. return self._session.itervalues()
  116. def iteritems(self):
  117. return self._session.iteritems()
  118. def clear(self):
  119. # To avoid unnecessary persistent storage accesses, we set up the
  120. # internals directly (loading data wastes time, since we are going to
  121. # set it to an empty dict anyway).
  122. self._session_cache = {}
  123. self.accessed = True
  124. self.modified = True
  125. def _get_new_session_key(self):
  126. "Returns session key that isn't being used."
  127. # The random module is seeded when this Apache child is created.
  128. # Use settings.SECRET_KEY as added salt.
  129. try:
  130. pid = os.getpid()
  131. except AttributeError:
  132. # No getpid() in Jython, for example
  133. pid = 1
  134. while 1:
  135. session_key = md5_constructor("%s%s%s%s"
  136. % (randrange(0, MAX_SESSION_KEY), pid, time.time(),
  137. settings.SECRET_KEY)).hexdigest()
  138. if not self.exists(session_key):
  139. break
  140. return session_key
  141. def _get_session_key(self):
  142. if self._session_key:
  143. return self._session_key
  144. else:
  145. self._session_key = self._get_new_session_key()
  146. return self._session_key
  147. def _set_session_key(self, session_key):
  148. self._session_key = session_key
  149. session_key = property(_get_session_key, _set_session_key)
  150. def _get_session(self, no_load=False):
  151. """
  152. Lazily loads session from storage (unless "no_load" is True, when only
  153. an empty dict is stored) and stores it in the current instance.
  154. """
  155. self.accessed = True
  156. try:
  157. return self._session_cache
  158. except AttributeError:
  159. if self._session_key is None or no_load:
  160. self._session_cache = {}
  161. else:
  162. self._session_cache = self.load()
  163. return self._session_cache
  164. _session = property(_get_session)
  165. def get_expiry_age(self):
  166. """Get the number of seconds until the session expires."""
  167. expiry = self.get('_session_expiry')
  168. if not expiry: # Checks both None and 0 cases
  169. return settings.SESSION_COOKIE_AGE
  170. if not isinstance(expiry, datetime):
  171. return expiry
  172. delta = expiry - datetime.now()
  173. return delta.days * 86400 + delta.seconds
  174. def get_expiry_date(self):
  175. """Get session the expiry date (as a datetime object)."""
  176. expiry = self.get('_session_expiry')
  177. if isinstance(expiry, datetime):
  178. return expiry
  179. if not expiry: # Checks both None and 0 cases
  180. expiry = settings.SESSION_COOKIE_AGE
  181. return datetime.now() + timedelta(seconds=expiry)
  182. def set_expiry(self, value):
  183. """
  184. Sets a custom expiration for the session. ``value`` can be an integer,
  185. a Python ``datetime`` or ``timedelta`` object or ``None``.
  186. If ``value`` is an integer, the session will expire after that many
  187. seconds of inactivity. If set to ``0`` then the session will expire on
  188. browser close.
  189. If ``value`` is a ``datetime`` or ``timedelta`` object, the session
  190. will expire at that specific future time.
  191. If ``value`` is ``None``, the session uses the global session expiry
  192. policy.
  193. """
  194. if value is None:
  195. # Remove any custom expiration for this session.
  196. try:
  197. del self['_session_expiry']
  198. except KeyError:
  199. pass
  200. return
  201. if isinstance(value, timedelta):
  202. value = datetime.now() + value
  203. self['_session_expiry'] = value
  204. def get_expire_at_browser_close(self):
  205. """
  206. Returns ``True`` if the session is set to expire when the browser
  207. closes, and ``False`` if there's an expiry date. Use
  208. ``get_expiry_date()`` or ``get_expiry_age()`` to find the actual expiry
  209. date/age, if there is one.
  210. """
  211. if self.get('_session_expiry') is None:
  212. return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
  213. return self.get('_session_expiry') == 0
  214. def flush(self):
  215. """
  216. Removes the current session data from the database and regenerates the
  217. key.
  218. """
  219. self.clear()
  220. self.delete()
  221. self.create()
  222. def cycle_key(self):
  223. """
  224. Creates a new session key, whilst retaining the current session data.
  225. """
  226. data = self._session_cache
  227. key = self.session_key
  228. self.create()
  229. self._session_cache = data
  230. self.delete(key)
  231. # Methods that child classes must implement.
  232. def exists(self, session_key):
  233. """
  234. Returns True if the given session_key already exists.
  235. """
  236. raise NotImplementedError
  237. def create(self):
  238. """
  239. Creates a new session instance. Guaranteed to create a new object with
  240. a unique key and will have saved the result once (with empty data)
  241. before the method returns.
  242. """
  243. raise NotImplementedError
  244. def save(self, must_create=False):
  245. """
  246. Saves the session data. If 'must_create' is True, a new session object
  247. is created (otherwise a CreateError exception is raised). Otherwise,
  248. save() can update an existing object with the same key.
  249. """
  250. raise NotImplementedError
  251. def delete(self, session_key=None):
  252. """
  253. Deletes the session data under this key. If the key is None, the
  254. current session key value is used.
  255. """
  256. raise NotImplementedError
  257. def load(self):
  258. """
  259. Loads the session data and returns a dictionary.
  260. """
  261. raise NotImplementedError