Implemented experimental JSON based sessions
This commit is contained in:
parent
d4415dd665
commit
4df3bf2058
5 changed files with 172 additions and 1 deletions
9
CHANGES
9
CHANGES
|
|
@ -3,6 +3,15 @@ Flask Changelog
|
||||||
|
|
||||||
Here you can see the full list of changes between each Flask release.
|
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
|
Version 0.9
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,13 @@ implementation that Flask is using.
|
||||||
.. autoclass:: SecureCookieSessionInterface
|
.. autoclass:: SecureCookieSessionInterface
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: UpgradeSecureCookieSessionInterface
|
||||||
|
|
||||||
|
.. autoclass:: SecureCookieSession
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: UpgradeSecureCookieSession
|
||||||
|
|
||||||
.. autoclass:: NullSession
|
.. autoclass:: NullSession
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,57 @@ installation, make sure to pass it the ``-U`` parameter::
|
||||||
|
|
||||||
$ easy_install -U Flask
|
$ 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
|
Version 0.9
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,12 @@
|
||||||
:license: BSD, see LICENSE for more details.
|
:license: BSD, see LICENSE for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import cPickle as pickle
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from werkzeug.contrib.securecookie import SecureCookie
|
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):
|
class SessionMixin(object):
|
||||||
|
|
@ -41,10 +45,74 @@ class SessionMixin(object):
|
||||||
modified = True
|
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):
|
class SecureCookieSession(SecureCookie, SessionMixin):
|
||||||
"""Expands the session with support for switching between permanent
|
"""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):
|
class NullSession(SecureCookieSession):
|
||||||
|
|
@ -203,3 +271,13 @@ class SecureCookieSessionInterface(SessionInterface):
|
||||||
session.save_cookie(response, app.session_cookie_name, path=path,
|
session.save_cookie(response, app.session_cookie_name, path=path,
|
||||||
expires=expires, httponly=httponly,
|
expires=expires, httponly=httponly,
|
||||||
secure=secure, domain=domain)
|
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
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ from __future__ import with_statement
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import flask
|
import flask
|
||||||
|
import pickle
|
||||||
import unittest
|
import unittest
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
@ -297,6 +298,31 @@ class BasicFunctionalityTestCase(FlaskTestCase):
|
||||||
self.assert_equal(c.get('/').data, 'None')
|
self.assert_equal(c.get('/').data, 'None')
|
||||||
self.assert_equal(c.get('/').data, '42')
|
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):
|
def test_flashes(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.secret_key = 'testkey'
|
app.secret_key = 'testkey'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue