Browse Source

Implemented experimental JSON based sessions

pull/607/merge
Armin Ronacher 12 years ago
parent
commit
4df3bf2058
  1. 9
      CHANGES
  2. 7
      docs/api.rst
  3. 51
      docs/upgrading.rst
  4. 80
      flask/sessions.py
  5. 26
      flask/testsuite/basic.py

9
CHANGES

@ -3,6 +3,15 @@ Flask Changelog
Here you can see the full list of changes between each Flask release.
Version 0.10
------------
Release date to be decided.
- Changed default cookie serialization format from pickle to JSON to
limit the impact an attacker can do if the secret key leaks. See
:ref:`upgrading-to-010` for more information.
Version 0.9
-----------

7
docs/api.rst

@ -215,6 +215,13 @@ implementation that Flask is using.
.. autoclass:: SecureCookieSessionInterface
:members:
.. autoclass:: UpgradeSecureCookieSessionInterface
.. autoclass:: SecureCookieSession
:members:
.. autoclass:: UpgradeSecureCookieSession
.. autoclass:: NullSession
:members:

51
docs/upgrading.rst

@ -19,6 +19,57 @@ installation, make sure to pass it the ``-U`` parameter::
$ easy_install -U Flask
.. _upgrading-to-010:
Version 0.10
------------
The biggest change going from 0.9 to 0.10 is that the cookie serialization
format changed from pickle to a specialized JSON format. This change has
been done in order to avoid the damage an attacker can do if the secret
key is leaked. When you upgrade you will notice two major changes: all
sessions that were issued before the upgrade are invalidated and you can
only store a limited amount of types in the session. There are two ways
to avoid these problems on upgrading:
Automatically Upgrade Sessions
``````````````````````````````
The first method is to allow pickle based sessions for a limited amount of
time. This can be done by using the
:class:`~flask.sessions.UpgradeSecureCookieSession` session
implementation::
from flask import Flask
from flask.sessions import UpgradeSecureCookieSessionInterface
app = Flask(__name__)
app.session_interface = UpgradeSecureCookieSessionInterface
For as long as this class is being used both pickle and json sessions are
supported but changes are written in JSON format only.
Revert to Pickle Sessions
`````````````````````````
You can also revert to pickle based sessions if you want::
import pickle
from flask import Flask
from flask.sessions import SecureCookieSession, \
SecureCookieSessionInterface
class PickleSessionInterface(SecureCookieSessionInterface):
class session_class(SecureCookieSession):
serialization_method = pickle
app = Flask(__name__)
app.session_interface = PickleSessionInterface
If you want to continue to use pickle based data we strongly recommend
switching to a server side session store however.
Version 0.9
-----------

80
flask/sessions.py

@ -10,8 +10,12 @@
:license: BSD, see LICENSE for more details.
"""
import cPickle as pickle
from datetime import datetime
from werkzeug.contrib.securecookie import SecureCookie
from werkzeug.http import http_date, parse_date
from .helpers import json, _assert_have_json
from . import Markup
class SessionMixin(object):
@ -41,10 +45,74 @@ class SessionMixin(object):
modified = True
class TaggedJSONSerializer(object):
"""A customized JSON serializer that supports a few extra types that
we take for granted when serializing (tuples, markup objects, datetime).
"""
def dumps(self, value):
if __debug__:
_assert_have_json()
def _tag(value):
if isinstance(value, tuple):
return {'##t': [_tag(x) for x in value]}
elif callable(getattr(value, '__html__', None)):
return {'##m': unicode(value.__html__())}
elif isinstance(value, list):
return [_tag(x) for x in value]
elif isinstance(value, datetime):
return {'##d': http_date(value)}
elif isinstance(value, dict):
return dict((k, _tag(v)) for k, v in value.iteritems())
return value
return json.dumps(_tag(value), separators=(',', ':'))
def loads(self, value):
if __debug__:
_assert_have_json()
def object_hook(obj):
if len(obj) != 1:
return obj
the_key, the_value = obj.iteritems().next()
if the_key == '##t':
return tuple(the_value)
elif the_key == '##m':
return Markup(the_value)
elif the_key == '##d':
return parse_date(the_value)
return obj
return json.loads(value, object_hook=object_hook)
session_json_serializer = TaggedJSONSerializer()
class SecureCookieSession(SecureCookie, SessionMixin):
"""Expands the session with support for switching between permanent
and non-permanent sessions.
and non-permanent sessions and changes the default pickle based
serialization format to a tagged json one.
"""
serialization_method = session_json_serializer
class _UpgradeSerializer(object):
def dumps(self, value):
return session_json_serializer.dumps(value)
def loads(self, value):
try:
return session_json_serializer.loads(value)
except Exception:
return pickle.loads(value)
class UpgradeSecureCookieSession(SecureCookieSession):
"""This cookie sesion implementation tries json first but will also
support pickle based session. This exists mainly to upgrade existing
pickle based users transparently to json.
.. versionadded:: 0.10
"""
serialization_method = _UpgradeSerializer()
class NullSession(SecureCookieSession):
@ -203,3 +271,13 @@ class SecureCookieSessionInterface(SessionInterface):
session.save_cookie(response, app.session_cookie_name, path=path,
expires=expires, httponly=httponly,
secure=secure, domain=domain)
class UpgradeSecureCookieSessionInterface(SecureCookieSessionInterface):
"""This session interface works exactly like the regular one but uses
the :class:`UpgradeSecureCookieSession` classes to upgrade from pickle
sessions to JSON sessions.
.. versionadded:: 0.10
"""
session_class = UpgradeSecureCookieSession

26
flask/testsuite/basic.py

@ -13,6 +13,7 @@ from __future__ import with_statement
import re
import flask
import pickle
import unittest
from datetime import datetime
from threading import Thread
@ -297,6 +298,31 @@ class BasicFunctionalityTestCase(FlaskTestCase):
self.assert_equal(c.get('/').data, 'None')
self.assert_equal(c.get('/').data, '42')
def test_session_special_types(self):
app = flask.Flask(__name__)
app.secret_key = 'development-key'
app.testing = True
now = datetime.utcnow().replace(microsecond=0)
@app.after_request
def modify_session(response):
flask.session['m'] = flask.Markup('Hello!')
flask.session['dt'] = now
flask.session['t'] = (1, 2, 3)
return response
@app.route('/')
def dump_session_contents():
return pickle.dumps(dict(flask.session))
c = app.test_client()
c.get('/')
rv = pickle.loads(c.get('/').data)
self.assert_equal(rv['m'], flask.Markup('Hello!'))
self.assert_equal(type(rv['m']), flask.Markup)
self.assert_equal(rv['dt'], now)
self.assert_equal(rv['t'], (1, 2, 3))
def test_flashes(self):
app = flask.Flask(__name__)
app.secret_key = 'testkey'

Loading…
Cancel
Save