diff options
Diffstat (limited to 'python/werkzeug/contrib/securecookie.py')
-rw-r--r-- | python/werkzeug/contrib/securecookie.py | 362 |
1 files changed, 0 insertions, 362 deletions
diff --git a/python/werkzeug/contrib/securecookie.py b/python/werkzeug/contrib/securecookie.py deleted file mode 100644 index c4c9eee..0000000 --- a/python/werkzeug/contrib/securecookie.py +++ /dev/null @@ -1,362 +0,0 @@ -# -*- coding: utf-8 -*- -r""" - werkzeug.contrib.securecookie - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module implements a cookie that is not alterable from the client - because it adds a checksum the server checks for. You can use it as - session replacement if all you have is a user id or something to mark - a logged in user. - - Keep in mind that the data is still readable from the client as a - normal cookie is. However you don't have to store and flush the - sessions you have at the server. - - Example usage: - - >>> from werkzeug.contrib.securecookie import SecureCookie - >>> x = SecureCookie({"foo": 42, "baz": (1, 2, 3)}, "deadbeef") - - Dumping into a string so that one can store it in a cookie: - - >>> value = x.serialize() - - Loading from that string again: - - >>> x = SecureCookie.unserialize(value, "deadbeef") - >>> x["baz"] - (1, 2, 3) - - If someone modifies the cookie and the checksum is wrong the unserialize - method will fail silently and return a new empty `SecureCookie` object. - - Keep in mind that the values will be visible in the cookie so do not - store data in a cookie you don't want the user to see. - - Application Integration - ======================= - - If you are using the werkzeug request objects you could integrate the - secure cookie into your application like this:: - - from werkzeug.utils import cached_property - from werkzeug.wrappers import BaseRequest - from werkzeug.contrib.securecookie import SecureCookie - - # don't use this key but a different one; you could just use - # os.urandom(20) to get something random - SECRET_KEY = '\xfa\xdd\xb8z\xae\xe0}4\x8b\xea' - - class Request(BaseRequest): - - @cached_property - def client_session(self): - data = self.cookies.get('session_data') - if not data: - return SecureCookie(secret_key=SECRET_KEY) - return SecureCookie.unserialize(data, SECRET_KEY) - - def application(environ, start_response): - request = Request(environ) - - # get a response object here - response = ... - - if request.client_session.should_save: - session_data = request.client_session.serialize() - response.set_cookie('session_data', session_data, - httponly=True) - return response(environ, start_response) - - A less verbose integration can be achieved by using shorthand methods:: - - class Request(BaseRequest): - - @cached_property - def client_session(self): - return SecureCookie.load_cookie(self, secret_key=COOKIE_SECRET) - - def application(environ, start_response): - request = Request(environ) - - # get a response object here - response = ... - - request.client_session.save_cookie(response) - return response(environ, start_response) - - :copyright: 2007 Pallets - :license: BSD-3-Clause -""" -import base64 -import pickle -import warnings -from hashlib import sha1 as _default_hash -from hmac import new as hmac -from time import time - -from .._compat import iteritems -from .._compat import text_type -from .._compat import to_bytes -from .._compat import to_native -from .._internal import _date_to_unix -from ..contrib.sessions import ModificationTrackingDict -from ..security import safe_str_cmp -from ..urls import url_quote_plus -from ..urls import url_unquote_plus - -warnings.warn( - "'werkzeug.contrib.securecookie' is deprecated as of version 0.15" - " and will be removed in version 1.0. It has moved to" - " https://github.com/pallets/secure-cookie.", - DeprecationWarning, - stacklevel=2, -) - - -class UnquoteError(Exception): - """Internal exception used to signal failures on quoting.""" - - -class SecureCookie(ModificationTrackingDict): - """Represents a secure cookie. You can subclass this class and provide - an alternative mac method. The import thing is that the mac method - is a function with a similar interface to the hashlib. Required - methods are update() and digest(). - - Example usage: - - >>> x = SecureCookie({"foo": 42, "baz": (1, 2, 3)}, "deadbeef") - >>> x["foo"] - 42 - >>> x["baz"] - (1, 2, 3) - >>> x["blafasel"] = 23 - >>> x.should_save - True - - :param data: the initial data. Either a dict, list of tuples or `None`. - :param secret_key: the secret key. If not set `None` or not specified - it has to be set before :meth:`serialize` is called. - :param new: The initial value of the `new` flag. - """ - - #: The hash method to use. This has to be a module with a new function - #: or a function that creates a hashlib object. Such as `hashlib.md5` - #: Subclasses can override this attribute. The default hash is sha1. - #: Make sure to wrap this in staticmethod() if you store an arbitrary - #: function there such as hashlib.sha1 which might be implemented - #: as a function. - hash_method = staticmethod(_default_hash) - - #: The module used for serialization. Should have a ``dumps`` and a - #: ``loads`` method that takes bytes. The default is :mod:`pickle`. - #: - #: .. versionchanged:: 0.15 - #: The default of ``pickle`` will change to :mod:`json` in 1.0. - serialization_method = pickle - - #: if the contents should be base64 quoted. This can be disabled if the - #: serialization process returns cookie safe strings only. - quote_base64 = True - - def __init__(self, data=None, secret_key=None, new=True): - ModificationTrackingDict.__init__(self, data or ()) - # explicitly convert it into a bytestring because python 2.6 - # no longer performs an implicit string conversion on hmac - if secret_key is not None: - secret_key = to_bytes(secret_key, "utf-8") - self.secret_key = secret_key - self.new = new - - if self.serialization_method is pickle: - warnings.warn( - "The default 'SecureCookie.serialization_method' will" - " change from pickle to json in version 1.0. To upgrade" - " existing tokens, override 'unquote' to try pickle if" - " json fails.", - stacklevel=2, - ) - - def __repr__(self): - return "<%s %s%s>" % ( - self.__class__.__name__, - dict.__repr__(self), - "*" if self.should_save else "", - ) - - @property - def should_save(self): - """True if the session should be saved. By default this is only true - for :attr:`modified` cookies, not :attr:`new`. - """ - return self.modified - - @classmethod - def quote(cls, value): - """Quote the value for the cookie. This can be any object supported - by :attr:`serialization_method`. - - :param value: the value to quote. - """ - if cls.serialization_method is not None: - value = cls.serialization_method.dumps(value) - if cls.quote_base64: - value = b"".join( - base64.b64encode(to_bytes(value, "utf8")).splitlines() - ).strip() - return value - - @classmethod - def unquote(cls, value): - """Unquote the value for the cookie. If unquoting does not work a - :exc:`UnquoteError` is raised. - - :param value: the value to unquote. - """ - try: - if cls.quote_base64: - value = base64.b64decode(value) - if cls.serialization_method is not None: - value = cls.serialization_method.loads(value) - return value - except Exception: - # unfortunately pickle and other serialization modules can - # cause pretty every error here. if we get one we catch it - # and convert it into an UnquoteError - raise UnquoteError() - - def serialize(self, expires=None): - """Serialize the secure cookie into a string. - - If expires is provided, the session will be automatically invalidated - after expiration when you unseralize it. This provides better - protection against session cookie theft. - - :param expires: an optional expiration date for the cookie (a - :class:`datetime.datetime` object) - """ - if self.secret_key is None: - raise RuntimeError("no secret key defined") - if expires: - self["_expires"] = _date_to_unix(expires) - result = [] - mac = hmac(self.secret_key, None, self.hash_method) - for key, value in sorted(self.items()): - result.append( - ( - "%s=%s" % (url_quote_plus(key), self.quote(value).decode("ascii")) - ).encode("ascii") - ) - mac.update(b"|" + result[-1]) - return b"?".join([base64.b64encode(mac.digest()).strip(), b"&".join(result)]) - - @classmethod - def unserialize(cls, string, secret_key): - """Load the secure cookie from a serialized string. - - :param string: the cookie value to unserialize. - :param secret_key: the secret key used to serialize the cookie. - :return: a new :class:`SecureCookie`. - """ - if isinstance(string, text_type): - string = string.encode("utf-8", "replace") - if isinstance(secret_key, text_type): - secret_key = secret_key.encode("utf-8", "replace") - try: - base64_hash, data = string.split(b"?", 1) - except (ValueError, IndexError): - items = () - else: - items = {} - mac = hmac(secret_key, None, cls.hash_method) - for item in data.split(b"&"): - mac.update(b"|" + item) - if b"=" not in item: - items = None - break - key, value = item.split(b"=", 1) - # try to make the key a string - key = url_unquote_plus(key.decode("ascii")) - try: - key = to_native(key) - except UnicodeError: - pass - items[key] = value - - # no parsing error and the mac looks okay, we can now - # sercurely unpickle our cookie. - try: - client_hash = base64.b64decode(base64_hash) - except TypeError: - items = client_hash = None - if items is not None and safe_str_cmp(client_hash, mac.digest()): - try: - for key, value in iteritems(items): - items[key] = cls.unquote(value) - except UnquoteError: - items = () - else: - if "_expires" in items: - if time() > items["_expires"]: - items = () - else: - del items["_expires"] - else: - items = () - return cls(items, secret_key, False) - - @classmethod - def load_cookie(cls, request, key="session", secret_key=None): - """Loads a :class:`SecureCookie` from a cookie in request. If the - cookie is not set, a new :class:`SecureCookie` instanced is - returned. - - :param request: a request object that has a `cookies` attribute - which is a dict of all cookie values. - :param key: the name of the cookie. - :param secret_key: the secret key used to unquote the cookie. - Always provide the value even though it has - no default! - """ - data = request.cookies.get(key) - if not data: - return cls(secret_key=secret_key) - return cls.unserialize(data, secret_key) - - def save_cookie( - self, - response, - key="session", - expires=None, - session_expires=None, - max_age=None, - path="/", - domain=None, - secure=None, - httponly=False, - force=False, - ): - """Saves the SecureCookie in a cookie on response object. All - parameters that are not described here are forwarded directly - to :meth:`~BaseResponse.set_cookie`. - - :param response: a response object that has a - :meth:`~BaseResponse.set_cookie` method. - :param key: the name of the cookie. - :param session_expires: the expiration date of the secure cookie - stored information. If this is not provided - the cookie `expires` date is used instead. - """ - if force or self.should_save: - data = self.serialize(session_expires or expires) - response.set_cookie( - key, - data, - expires=expires, - max_age=max_age, - path=path, - domain=domain, - secure=secure, - httponly=httponly, - ) |