forked from orbit-oss/flask
make session serializer extensible
support serializing 1-item dicts with tag as key refactor serializer into flask.json.tag module continues #1452, closes #1438, closes #1908
This commit is contained in:
parent
ea2e9609bc
commit
5e1ced3c05
4 changed files with 201 additions and 140 deletions
|
|
@ -9,18 +9,14 @@
|
|||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import hashlib
|
||||
import uuid
|
||||
import warnings
|
||||
from base64 import b64decode, b64encode
|
||||
from datetime import datetime
|
||||
|
||||
from itsdangerous import BadSignature, URLSafeTimedSerializer
|
||||
from werkzeug.datastructures import CallbackDict
|
||||
from werkzeug.http import http_date, parse_date
|
||||
|
||||
from . import Markup, json
|
||||
from ._compat import iteritems, text_type
|
||||
from .helpers import is_ip, total_seconds
|
||||
from flask.helpers import is_ip, total_seconds
|
||||
from flask.json.tag import TaggedJSONSerializer
|
||||
|
||||
|
||||
class SessionMixin(object):
|
||||
|
|
@ -57,126 +53,6 @@ class SessionMixin(object):
|
|||
#: from being served the same cache.
|
||||
accessed = 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 __init__(self):
|
||||
self.conversions = [
|
||||
{
|
||||
'check': lambda value: self._is_dict_with_used_key(value),
|
||||
'tag': lambda value: self._tag_dict_used_with_key(value),
|
||||
'untag': lambda value: self._untag_dict_used_with_key(value),
|
||||
'key': ' di',
|
||||
},
|
||||
{
|
||||
'check': lambda value: isinstance(value, tuple),
|
||||
'tag': lambda value: [self._tag(x) for x in value],
|
||||
'untag': lambda value: tuple(value),
|
||||
'key': ' t',
|
||||
},
|
||||
{
|
||||
'check': lambda value: isinstance(value, uuid.UUID),
|
||||
'tag': lambda value: value.hex,
|
||||
'untag': lambda value: uuid.UUID(value),
|
||||
'key': ' u',
|
||||
},
|
||||
{
|
||||
'check': lambda value: isinstance(value, bytes),
|
||||
'tag': lambda value: b64encode(value).decode('ascii'),
|
||||
'untag': lambda value: b64decode(value),
|
||||
'key': ' b',
|
||||
},
|
||||
{
|
||||
'check': lambda value: callable(getattr(value, '__html__',
|
||||
None)),
|
||||
'tag': lambda value: text_type(value.__html__()),
|
||||
'untag': lambda value: Markup(value),
|
||||
'key': ' m',
|
||||
},
|
||||
{
|
||||
'check': lambda value: isinstance(value, list),
|
||||
'tag': lambda value: [self._tag(x) for x in value],
|
||||
},
|
||||
{
|
||||
'check': lambda value: isinstance(value, datetime),
|
||||
'tag': lambda value: http_date(value),
|
||||
'untag': lambda value: parse_date(value),
|
||||
'key': ' d',
|
||||
},
|
||||
{
|
||||
'check': lambda value: isinstance(value, dict),
|
||||
'tag': lambda value: dict((k, self._tag(v)) for k, v in
|
||||
iteritems(value)),
|
||||
},
|
||||
{
|
||||
'check': lambda value: isinstance(value, str),
|
||||
'tag': lambda value: self._tag_string(value),
|
||||
}
|
||||
]
|
||||
|
||||
@property
|
||||
def keys(self):
|
||||
return [c['key'] for c in self.conversions if c.get('key')]
|
||||
|
||||
def _get_conversion_untag(self, key):
|
||||
return next(
|
||||
(c['untag'] for c in self.conversions if c.get('key') == key),
|
||||
lambda v: None
|
||||
)
|
||||
|
||||
def _is_dict_with_used_key(self, v):
|
||||
return isinstance(v, dict) and len(v) == 1 and list(v)[0] in self.keys
|
||||
|
||||
def _was_dict_with_used_key(self, k):
|
||||
return k.endswith('__') and k[:-2] in self.keys
|
||||
|
||||
def _tag_string(self, value):
|
||||
try:
|
||||
return text_type(value)
|
||||
except UnicodeError:
|
||||
from flask.debughelpers import UnexpectedUnicodeError
|
||||
raise UnexpectedUnicodeError(u'A byte string with '
|
||||
u'non-ASCII data was passed to the session system '
|
||||
u'which can only store unicode strings. Consider '
|
||||
u'base64 encoding your string (String was %r)' % value)
|
||||
|
||||
def _tag_dict_used_with_key(self, value):
|
||||
k, v = next(iteritems(value))
|
||||
return {'%s__' % k: v}
|
||||
|
||||
def _tag(self, value):
|
||||
for tag_ops in self.conversions:
|
||||
if tag_ops['check'](value):
|
||||
tag = tag_ops.get('key')
|
||||
if tag:
|
||||
return {tag: tag_ops['tag'](value)}
|
||||
return tag_ops['tag'](value)
|
||||
return value
|
||||
|
||||
def _untag_dict_used_with_key(self, the_value):
|
||||
k, v = next(iteritems(the_value))
|
||||
if self._was_dict_with_used_key(k):
|
||||
return {k[:-2]: self._untag(v)}
|
||||
|
||||
def _untag(self, obj):
|
||||
if len(obj) != 1:
|
||||
return obj
|
||||
the_key, the_value = next(iteritems(obj))
|
||||
untag = self._get_conversion_untag(the_key)
|
||||
new_value = untag(the_value)
|
||||
return new_value if new_value else obj
|
||||
|
||||
def dumps(self, value):
|
||||
return json.dumps(self._tag(value), separators=(',', ':'))
|
||||
|
||||
def loads(self, value):
|
||||
return json.loads(value, object_hook=self._untag)
|
||||
|
||||
|
||||
session_json_serializer = TaggedJSONSerializer()
|
||||
|
||||
|
||||
class SecureCookieSession(CallbackDict, SessionMixin):
|
||||
"""Base class for sessions based on signed cookies."""
|
||||
|
|
@ -284,10 +160,10 @@ class SessionInterface(object):
|
|||
|
||||
def get_cookie_domain(self, app):
|
||||
"""Returns the domain that should be set for the session cookie.
|
||||
|
||||
|
||||
Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
|
||||
falls back to detecting the domain based on ``SERVER_NAME``.
|
||||
|
||||
|
||||
Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is
|
||||
updated to avoid re-running the logic.
|
||||
"""
|
||||
|
|
@ -377,7 +253,7 @@ class SessionInterface(object):
|
|||
has been modified, the cookie is set. If the session is permanent and
|
||||
the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is
|
||||
always set.
|
||||
|
||||
|
||||
This check is usually skipped if the session was deleted.
|
||||
|
||||
.. versionadded:: 0.11
|
||||
|
|
@ -404,6 +280,9 @@ class SessionInterface(object):
|
|||
raise NotImplementedError()
|
||||
|
||||
|
||||
session_json_serializer = TaggedJSONSerializer()
|
||||
|
||||
|
||||
class SecureCookieSessionInterface(SessionInterface):
|
||||
"""The default session interface that stores sessions in signed cookies
|
||||
through the :mod:`itsdangerous` module.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue