Commit ff4a13ed authored by Vladislav Rykov's avatar Vladislav Rykov
Browse files

major changes

parent 51f76a47
Pipeline #38 failed with stages
import decimal
import hmac
import numbers
import sys
PY2 = sys.version_info[0] == 2
if PY2:
from itertools import izip
text_type = unicode # noqa: 821
else:
izip = zip
text_type = str
number_types = (numbers.Real, decimal.Decimal)
def _constant_time_compare(val1, val2):
"""Return ``True`` if the two strings are equal, ``False``
otherwise.
The time taken is independent of the number of characters that
match. Do not use this function for anything else than comparision
with known length targets.
This is should be implemented in C in order to get it completely
right.
This is an alias of :func:`hmac.compare_digest` on Python>=2.7,3.3.
"""
len_eq = len(val1) == len(val2)
if len_eq:
result = 0
left = val1
else:
result = 1
left = val2
for x, y in izip(bytearray(left), bytearray(val2)):
result |= x ^ y
return result == 0
# Starting with 2.7/3.3 the standard library has a c-implementation for
# constant time string compares.
constant_time_compare = getattr(hmac, "compare_digest", _constant_time_compare)
try:
import simplejson as json
except ImportError:
import json
class _CompactJSON(object):
"""Wrapper around json module that strips whitespace."""
@staticmethod
def loads(payload):
return json.loads(payload)
@staticmethod
def dumps(obj, **kwargs):
kwargs.setdefault("ensure_ascii", False)
kwargs.setdefault("separators", (",", ":"))
return json.dumps(obj, **kwargs)
import base64
import string
import struct
from ._compat import text_type
from .exc import BadData
def want_bytes(s, encoding="utf-8", errors="strict"):
if isinstance(s, text_type):
s = s.encode(encoding, errors)
return s
def base64_encode(string):
"""Base64 encode a string of bytes or text. The resulting bytes are
safe to use in URLs.
"""
string = want_bytes(string)
return base64.urlsafe_b64encode(string).rstrip(b"=")
def base64_decode(string):
"""Base64 decode a URL-safe string of bytes or text. The result is
bytes.
"""
string = want_bytes(string, encoding="ascii", errors="ignore")
string += b"=" * (-len(string) % 4)
try:
return base64.urlsafe_b64decode(string)
except (TypeError, ValueError):
raise BadData("Invalid base64-encoded data")
# The alphabet used by base64.urlsafe_*
_base64_alphabet = (string.ascii_letters + string.digits + "-_=").encode("ascii")
_int64_struct = struct.Struct(">Q")
_int_to_bytes = _int64_struct.pack
_bytes_to_int = _int64_struct.unpack
def int_to_bytes(num):
return _int_to_bytes(num).lstrip(b"\x00")
def bytes_to_int(bytestr):
return _bytes_to_int(bytestr.rjust(8, b"\x00"))[0]
from ._compat import PY2
from ._compat import text_type
class BadData(Exception):
"""Raised if bad data of any sort was encountered. This is the base
for all exceptions that itsdangerous defines.
.. versionadded:: 0.15
"""
message = None
def __init__(self, message):
super(BadData, self).__init__(self, message)
self.message = message
def __str__(self):
return text_type(self.message)
if PY2:
__unicode__ = __str__
def __str__(self):
return self.__unicode__().encode("utf-8")
class BadSignature(BadData):
"""Raised if a signature does not match."""
def __init__(self, message, payload=None):
BadData.__init__(self, message)
#: The payload that failed the signature test. In some
#: situations you might still want to inspect this, even if
#: you know it was tampered with.
#:
#: .. versionadded:: 0.14
self.payload = payload
class BadTimeSignature(BadSignature):
"""Raised if a time-based signature is invalid. This is a subclass
of :class:`BadSignature`.
"""
def __init__(self, message, payload=None, date_signed=None):
BadSignature.__init__(self, message, payload)
#: If the signature expired this exposes the date of when the
#: signature was created. This can be helpful in order to
#: tell the user how long a link has been gone stale.
#:
#: .. versionadded:: 0.14
self.date_signed = date_signed
class SignatureExpired(BadTimeSignature):
"""Raised if a signature timestamp is older than ``max_age``. This
is a subclass of :exc:`BadTimeSignature`.
"""
class BadHeader(BadSignature):
"""Raised if a signed header is invalid in some form. This only
happens for serializers that have a header that goes with the
signature.
.. versionadded:: 0.24
"""
def __init__(self, message, payload=None, header=None, original_error=None):
BadSignature.__init__(self, message, payload)
#: If the header is actually available but just malformed it
#: might be stored here.
self.header = header
#: If available, the error that indicates why the payload was
#: not valid. This might be ``None``.
self.original_error = original_error
class BadPayload(BadData):
"""Raised if a payload is invalid. This could happen if the payload
is loaded despite an invalid signature, or if there is a mismatch
between the serializer and deserializer. The original exception
that occurred during loading is stored on as :attr:`original_error`.
.. versionadded:: 0.15
"""
def __init__(self, message, original_error=None):
BadData.__init__(self, message)
#: If available, the error that indicates why the payload was
#: not valid. This might be ``None``.
self.original_error = original_error
import hashlib
import time
from datetime import datetime
from ._compat import number_types
from ._json import _CompactJSON
from ._json import json
from .encoding import base64_decode
from .encoding import base64_encode
from .encoding import want_bytes
from .exc import BadData
from .exc import BadHeader
from .exc import BadPayload
from .exc import BadSignature
from .exc import SignatureExpired
from .serializer import Serializer
from .signer import HMACAlgorithm
from .signer import NoneAlgorithm
class JSONWebSignatureSerializer(Serializer):
"""This serializer implements JSON Web Signature (JWS) support. Only
supports the JWS Compact Serialization.
"""
jws_algorithms = {
"HS256": HMACAlgorithm(hashlib.sha256),
"HS384": HMACAlgorithm(hashlib.sha384),
"HS512": HMACAlgorithm(hashlib.sha512),
"none": NoneAlgorithm(),
}
#: The default algorithm to use for signature generation
default_algorithm = "HS512"
default_serializer = _CompactJSON
def __init__(
self,
secret_key,
salt=None,
serializer=None,
serializer_kwargs=None,
signer=None,
signer_kwargs=None,
algorithm_name=None,
):
Serializer.__init__(
self,
secret_key=secret_key,
salt=salt,
serializer=serializer,
serializer_kwargs=serializer_kwargs,
signer=signer,
signer_kwargs=signer_kwargs,
)
if algorithm_name is None:
algorithm_name = self.default_algorithm
self.algorithm_name = algorithm_name
self.algorithm = self.make_algorithm(algorithm_name)
def load_payload(self, payload, serializer=None, return_header=False):
payload = want_bytes(payload)
if b"." not in payload:
raise BadPayload('No "." found in value')
base64d_header, base64d_payload = payload.split(b".", 1)
try:
json_header = base64_decode(base64d_header)
except Exception as e:
raise BadHeader(
"Could not base64 decode the header because of an exception",
original_error=e,
)
try:
json_payload = base64_decode(base64d_payload)
except Exception as e:
raise BadPayload(
"Could not base64 decode the payload because of an exception",
original_error=e,
)
try:
header = Serializer.load_payload(self, json_header, serializer=json)
except BadData as e:
raise BadHeader(
"Could not unserialize header because it was malformed",
original_error=e,
)
if not isinstance(header, dict):
raise BadHeader("Header payload is not a JSON object", header=header)
payload = Serializer.load_payload(self, json_payload, serializer=serializer)
if return_header:
return payload, header
return payload
def dump_payload(self, header, obj):
base64d_header = base64_encode(
self.serializer.dumps(header, **self.serializer_kwargs)
)
base64d_payload = base64_encode(
self.serializer.dumps(obj, **self.serializer_kwargs)
)
return base64d_header + b"." + base64d_payload
def make_algorithm(self, algorithm_name):
try:
return self.jws_algorithms[algorithm_name]
except KeyError:
raise NotImplementedError("Algorithm not supported")
def make_signer(self, salt=None, algorithm=None):
if salt is None:
salt = self.salt
key_derivation = "none" if salt is None else None
if algorithm is None:
algorithm = self.algorithm
return self.signer(
self.secret_key,
salt=salt,
sep=".",
key_derivation=key_derivation,
algorithm=algorithm,
)
def make_header(self, header_fields):
header = header_fields.copy() if header_fields else {}
header["alg"] = self.algorithm_name
return header
def dumps(self, obj, salt=None, header_fields=None):
"""Like :meth:`.Serializer.dumps` but creates a JSON Web
Signature. It also allows for specifying additional fields to be
included in the JWS header.
"""
header = self.make_header(header_fields)
signer = self.make_signer(salt, self.algorithm)
return signer.sign(self.dump_payload(header, obj))
def loads(self, s, salt=None, return_header=False):
"""Reverse of :meth:`dumps`. If requested via ``return_header``
it will return a tuple of payload and header.
"""
payload, header = self.load_payload(
self.make_signer(salt, self.algorithm).unsign(want_bytes(s)),
return_header=True,
)
if header.get("alg") != self.algorithm_name:
raise BadHeader("Algorithm mismatch", header=header, payload=payload)
if return_header:
return payload, header
return payload
def loads_unsafe(self, s, salt=None, return_header=False):
kwargs = {"return_header": return_header}
return self._loads_unsafe_impl(s, salt, kwargs, kwargs)
class TimedJSONWebSignatureSerializer(JSONWebSignatureSerializer):
"""Works like the regular :class:`JSONWebSignatureSerializer` but
also records the time of the signing and can be used to expire
signatures.
JWS currently does not specify this behavior but it mentions a
possible extension like this in the spec. Expiry date is encoded
into the header similar to what's specified in `draft-ietf-oauth
-json-web-token <http://self-issued.info/docs/draft-ietf-oauth-json
-web-token.html#expDef>`_.
"""
DEFAULT_EXPIRES_IN = 3600
def __init__(self, secret_key, expires_in=None, **kwargs):
JSONWebSignatureSerializer.__init__(self, secret_key, **kwargs)
if expires_in is None:
expires_in = self.DEFAULT_EXPIRES_IN
self.expires_in = expires_in
def make_header(self, header_fields):
header = JSONWebSignatureSerializer.make_header(self, header_fields)
iat = self.now()
exp = iat + self.expires_in
header["iat"] = iat
header["exp"] = exp
return header
def loads(self, s, salt=None, return_header=False):
payload, header = JSONWebSignatureSerializer.loads(
self, s, salt, return_header=True
)
if "exp" not in header:
raise BadSignature("Missing expiry date", payload=payload)
int_date_error = BadHeader("Expiry date is not an IntDate", payload=payload)
try:
header["exp"] = int(header["exp"])
except ValueError:
raise int_date_error
if header["exp"] < 0:
raise int_date_error
if header["exp"] < self.now():
raise SignatureExpired(
"Signature expired",
payload=payload,
date_signed=self.get_issue_date(header),
)
if return_header:
return payload, header
return payload
def get_issue_date(self, header):
rv = header.get("iat")
if isinstance(rv, number_types):
return datetime.utcfromtimestamp(int(rv))
def now(self):
return int(time.time())
import hashlib
from ._compat import text_type
from ._json import json
from .encoding import want_bytes
from .exc import BadPayload
from .exc import BadSignature
from .signer import Signer
def is_text_serializer(serializer):
"""Checks whether a serializer generates text or binary."""
return isinstance(serializer.dumps({}), text_type)
class Serializer(object):
"""This class provides a serialization interface on top of the
signer. It provides a similar API to json/pickle and other modules
but is structured differently internally. If you want to change the
underlying implementation for parsing and loading you have to
override the :meth:`load_payload` and :meth:`dump_payload`
functions.
This implementation uses simplejson if available for dumping and
loading and will fall back to the standard library's json module if
it's not available.
You do not need to subclass this class in order to switch out or
customize the :class:`.Signer`. You can instead pass a different
class to the constructor as well as keyword arguments as a dict that
should be forwarded.
.. code-block:: python
s = Serializer(signer_kwargs={'key_derivation': 'hmac'})
You may want to upgrade the signing parameters without invalidating
existing signatures that are in use. Fallback signatures can be
given that will be tried if unsigning with the current signer fails.
Fallback signers can be defined by providing a list of
``fallback_signers``. Each item can be one of the following: a
signer class (which is instantiated with ``signer_kwargs``,
``salt``, and ``secret_key``), a tuple
``(signer_class, signer_kwargs)``, or a dict of ``signer_kwargs``.
For example, this is a serializer that signs using SHA-512, but will
unsign using either SHA-512 or SHA1:
.. code-block:: python
s = Serializer(
signer_kwargs={"digest_method": hashlib.sha512},
fallback_signers=[{"digest_method": hashlib.sha1}]
)
.. versionchanged:: 0.14:
The ``signer`` and ``signer_kwargs`` parameters were added to
the constructor.
.. versionchanged:: 1.1.0:
Added support for ``fallback_signers`` and configured a default
SHA-512 fallback. This fallback is for users who used the yanked
1.0.0 release which defaulted to SHA-512.
"""
#: If a serializer module or class is not passed to the constructor
#: this one is picked up. This currently defaults to :mod:`json`.
default_serializer = json
#: The default :class:`Signer` class that is being used by this
#: serializer.
#:
#: .. versionadded:: 0.14
default_signer = Signer
#: The default fallback signers.
default_fallback_signers = [{"digest_method": hashlib.sha512}]
def __init__(
self,
secret_key,
salt=b"itsdangerous",
serializer=None,
serializer_kwargs=None,
signer=None,
signer_kwargs=None,
fallback_signers=None,
):
self.secret_key = want_bytes(secret_key)
self.salt = want_bytes(salt)
if serializer is None:
serializer = self.default_serializer
self.serializer = serializer
self.is_text_serializer = is_text_serializer(serializer)
if signer is None:
signer = self.default_signer
self.signer = signer
self.signer_kwargs = signer_kwargs or {}
if fallback_signers is None:
fallback_signers = list(self.default_fallback_signers or ())
self.fallback_signers = fallback_signers
self.serializer_kwargs = serializer_kwargs or {}
def load_payload(self, payload, serializer=None):
"""Loads the encoded object. This function raises
:class:`.BadPayload` if the payload is not valid. The
``serializer`` parameter can be used to override the serializer
stored on the class. The encoded ``payload`` should always be
bytes.
"""
if serializer is None:
serializer = self.serializer
is_text = self.is_text_serializer
else:
is_text = is_text_serializer(serializer)
try:
if is_text:
payload = payload.decode("utf-8")
return serializer.loads(payload)
except Exception as e:
raise BadPayload(
"Could not load the payload because an exception"
" occurred on unserializing the data.",
original_error=e,
)
def dump_payload(self, obj):
"""Dumps the encoded object. The return value is always bytes.
If the internal serializer returns text, the value will be
encoded as UTF-8.
"""
return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
def make_signer(self, salt=None):
"""Creates a new instance of the signer to be used. The default
implementation uses the :class:`.Signer` base class.
"""
if salt is None:
salt = self.salt
return self.signer(self.secret_key, salt=salt, **self.signer_kwargs)
def iter_unsigners(self, salt=None):
"""Iterates over all signers to be tried for unsigning. Starts
with the configured signer, then constructs each signer
specified in ``fallback_signers``.
"""
if salt is None:
salt = self.salt
yield self.make_signer(salt)
for fallback in self.fallback_signers:
if type(fallback) is dict:
kwargs = fallback
fallback = self.signer
elif type(fallback) is tuple:
fallback, kwargs = fallback
else:
kwargs = self.signer_kwargs
yield fallback(self.secret_key, salt=salt, **kwargs)
def dumps(self, obj, salt=None):
"""Returns a signed string serialized with the internal
serializer. The return value can be either a byte or unicode
string depending on the format of the internal serializer.
"""
payload = want_bytes(self.dump_payload(obj))
rv = self.make_signer(salt).sign(payload)
if self.is_text_serializer:
rv = rv.decode("utf-8")
return rv
def dump(self, obj, f, salt=None):
"""Like :meth:`dumps` but dumps into a file. The file handle has
to be compatible with what the internal serializer expects.
"""
f.write(self.dumps(obj, salt))
def loads(self, s, salt=None):
"""Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the
signature validation fails.
"""
s = want_bytes(s)
last_exception = None
for signer in self.iter_unsigners(salt):
try:
return self.load_payload(signer.unsign(s))
except BadSignature as err:
last_exception = err
raise last_exception
def load(self, f, salt=None):
"""Like :meth:`loads` but loads from a file."""
return self.loads(f.read(), salt)
def loads_unsafe(self, s, salt=None):
"""Like :meth:`loads` but without verifying the signature. This
is potentially very dangerous to use depending on how your
serializer works. The return value is ``(signature_valid,
payload)`` instead of just the payload. The first item will be a
boolean that indicates if the signature is valid. This function
never fails.
Use it for debugging only and if you know that your serializer
module is not exploitable (for example, do not use it with a
pickle serializer).
.. versionadded:: 0.15
"""
return self._loads_unsafe_impl(s, salt)
def _loads_unsafe_impl(self, s, salt, load_kwargs=None, load_payload_kwargs=None):
"""Low level helper function to implement :meth:`loads_unsafe`
in serializer subclasses.
"""
try:
return True, self.loads(s, salt=salt, **(load_kwargs or {}))
except BadSignature as e:
if e.payload is None:
return False, None
try:
return (
False,
self.load_payload(e.payload, **(load_payload_kwargs or {})),
)
except BadPayload:
return False, None
def load_unsafe(self, f, *args, **kwargs):
"""Like :meth:`loads_unsafe` but loads from a file.
.. versionadded:: 0.15
"""
return self.loads_unsafe(f.read(), *args, **kwargs)
import hashlib
import hmac
from ._compat import constant_time_compare
from .encoding import _base64_alphabet
from .encoding import base64_decode
from .encoding import base64_encode
from .encoding import want_bytes
from .exc import BadSignature
class SigningAlgorithm(object):
"""Subclasses must implement :meth:`get_signature` to provide
signature generation functionality.
"""
def get_signature(self, key, value):
"""Returns the signature for the given key and value."""
raise NotImplementedError()
def verify_signature(self, key, value, sig):
"""Verifies the given signature matches the expected
signature.
"""
return constant_time_compare(sig, self.get_signature(key, value))
class NoneAlgorithm(SigningAlgorithm):
"""Provides an algorithm that does not perform any signing and
returns an empty signature.
"""
def get_signature(self, key, value):
return b""
class HMACAlgorithm(SigningAlgorithm):
"""Provides signature generation using HMACs."""
#: The digest method to use with the MAC algorithm. This defaults to
#: SHA1, but can be changed to any other function in the hashlib
#: module.
default_digest_method = staticmethod(hashlib.sha1)
def __init__(self, digest_method=None):
if digest_method is None:
digest_method = self.default_digest_method
self.digest_method = digest_method
def get_signature(self, key, value):
mac = hmac.new(key, msg=value, digestmod=self.digest_method)
return mac.digest()
class Signer(object):
"""This class can sign and unsign bytes, validating the signature
provided.
Salt can be used to namespace the hash, so that a signed string is
only valid for a given namespace. Leaving this at the default value
or re-using a salt value across different parts of your application
where the same signed value in one part can mean something different
in another part is a security risk.
See :ref:`the-salt` for an example of what the salt is doing and how
you can utilize it.
.. versionadded:: 0.14
``key_derivation`` and ``digest_method`` were added as arguments
to the class constructor.
.. versionadded:: 0.18
``algorithm`` was added as an argument to the class constructor.
"""
#: The digest method to use for the signer. This defaults to
#: SHA1 but can be changed to any other function in the hashlib
#: module.
#:
#: .. versionadded:: 0.14
default_digest_method = staticmethod(hashlib.sha1)
#: Controls how the key is derived. The default is Django-style
#: concatenation. Possible values are ``concat``, ``django-concat``
#: and ``hmac``. This is used for deriving a key from the secret key
#: with an added salt.
#:
#: .. versionadded:: 0.14
default_key_derivation = "django-concat"
def __init__(
self,
secret_key,
salt=None,
sep=".",
key_derivation=None,
digest_method=None,
algorithm=None,
):
self.secret_key = want_bytes(secret_key)
self.sep = want_bytes(sep)
if self.sep in _base64_alphabet:
raise ValueError(
"The given separator cannot be used because it may be"
" contained in the signature itself. Alphanumeric"
" characters and `-_=` must not be used."
)
self.salt = "itsdangerous.Signer" if salt is None else salt
if key_derivation is None:
key_derivation = self.default_key_derivation
self.key_derivation = key_derivation
if digest_method is None:
digest_method = self.default_digest_method
self.digest_method = digest_method
if algorithm is None:
algorithm = HMACAlgorithm(self.digest_method)
self.algorithm = algorithm
def derive_key(self):
"""This method is called to derive the key. The default key
derivation choices can be overridden here. Key derivation is not
intended to be used as a security method to make a complex key
out of a short password. Instead you should use large random
secret keys.
"""
salt = want_bytes(self.salt)
if self.key_derivation == "concat":
return self.digest_method(salt + self.secret_key).digest()
elif self.key_derivation == "django-concat":
return self.digest_method(salt + b"signer" + self.secret_key).digest()
elif self.key_derivation == "hmac":
mac = hmac.new(self.secret_key, digestmod=self.digest_method)
mac.update(salt)
return mac.digest()
elif self.key_derivation == "none":
return self.secret_key
else:
raise TypeError("Unknown key derivation method")
def get_signature(self, value):
"""Returns the signature for the given value."""
value = want_bytes(value)
key = self.derive_key()
sig = self.algorithm.get_signature(key, value)
return base64_encode(sig)
def sign(self, value):
"""Signs the given string."""
return want_bytes(value) + want_bytes(self.sep) + self.get_signature(value)
def verify_signature(self, value, sig):
"""Verifies the signature for the given value."""
key = self.derive_key()
try:
sig = base64_decode(sig)
except Exception:
return False
return self.algorithm.verify_signature(key, value, sig)
def unsign(self, signed_value):
"""Unsigns the given string."""
signed_value = want_bytes(signed_value)
sep = want_bytes(self.sep)
if sep not in signed_value:
raise BadSignature("No %r found in value" % self.sep)
value, sig = signed_value.rsplit(sep, 1)
if self.verify_signature(value, sig):
return value
raise BadSignature("Signature %r does not match" % sig, payload=value)
def validate(self, signed_value):
"""Only validates the given signed value. Returns ``True`` if
the signature exists and is valid.
"""
try:
self.unsign(signed_value)
return True
except BadSignature:
return False
import time
from datetime import datetime
from ._compat import text_type
from .encoding import base64_decode
from .encoding import base64_encode
from .encoding import bytes_to_int
from .encoding import int_to_bytes
from .encoding import want_bytes
from .exc import BadSignature
from .exc import BadTimeSignature
from .exc import SignatureExpired
from .serializer import Serializer
from .signer import Signer
class TimestampSigner(Signer):
"""Works like the regular :class:`.Signer` but also records the time
of the signing and can be used to expire signatures. The
:meth:`unsign` method can raise :exc:`.SignatureExpired` if the
unsigning failed because the signature is expired.
"""
def get_timestamp(self):
"""Returns the current timestamp. The function must return an
integer.
"""
return int(time.time())
def timestamp_to_datetime(self, ts):
"""Used to convert the timestamp from :meth:`get_timestamp` into
a datetime object.
"""
return datetime.utcfromtimestamp(ts)
def sign(self, value):
"""Signs the given string and also attaches time information."""
value = want_bytes(value)
timestamp = base64_encode(int_to_bytes(self.get_timestamp()))
sep = want_bytes(self.sep)
value = value + sep + timestamp
return value + sep + self.get_signature(value)
def unsign(self, value, max_age=None, return_timestamp=False):
"""Works like the regular :meth:`.Signer.unsign` but can also
validate the time. See the base docstring of the class for
the general behavior. If ``return_timestamp`` is ``True`` the
timestamp of the signature will be returned as a naive
:class:`datetime.datetime` object in UTC.
"""
try:
result = Signer.unsign(self, value)
sig_error = None
except BadSignature as e:
sig_error = e
result = e.payload or b""
sep = want_bytes(self.sep)
# If there is no timestamp in the result there is something
# seriously wrong. In case there was a signature error, we raise
# that one directly, otherwise we have a weird situation in
# which we shouldn't have come except someone uses a time-based
# serializer on non-timestamp data, so catch that.
if sep not in result:
if sig_error:
raise sig_error
raise BadTimeSignature("timestamp missing", payload=result)
value, timestamp = result.rsplit(sep, 1)
try:
timestamp = bytes_to_int(base64_decode(timestamp))
except Exception:
timestamp = None
# Signature is *not* okay. Raise a proper error now that we have
# split the value and the timestamp.
if sig_error is not None:
raise BadTimeSignature(
text_type(sig_error), payload=value, date_signed=timestamp
)
# Signature was okay but the timestamp is actually not there or
# malformed. Should not happen, but we handle it anyway.
if timestamp is None:
raise BadTimeSignature("Malformed timestamp", payload=value)
# Check timestamp is not older than max_age
if max_age is not None:
age = self.get_timestamp() - timestamp
if age > max_age:
raise SignatureExpired(
"Signature age %s > %s seconds" % (age, max_age),
payload=value,
date_signed=self.timestamp_to_datetime(timestamp),
)
if return_timestamp:
return value, self.timestamp_to_datetime(timestamp)
return value
def validate(self, signed_value, max_age=None):
"""Only validates the given signed value. Returns ``True`` if
the signature exists and is valid."""
try:
self.unsign(signed_value, max_age=max_age)
return True
except BadSignature:
return False
class TimedSerializer(Serializer):
"""Uses :class:`TimestampSigner` instead of the default
:class:`.Signer`.
"""
default_signer = TimestampSigner
def loads(self, s, max_age=None, return_timestamp=False, salt=None):
"""Reverse of :meth:`dumps`, raises :exc:`.BadSignature` if the
signature validation fails. If a ``max_age`` is provided it will
ensure the signature is not older than that time in seconds. In
case the signature is outdated, :exc:`.SignatureExpired` is
raised. All arguments are forwarded to the signer's
:meth:`~TimestampSigner.unsign` method.
"""
s = want_bytes(s)
last_exception = None
for signer in self.iter_unsigners(salt):
try:
base64d, timestamp = signer.unsign(s, max_age, return_timestamp=True)
payload = self.load_payload(base64d)
if return_timestamp:
return payload, timestamp
return payload
# If we get a signature expired it means we could read the
# signature but it's invalid. In that case we do not want to
# try the next signer.
except SignatureExpired:
raise
except BadSignature as err:
last_exception = err
raise last_exception
def loads_unsafe(self, s, max_age=None, salt=None):
load_kwargs = {"max_age": max_age}
load_payload_kwargs = {}
return self._loads_unsafe_impl(s, salt, load_kwargs, load_payload_kwargs)
import zlib
from ._json import _CompactJSON
from .encoding import base64_decode
from .encoding import base64_encode
from .exc import BadPayload
from .serializer import Serializer
from .timed import TimedSerializer
class URLSafeSerializerMixin(object):
"""Mixed in with a regular serializer it will attempt to zlib
compress the string to make it shorter if necessary. It will also
base64 encode the string so that it can safely be placed in a URL.
"""
default_serializer = _CompactJSON
def load_payload(self, payload, *args, **kwargs):
decompress = False
if payload.startswith(b"."):
payload = payload[1:]
decompress = True
try:
json = base64_decode(payload)
except Exception as e:
raise BadPayload(
"Could not base64 decode the payload because of an exception",
original_error=e,
)
if decompress:
try:
json = zlib.decompress(json)
except Exception as e:
raise BadPayload(
"Could not zlib decompress the payload before decoding the payload",
original_error=e,
)
return super(URLSafeSerializerMixin, self).load_payload(json, *args, **kwargs)
def dump_payload(self, obj):
json = super(URLSafeSerializerMixin, self).dump_payload(obj)
is_compressed = False
compressed = zlib.compress(json)
if len(compressed) < (len(json) - 1):
json = compressed
is_compressed = True
base64d = base64_encode(json)
if is_compressed:
base64d = b"." + base64d
return base64d
class URLSafeSerializer(URLSafeSerializerMixin, Serializer):
"""Works like :class:`.Serializer` but dumps and loads into a URL
safe string consisting of the upper and lowercase character of the
alphabet as well as ``'_'``, ``'-'`` and ``'.'``.
"""
class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer):
"""Works like :class:`.TimedSerializer` but dumps and loads into a
URL safe string consisting of the upper and lowercase character of
the alphabet as well as ``'_'``, ``'-'`` and ``'.'``.
"""
# -*- coding: utf-8 -*-
"""Jinja is a template engine written in pure Python. It provides a
non-XML syntax that supports inline expressions and an optional
sandboxed environment.
"""
from markupsafe import escape
from markupsafe import Markup
from .bccache import BytecodeCache
from .bccache import FileSystemBytecodeCache
from .bccache import MemcachedBytecodeCache
from .environment import Environment
from .environment import Template
from .exceptions import TemplateAssertionError
from .exceptions import TemplateError
from .exceptions import TemplateNotFound
from .exceptions import TemplateRuntimeError
from .exceptions import TemplatesNotFound
from .exceptions import TemplateSyntaxError
from .exceptions import UndefinedError
from .filters import contextfilter
from .filters import environmentfilter
from .filters import evalcontextfilter
from .loaders import BaseLoader
from .loaders import ChoiceLoader
from .loaders import DictLoader
from .loaders import FileSystemLoader
from .loaders import FunctionLoader
from .loaders import ModuleLoader
from .loaders import PackageLoader
from .loaders import PrefixLoader
from .runtime import ChainableUndefined
from .runtime import DebugUndefined
from .runtime import make_logging_undefined
from .runtime import StrictUndefined
from .runtime import Undefined
from .utils import clear_caches
from .utils import contextfunction
from .utils import environmentfunction
from .utils import evalcontextfunction
from .utils import is_undefined
from .utils import select_autoescape
__version__ = "2.11.2"
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment