diff options
Diffstat (limited to 'itsdangerous.py')
-rw-r--r-- | itsdangerous.py | 148 |
1 files changed, 74 insertions, 74 deletions
diff --git a/itsdangerous.py b/itsdangerous.py index 909af12..5d36e86 100644 --- a/itsdangerous.py +++ b/itsdangerous.py @@ -32,27 +32,24 @@ else: int_to_byte = operator.methodcaller('to_bytes', 1, 'big') -# On Python 3 the builtin JSON module does not match the -# behavior of the JSON module from simplejson. We need to -# wrap it and encode to utf-8 the return value. This behavior -# does not match the behavior of the standalone simplejson -# module. If we find simplejson installed we just use it -# unchanged. try: - import simplejson + import simplejson as json except ImportError: - import json as simplejson - if not PY2: - _json = simplejson - class simplejson(object): + import json - @staticmethod - def dumps(obj, *args, **kwargs): - return _json.dumps(obj, *args, **kwargs).encode('utf-8') - @staticmethod - def loads(obj, *args, **kwargs): - return _json.loads(obj.decode('utf-8'), *args, **kwargs) +class _CompactJSON(object): + """Wrapper around simplejson that strips whitespace. + """ + + def loads(self, payload): + return json.loads(payload) + + def dumps(self, obj): + return json.dumps(obj, separators=(',', ':')) + + +compact_json = _CompactJSON() # 2011/01/01 in UTC @@ -65,6 +62,11 @@ def want_bytes(s, encoding='utf-8', errors='strict'): return s +def is_text_serializer(serializer): + """Checks weather a serializer generates text or binary.""" + return isinstance(serializer.dumps({}), text_type) + + def constant_time_compare(val1, val2): """Returns True if the two strings are equal, False otherwise. @@ -230,8 +232,8 @@ class HMACAlgorithm(SigningAlgorithm): class Signer(object): - """This class can sign a string and unsign it and validate the - signature provided. + """This class can sign bytes and unsign it and validate 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 @@ -268,8 +270,7 @@ class Signer(object): digest_method=None, algorithm=None): self.secret_key = want_bytes(secret_key) self.sep = sep - self.salt = b'itsdangerous.Signer' if salt is None \ - else want_bytes(salt) + 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 @@ -287,14 +288,15 @@ class Signer(object): 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(self.salt + self.secret_key).digest() + return self.digest_method(salt + self.secret_key).digest() elif self.key_derivation == 'django-concat': - return self.digest_method(self.salt + b'signer' + + 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(self.salt) + mac.update(salt) return mac.digest() elif self.key_derivation == 'none': return self.secret_key @@ -321,7 +323,7 @@ class Signer(object): value, sig = signed_value.rsplit(sep, 1) if constant_time_compare(sig, self.get_signature(value)): return value - raise BadSignature('Signature "%s" does not match' % sig, + raise BadSignature('Signature %r does not match' % sig, payload=value) def validate(self, signed_value): @@ -429,18 +431,19 @@ class TimestampSigner(Signer): class Serializer(object): """This class provides a serialization interface on top of the - signer. It provides a similar API to json/pickle/simplejson and - other modules but is slightly differently structured 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. + signer. It provides a similar API to json/pickle and other modules but is + slightly differently structured 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 for dumping and loading. + 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. - Starting with 0.14 you do not need to subclass this class in order - to switch out or customer the :class:`Signer`. You can instead - also pass a different class to the constructor as well as - keyword arguments as dictionary that should be forwarded:: + Starting with 0.14 you do not need to subclass this class in order to + switch out or customer the :class:`Signer`. You can instead also pass a + different class to the constructor as well as keyword arguments as + dictionary that should be forwarded:: s = Serializer(signer_kwargs={'key_derivation': 'hmac'}) @@ -451,7 +454,7 @@ class Serializer(object): #: 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 = simplejson + default_serializer = json #: The default :class:`Signer` class that is being used by this #: serializer. @@ -466,21 +469,26 @@ class Serializer(object): 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 {} def load_payload(self, payload, serializer=None): - """Loads the encoded object. This implementation uses simplejson. - 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. + """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 is + always byte based. """ if serializer is None: serializer = self.serializer + is_text = self.is_text_serializer + else: + is_text = is_text_serializer(serializer) try: - payload = want_bytes(payload) + 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 ' @@ -488,8 +496,9 @@ class Serializer(object): original_error=e) def dump_payload(self, obj): - """Dumps the encoded object into a bytestring. This implementation - uses simplejson. + """Dumps the encoded object. The return value is always a + bytestring. If the internal serializer is text based the value + will automatically be encoded to utf-8. """ return want_bytes(self.serializer.dumps(obj)) @@ -502,22 +511,29 @@ class Serializer(object): return self.signer(self.secret_key, salt=salt, **self.signer_kwargs) def dumps(self, obj, salt=None): - """Returns URL-safe, signed base64 compressed JSON string. - - If compress is True (the default) checks if compressing using zlib can - save some space. Prepends a '.' to signify compression. This is included - in the signature, to protect against zip bombs. + """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. """ - return self.make_signer(salt).sign(self.dump_payload(obj)) + payload = self.dump_payload(obj) + if isinstance(payload, text_type): + payload = payload.encode('utf-8') + 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.""" + """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) return self.load_payload(self.make_signer(salt).unsign(s)) def load(self, f, salt=None): @@ -606,13 +622,12 @@ class JSONWebSignatureSerializer(Serializer): #: The default algorithm to use for signature generation default_algorithm = 'HS256' - def __init__(self, secret_key, salt=None, signer_kwargs=None, algorithm_name=None): - self.secret_key = want_bytes(secret_key) - self.salt = salt if salt is None \ - else want_bytes(salt) - self.serializer = compact_json - self.signer = Signer - self.signer_kwargs = signer_kwargs or {} + default_serializer = compact_json + + def __init__(self, secret_key, salt=None, serializer=None, + signer=None, signer_kwargs=None, algorithm_name=None): + Serializer.__init__(self, secret_key, salt, serializer, + signer, signer_kwargs) if algorithm_name is None: algorithm_name = self.default_algorithm self.algorithm_name = algorithm_name @@ -630,7 +645,7 @@ class JSONWebSignatureSerializer(Serializer): raise BadPayload('Could not base64 decode the payload because of ' 'an exception', original_error=e) header = Serializer.load_payload(self, json_header, - serializer=simplejson) + serializer=json) if not isinstance(header, dict): raise BadPayload('Header payload is not a JSON object') payload = Serializer.load_payload(self, json_payload) @@ -677,7 +692,7 @@ class JSONWebSignatureSerializer(Serializer): return a tuple of payload and header. """ payload, header = self.load_payload( - self.make_signer(salt, self.algorithm).unsign(s), + self.make_signer(salt, self.algorithm).unsign(want_bytes(s)), return_header=True) if header.get('alg') != self.algorithm_name: raise BadSignature('Algorithm mismatch') @@ -697,7 +712,6 @@ class URLSafeSerializerMixin(object): """ def load_payload(self, payload): - payload = want_bytes(payload) decompress = False if payload[0] == b'.': payload = payload[1:] @@ -728,20 +742,6 @@ class URLSafeSerializerMixin(object): return base64d -class _CompactJSON(object): - """Wrapper around simplejson that strips whitespace. - """ - - def loads(self, payload): - return simplejson.loads(payload) - - def dumps(self, obj): - return want_bytes(simplejson.dumps(obj, separators=(',', ':'))) - - -compact_json = _CompactJSON() - - 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 |