Ticket #12417: ticket12417-v7.diff
File ticket12417-v7.diff, 35.2 KB (added by , 13 years ago) |
---|
-
django/conf/global_settings.py
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 88aa5a3..c98cab7 100644
a b LOGIN_REDIRECT_URL = '/accounts/profile/' 476 476 # The number of days a password reset link is valid for 477 477 PASSWORD_RESET_TIMEOUT_DAYS = 3 478 478 479 ########### 480 # SIGNING # 481 ########### 482 483 SIGNING_BACKEND = 'django.core.signing.TimestampSigner' 484 479 485 ######## 480 486 # CSRF # 481 487 ######## -
new file django/core/signing.py
diff --git a/django/core/signing.py b/django/core/signing.py new file mode 100644 index 0000000..3b0a48b
- + 1 """ 2 Functions for creating and restoring url-safe signed JSON objects. 3 4 The format used looks like this: 5 6 >>> signed.dumps("hello") 7 'ImhlbGxvIg.RjVSUCt6S64WBilMYxG89-l0OA8' 8 9 There are two components here, separatad by a '.'. The first component is a 10 URLsafe base64 encoded JSON of the object passed to dumps(). The second 11 component is a base64 encoded hmac/SHA1 hash of "$first_component.$secret" 12 13 signed.loads(s) checks the signature and returns the deserialised object. 14 If the signature fails, a BadSignature exception is raised. 15 16 >>> signed.loads("ImhlbGxvIg.RjVSUCt6S64WBilMYxG89-l0OA8") 17 u'hello' 18 >>> signed.loads("ImhlbGxvIg.RjVSUCt6S64WBilMYxG89-l0OA8-modified") 19 ... 20 BadSignature: Signature failed: RjVSUCt6S64WBilMYxG89-l0OA8-modified 21 22 You can optionally compress the JSON prior to base64 encoding it to save 23 space, using the compress=True argument. This checks if compression actually 24 helps and only applies compression if the result is a shorter string: 25 26 >>> signed.dumps(range(1, 20), compress=True) 27 '.eJwFwcERACAIwLCF-rCiILN47r-GyZVJsNgkxaFxoDgxcOHGxMKD_T7vhAml.oFq6lAAEbkHXBHfGnVX7Qx6NlZ8' 28 29 The fact that the string is compressed is signalled by the prefixed '.' at the 30 start of the base64 JSON. 31 32 There are 65 url-safe characters: the 64 used by url-safe base64 and the '.'. 33 These functions make use of all of them. 34 """ 35 import base64 36 import time 37 import zlib 38 39 from django.conf import settings 40 from django.core.exceptions import ImproperlyConfigured 41 from django.utils import baseconv, simplejson 42 from django.utils.crypto import constant_time_compare, salted_hmac 43 from django.utils.encoding import force_unicode, smart_str 44 from django.utils.importlib import import_module 45 46 47 class BadSignature(Exception): 48 """ 49 Signature does not match 50 """ 51 pass 52 53 54 class SignatureExpired(BadSignature): 55 """ 56 Signature timestamp is older than required max_age 57 """ 58 pass 59 60 61 def b64_encode(s): 62 return base64.urlsafe_b64encode(s).strip('=') 63 64 65 def b64_decode(s): 66 pad = '=' * (-len(s) % 4) 67 return base64.urlsafe_b64decode(s + pad) 68 69 70 def base64_hmac(salt, value, key): 71 return b64_encode(salted_hmac(salt, value, key).digest()) 72 73 74 def get_cookie_signer(salt='django.core.signing.get_cookie_signer'): 75 modpath = settings.SIGNING_BACKEND 76 module, attr = modpath.rsplit('.', 1) 77 try: 78 mod = import_module(module) 79 except ImportError, e: 80 raise ImproperlyConfigured( 81 'Error importing cookie signer %s: "%s"' % (modpath, e)) 82 try: 83 Signer = getattr(mod, attr) 84 except AttributeError, e: 85 raise ImproperlyConfigured( 86 'Error importing cookie signer %s: "%s"' % (modpath, e)) 87 return Signer('django.http.cookies' + settings.SECRET_KEY, salt=salt) 88 89 90 def dumps(obj, key=None, salt='django.core.signing', compress=False): 91 """ 92 Returns URL-safe, sha1 signed base64 compressed JSON string. If key is 93 None, settings.SECRET_KEY is used instead. 94 95 If compress is True (not the default) checks if compressing using zlib can 96 save some space. Prepends a '.' to signify compression. This is included 97 in the signature, to protect against zip bombs. 98 99 salt can be used to further salt the hash, in case you're worried 100 that the NSA might try to brute-force your SHA-1 protected secret. 101 """ 102 json = simplejson.dumps(obj, separators=(',', ':')) 103 104 # Flag for if it's been compressed or not 105 is_compressed = False 106 107 if compress: 108 # Avoid zlib dependency unless compress is being used 109 compressed = zlib.compress(json) 110 if len(compressed) < (len(json) - 1): 111 json = compressed 112 is_compressed = True 113 base64d = b64_encode(json) 114 if is_compressed: 115 base64d = '.' + base64d 116 return TimestampSigner(key, salt=salt).sign(base64d) 117 118 119 def loads(s, key=None, salt='django.core.signing', max_age=None): 120 """ 121 Reverse of dumps(), raises BadSignature if signature fails 122 """ 123 base64d = smart_str( 124 TimestampSigner(key, salt=salt).unsign(s, max_age=max_age)) 125 decompress = False 126 if base64d[0] == '.': 127 # It's compressed; uncompress it first 128 base64d = base64d[1:] 129 decompress = True 130 json = b64_decode(base64d) 131 if decompress: 132 json = zlib.decompress(json) 133 return simplejson.loads(json) 134 135 136 class Signer(object): 137 def __init__(self, key=None, sep=':', salt=None): 138 self.sep = sep 139 self.key = key or settings.SECRET_KEY 140 self.salt = salt or ('%s.%s' % 141 (self.__class__.__module__, self.__class__.__name__)) 142 143 def signature(self, value): 144 return base64_hmac(self.salt + 'signer', value, self.key) 145 146 def sign(self, value): 147 value = smart_str(value) 148 return '%s%s%s' % (value, self.sep, self.signature(value)) 149 150 def unsign(self, signed_value): 151 signed_value = smart_str(signed_value) 152 if not self.sep in signed_value: 153 raise BadSignature('No "%s" found in value' % self.sep) 154 value, sig = signed_value.rsplit(self.sep, 1) 155 if constant_time_compare(sig, self.signature(value)): 156 return force_unicode(value) 157 raise BadSignature('Signature "%s" does not match' % sig) 158 159 160 class TimestampSigner(Signer): 161 def timestamp(self): 162 return baseconv.base62.encode(int(time.time())) 163 164 def sign(self, value): 165 value = smart_str('%s%s%s' % (value, self.sep, self.timestamp())) 166 return '%s%s%s' % (value, self.sep, self.signature(value)) 167 168 def unsign(self, value, max_age=None): 169 result = super(TimestampSigner, self).unsign(value) 170 value, timestamp = result.rsplit(self.sep, 1) 171 timestamp = baseconv.base62.decode(timestamp) 172 if max_age is not None: 173 # Check timestamp is not older than max_age 174 age = time.time() - timestamp 175 if age > max_age: 176 raise SignatureExpired( 177 'Signature age %s > %s seconds' % (age, max_age)) 178 return value -
django/http/__init__.py
diff --git a/django/http/__init__.py b/django/http/__init__.py index 0d28ec0..a3fd7f5 100644
a b from django.utils.encoding import smart_str, iri_to_uri, force_unicode 122 122 from django.utils.http import cookie_date 123 123 from django.http.multipartparser import MultiPartParser 124 124 from django.conf import settings 125 from django.core import signing 125 126 from django.core.files import uploadhandler 126 127 from utils import * 127 128 … … absolute_http_url_re = re.compile(r"^https?://", re.I) 132 133 class Http404(Exception): 133 134 pass 134 135 136 RAISE_ERROR = object() 137 135 138 class HttpRequest(object): 136 139 """A basic HTTP request.""" 137 140 … … class HttpRequest(object): 170 173 # Rather than crash if this doesn't happen, we encode defensively. 171 174 return '%s%s' % (self.path, self.META.get('QUERY_STRING', '') and ('?' + iri_to_uri(self.META.get('QUERY_STRING', ''))) or '') 172 175 176 def get_signed_cookie(self, key, default=RAISE_ERROR, salt='', max_age=None): 177 """ 178 Attempts to return a signed cookie. If the signature fails or the 179 cookie has expired, raises an exception... unless you provide the 180 default argument in which case that value will be returned instead. 181 """ 182 try: 183 cookie_value = self.COOKIES[key].encode('utf-8') 184 except KeyError: 185 if default is not RAISE_ERROR: 186 return default 187 else: 188 raise 189 try: 190 value = signing.get_cookie_signer(salt=key + salt).unsign( 191 cookie_value, max_age=max_age) 192 except signing.BadSignature: 193 if default is not RAISE_ERROR: 194 return default 195 else: 196 raise 197 return value 198 173 199 def build_absolute_uri(self, location=None): 174 200 """ 175 201 Builds an absolute URI from the location and the variables available in … … class HttpResponse(object): 584 610 if httponly: 585 611 self.cookies[key]['httponly'] = True 586 612 613 def set_signed_cookie(self, key, value, salt='', **kwargs): 614 value = signing.get_cookie_signer(salt=key + salt).sign(value) 615 return self.set_cookie(key, value, **kwargs) 616 587 617 def delete_cookie(self, key, path='/', domain=None): 588 618 self.set_cookie(key, max_age=0, path=path, domain=domain, 589 619 expires='Thu, 01-Jan-1970 00:00:00 GMT') … … def str_to_unicode(s, encoding): 686 716 return unicode(s, encoding, 'replace') 687 717 else: 688 718 return s 689 -
new file django/utils/baseconv.py
diff --git a/django/utils/baseconv.py b/django/utils/baseconv.py new file mode 100644 index 0000000..702c172
- + 1 # Copyright (c) 2010 Taurinus Collective. All rights reserved. 2 # Copyright (c) 2009 Simon Willison. All rights reserved. 3 # Copyright (c) 2002 Drew Perttula. All rights reserved. 4 # 5 # License: 6 # Python Software Foundation License version 2 7 # 8 # See the file "LICENSE" for terms & conditions for usage, and a DISCLAIMER OF 9 # ALL WARRANTIES. 10 # 11 # This Baseconv distribution contains no GNU General Public Licensed (GPLed) 12 # code so it may be used in proprietary projects just like prior ``baseconv`` 13 # distributions. 14 # 15 # All trademarks referenced herein are property of their respective holders. 16 # 17 18 """ 19 Convert numbers from base 10 integers to base X strings and back again. 20 21 Sample usage:: 22 23 >>> base20 = BaseConverter('0123456789abcdefghij') 24 >>> base20.encode(1234) 25 '31e' 26 >>> base20.decode('31e') 27 1234 28 >>> base20.encode(-1234) 29 '-31e' 30 >>> base20.decode('-31e') 31 -1234 32 >>> base11 = BaseConverter('0123456789-', sign='$') 33 >>> base11.encode('$1234') 34 '$-22' 35 >>> base11.decode('$-22') 36 '$1234' 37 38 """ 39 40 BASE2_ALPHABET = '01' 41 BASE16_ALPHABET = '0123456789ABCDEF' 42 BASE56_ALPHABET = '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz' 43 BASE36_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz' 44 BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' 45 BASE64_ALPHABET = BASE62_ALPHABET + '-_' 46 47 class BaseConverter(object): 48 decimal_digits = '0123456789' 49 50 def __init__(self, digits, sign='-'): 51 self.sign = sign 52 self.digits = digits 53 if sign in self.digits: 54 raise ValueError('Sign character found in converter base digits.') 55 56 def __repr__(self): 57 return "<BaseConverter: base%s (%s)>" % (len(self.digits), self.digits) 58 59 def encode(self, i): 60 neg, value = self.convert(i, self.decimal_digits, self.digits, '-') 61 if neg: 62 return self.sign + value 63 return value 64 65 def decode(self, s): 66 neg, value = self.convert(s, self.digits, self.decimal_digits, self.sign) 67 if neg: 68 value = '-' + value 69 return int(value) 70 71 def convert(self, number, from_digits, to_digits, sign): 72 if str(number)[0] == sign: 73 number = str(number)[1:] 74 neg = 1 75 else: 76 neg = 0 77 78 # make an integer out of the number 79 x = 0 80 for digit in str(number): 81 x = x * len(from_digits) + from_digits.index(digit) 82 83 # create the result in base 'len(to_digits)' 84 if x == 0: 85 res = to_digits[0] 86 else: 87 res = '' 88 while x > 0: 89 digit = x % len(to_digits) 90 res = to_digits[digit] + res 91 x = int(x / len(to_digits)) 92 return neg, res 93 94 base2 = BaseConverter(BASE2_ALPHABET) 95 base16 = BaseConverter(BASE16_ALPHABET) 96 base36 = BaseConverter(BASE36_ALPHABET) 97 base56 = BaseConverter(BASE56_ALPHABET) 98 base62 = BaseConverter(BASE62_ALPHABET) 99 base64 = BaseConverter(BASE64_ALPHABET, sign='$') -
docs/index.txt
diff --git a/docs/index.txt b/docs/index.txt index 9135d32..8b4ae53 100644
a b Other batteries included 171 171 * :doc:`Comments <ref/contrib/comments/index>` | :doc:`Moderation <ref/contrib/comments/moderation>` | :doc:`Custom comments <ref/contrib/comments/custom>` 172 172 * :doc:`Content types <ref/contrib/contenttypes>` 173 173 * :doc:`Cross Site Request Forgery protection <ref/contrib/csrf>` 174 * :doc:`Cryptographic signing <topics/signing>` 174 175 * :doc:`Databrowse <ref/contrib/databrowse>` 175 176 * :doc:`E-mail (sending) <topics/email>` 176 177 * :doc:`Flatpages <ref/contrib/flatpages>` -
docs/ref/request-response.txt
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 6281120..e17c0a7 100644
a b Methods 240 240 241 241 Example: ``"http://example.com/music/bands/the_beatles/?print=true"`` 242 242 243 .. method:: HttpRequest.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None) 244 245 .. versionadded:: 1.4 246 247 Returns a cookie value for a signed cookie, or raises a 248 :class:`~django.core.signing.BadSignature` exception if the signature is 249 no longer valid. If you provide the ``default`` argument the exception 250 will be suppressed and that default value will be returned instead. 251 252 The optional ``salt`` argument can be used to provide extra protection 253 against brute force attacks on your secret key. If supplied, the 254 ``max_age`` argument will be checked against the signed timestamp 255 attached to the cookie value to ensure the cookie is not older than 256 ``max_age`` seconds. 257 258 For example:: 259 260 >>> request.get_signed_cookie('name') 261 'Tony' 262 >>> request.get_signed_cookie('name', salt='name-salt') 263 'Tony' # assuming cookie was set using the same salt 264 >>> request.get_signed_cookie('non-existing-cookie') 265 ... 266 KeyError: 'non-existing-cookie' 267 >>> request.get_signed_cookie('non-existing-cookie', False) 268 False 269 >>> request.get_signed_cookie('cookie-that-was-tampered-with') 270 ... 271 BadSignature: ... 272 >>> request.get_signed_cookie('name', max_age=60) 273 ... 274 SignatureExpired: Signature age 1677.3839159 > 60 seconds 275 >>> request.get_signed_cookie('name', False, max_age=60) 276 False 277 278 See :ref:`cryptographic signing <topics-signing>` for more information. 279 243 280 .. method:: HttpRequest.is_secure() 244 281 245 282 Returns ``True`` if the request is secure; that is, if it was made with … … Methods 618 655 .. _`cookie Morsel`: http://docs.python.org/library/cookie.html#Cookie.Morsel 619 656 .. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly 620 657 658 .. method:: HttpResponse.set_signed_cookie(key, value='', salt='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=False) 659 660 .. versionadded:: 1.4 661 662 Like :meth:`~HttpResponse.set_cookie()`, but 663 :ref:`cryptographically signs <topics-signing>` the cookie before setting 664 it. Use in conjunction with :meth:`HttpRequest.get_signed_cookie`. 665 You can use the optional ``salt`` argument for added key strength, but 666 you will need to remember to pass it to the corresponding 667 :meth:`HttpRequest.get_signed_cookie` call. 668 621 669 .. method:: HttpResponse.delete_cookie(key, path='/', domain=None) 622 670 623 671 Deletes the cookie with the given key. Fails silently if the key doesn't -
docs/ref/settings.txt
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index f5f1226..38977e8 100644
a b See :tfilter:`allowed date format strings <date>`. 1647 1647 1648 1648 See also ``DATE_FORMAT`` and ``SHORT_DATETIME_FORMAT``. 1649 1649 1650 .. setting:: SIGNING_BACKEND 1651 1652 SIGNING_BACKEND 1653 --------------- 1654 1655 .. versionadded:: 1.4 1656 1657 Default: 'django.core.signing.TimestampSigner' 1658 1659 The backend used for signing cookies and other data. 1660 1661 See also the :ref:`topics-signing` documentation. 1662 1650 1663 .. setting:: SITE_ID 1651 1664 1652 1665 SITE_ID -
docs/topics/http/shortcuts.txt
diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 3a43fe1..37ec98e 100644
a b This example is equivalent to:: 89 89 ``render_to_response`` 90 90 ====================== 91 91 92 .. function:: render_to_response(template _name[, dictionary][, context_instance][, mimetype])92 .. function:: render_to_response(template[, dictionary][, context_instance][, mimetype]) 93 93 94 94 Renders a given template with a given context dictionary and returns an 95 95 :class:`~django.http.HttpResponse` object with that rendered text. … … This example is equivalent to:: 97 97 Required arguments 98 98 ------------------ 99 99 100 ``template _name``100 ``template`` 101 101 The full name of a template to use or sequence of template names. If a 102 102 sequence is given, the first template that exists will be used. See the 103 103 :ref:`template loader documentation <ref-templates-api-the-python-api>` -
docs/topics/http/views.txt
diff --git a/docs/topics/http/views.txt b/docs/topics/http/views.txt index cfdd008..562be53 100644
a b of a Web page, or a redirect, or a 404 error, or an XML document, or an image . 8 8 . . or anything, really. The view itself contains whatever arbitrary logic is 9 9 necessary to return that response. This code can live anywhere you want, as long 10 10 as it's on your Python path. There's no other requirement--no "magic," so to 11 speak. For the sake of putting the code *somewhere*, the convention is to12 put views in a file called ``views.py``, placed in your project or 13 application directory.11 speak. For the sake of putting the code *somewhere*, let's create a file called 12 ``views.py`` in the ``mysite`` directory, which you created in the previous 13 chapter. 14 14 15 15 A simple view 16 16 ============= … … Let's step through this code one line at a time: 47 47 exceptions, but we'll get to those later.) 48 48 49 49 .. admonition:: Django's Time Zone 50 50 51 51 Django includes a ``TIME_ZONE`` setting that defaults to 52 52 ``America/Chicago``. This probably isn't where you live, so you might want 53 53 to change it in your settings file. -
docs/topics/index.txt
diff --git a/docs/topics/index.txt b/docs/topics/index.txt index 49a03be..84f9e9f 100644
a b Introductions to all the key parts of Django you'll need to know: 18 18 auth 19 19 cache 20 20 conditional-view-processing 21 signing 21 22 email 22 23 i18n/index 23 24 logging -
new file docs/topics/signing.txt
diff --git a/docs/topics/signing.txt b/docs/topics/signing.txt new file mode 100644 index 0000000..8947212
- + 1 .. _topics-signing: 2 3 ===================== 4 Cryptographic signing 5 ===================== 6 7 .. module:: django.core.signing 8 :synopsis: Django's signing framework. 9 10 .. versionadded:: 1.4 11 12 The golden rule of Web application security is to never trust data from 13 untrusted sources. Sometimes it can be useful to pass data through an 14 untrusted medium. Cryptographically signed values can be passed through an 15 untrusted channel safe in the knowledge that any tampering will be detected. 16 17 Django provides both a low-level API for signing values and a high-level API 18 for setting and reading signed cookies, one of the most common uses of 19 signing in Web applications. 20 21 You may also find signing useful for the following: 22 23 * Generating "recover my account" URLs for sending to users who have 24 lost their password. 25 26 * Ensuring data stored in hidden form fields has not been tampered with. 27 28 * Generating one-time secret URLs for allowing temporary access to a 29 protected resource, for example a downloadable file that a user has 30 paid for. 31 32 Protecting the SECRET_KEY 33 ========================= 34 35 When you create a new Django project using :djadmin:`startproject`, the 36 ``settings.py`` file it generates automatically gets a random 37 :setting:`SECRET_KEY` value. This value is the key to securing signed 38 data -- it is vital you keep this secure, or attackers could use it to 39 generate their own signed values. 40 41 Using the low-level API 42 ======================= 43 44 .. class:: Signer 45 46 Django's signing methods live in the ``django.core.signing`` module. 47 To sign a value, first instantiate a ``Signer`` instance:: 48 49 >>> from django.core.signing import Signer 50 >>> signer = Signer() 51 >>> value = signer.sign('My string') 52 >>> value 53 'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w' 54 55 The signature is appended to the end of the string, following the colon. 56 You can retrieve the original value using the ``unsign`` method:: 57 58 >>> original = signer.unsign(value) 59 >>> original 60 u'My string' 61 62 If the signature or value have been altered in any way, a 63 ``django.core.signing.BadSigature`` exception will be raised:: 64 65 >>> value += 'm' 66 >>> try: 67 ... original = signer.unsign(value) 68 ... except signing.BadSignature: 69 ... print "Tampering detected!" 70 71 By default, the ``Signer`` class uses the :setting:`SECRET_KEY` setting to 72 generate signatures. You can use a different secret by passing it to the 73 ``Signer`` constructor:: 74 75 >>> signer = Signer('my-other-secret') 76 >>> value = signer.sign('My string') 77 >>> value 78 'My string:EkfQJafvGyiofrdGnuthdxImIJw' 79 80 Using the salt argument 81 ----------------------- 82 83 If you do not wish to use the same key for every signing operation in your 84 application, you can use the optional ``salt`` argument to the ``Signer`` 85 class to further strengthen your :setting:`SECRET_KEY` against brute force 86 attacks. Using a salt will cause a new key to be derived from both the salt 87 and your :setting:`SECRET_KEY`:: 88 89 >>> signer = Signer() 90 >>> signer.sign('My string') 91 'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w' 92 >>> signer = Signer(salt='extra') 93 >>> signer.sign('My string') 94 'My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw' 95 >>> signer.unsign('My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw') 96 u'My string' 97 98 Unlike your :setting:`SECRET_KEY`, your salt argument does not need to stay 99 secret. 100 101 Verifying timestamped values 102 ---------------------------- 103 104 .. class:: TimestampSigner 105 106 ``TimestampSigner`` is a subclass of :class:`~Signer` that appends a signed 107 timestamp to the value. This allows you to confirm that a signed value was 108 created within a specified period of time:: 109 110 >>> from django.core.signing import TimestampSigner 111 >>> signer = TimestampSigner() 112 >>> value = signer.sign('hello') 113 >>> value 114 'hello:1NMg5H:oPVuCqlJWmChm1rA2lyTUtelC-c' 115 >>> signer.unsign(value) 116 u'hello' 117 >>> signer.unsign(value, max_age=10) 118 ... 119 SignatureExpired: Signature age 15.5289158821 > 10 seconds 120 >>> signer.unsign(value, max_age=20) 121 u'hello' 122 123 Protecting complex data structures 124 ---------------------------------- 125 126 If you wish to protect a list, tuple or dictionary you can do so using the 127 signing module's dumps and loads functions. These imitate Python's pickle 128 module, but uses JSON serialization under the hood. JSON ensures that even 129 if your :setting:`SECRET_KEY` is stolen an attacker will not be able to 130 execute arbitrary commands by exploiting the pickle format.:: 131 132 >>> from django.core import signing 133 >>> value = signing.dumps({"foo": "bar"}) 134 >>> value 135 'eyJmb28iOiJiYXIifQ:1NMg1b:zGcDE4-TCkaeGzLeW9UQwZesciI' 136 >>> signing.loads(value) 137 {'foo': 'bar'} -
docs/topics/testing.txt
diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 07049fd..36045d2 100644
a b this use case Django provides a standard `Python context manager`_ 1385 1385 response = self.client.get('/sekrit/') 1386 1386 self.assertRedirects(response, '/accounts/login/?next=/sekrit/') 1387 1387 1388 # Then override the LOGIN _URL setting1388 # Then override the LOGING_URL setting 1389 1389 with self.settings(LOGIN_URL='/other/login/'): 1390 1390 response = self.client.get('/sekrit/') 1391 1391 self.assertRedirects(response, '/other/login/?next=/sekrit/') … … decorate the class:: 1438 1438 .. note:: 1439 1439 1440 1440 When overriding settings make sure to also handle the cases in which 1441 Django or your app's code use s a cache or similar feature that retains1441 Django or your app's code use a cache or another feature that retain 1442 1442 state even if the setting is changed. Django provides the 1443 1443 :data:`django.test.signals.setting_changed` signal to connect cleanup 1444 and other state -resetting callbacks to.1444 and other state resetting callbacks to. 1445 1445 1446 1446 .. _`Python context manager`: http://www.python.org/dev/peps/pep-0343/ 1447 1447 .. _`decorator`: http://www.python.org/dev/peps/pep-0318/ -
new file tests/regressiontests/signed_cookies_tests/models.py
diff --git a/tests/regressiontests/signed_cookies_tests/__init__.py b/tests/regressiontests/signed_cookies_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/regressiontests/signed_cookies_tests/models.py b/tests/regressiontests/signed_cookies_tests/models.py new file mode 100644 index 0000000..71abcc5
- + 1 # models.py file for tests to run. -
new file tests/regressiontests/signed_cookies_tests/tests.py
diff --git a/tests/regressiontests/signed_cookies_tests/tests.py b/tests/regressiontests/signed_cookies_tests/tests.py new file mode 100644 index 0000000..c28892a
- + 1 import time 2 3 from django.core import signing 4 from django.http import HttpRequest, HttpResponse 5 from django.test import TestCase 6 7 class SignedCookieTest(TestCase): 8 9 def test_can_set_and_read_signed_cookies(self): 10 response = HttpResponse() 11 response.set_signed_cookie('c', 'hello') 12 self.assertIn('c', response.cookies) 13 self.assertTrue(response.cookies['c'].value.startswith('hello:')) 14 request = HttpRequest() 15 request.COOKIES['c'] = response.cookies['c'].value 16 value = request.get_signed_cookie('c') 17 self.assertEqual(value, u'hello') 18 19 def test_can_use_salt(self): 20 response = HttpResponse() 21 response.set_signed_cookie('a', 'hello', salt='one') 22 request = HttpRequest() 23 request.COOKIES['a'] = response.cookies['a'].value 24 value = request.get_signed_cookie('a', salt='one') 25 self.assertEqual(value, u'hello') 26 self.assertRaises(signing.BadSignature, 27 request.get_signed_cookie, 'a', salt='two') 28 29 def test_detects_tampering(self): 30 response = HttpResponse() 31 response.set_signed_cookie('c', 'hello') 32 request = HttpRequest() 33 request.COOKIES['c'] = response.cookies['c'].value[:-2] + '$$' 34 self.assertRaises(signing.BadSignature, 35 request.get_signed_cookie, 'c') 36 37 def test_default_argument_supresses_exceptions(self): 38 response = HttpResponse() 39 response.set_signed_cookie('c', 'hello') 40 request = HttpRequest() 41 request.COOKIES['c'] = response.cookies['c'].value[:-2] + '$$' 42 self.assertEqual(request.get_signed_cookie('c', default=None), None) 43 44 def test_max_age_argument(self): 45 value = u'hello' 46 _time = time.time 47 time.time = lambda: 123456789 48 try: 49 response = HttpResponse() 50 response.set_signed_cookie('c', value) 51 request = HttpRequest() 52 request.COOKIES['c'] = response.cookies['c'].value 53 self.assertEqual(request.get_signed_cookie('c'), value) 54 55 time.time = lambda: 123456800 56 self.assertEqual(request.get_signed_cookie('c', max_age=12), value) 57 self.assertEqual(request.get_signed_cookie('c', max_age=11), value) 58 self.assertRaises(signing.SignatureExpired, 59 request.get_signed_cookie, 'c', max_age = 10) 60 finally: 61 time.time = _time -
new file tests/regressiontests/signing/models.py
diff --git a/tests/regressiontests/signing/__init__.py b/tests/regressiontests/signing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/regressiontests/signing/models.py b/tests/regressiontests/signing/models.py new file mode 100644 index 0000000..71abcc5
- + 1 # models.py file for tests to run. -
new file tests/regressiontests/signing/tests.py
diff --git a/tests/regressiontests/signing/tests.py b/tests/regressiontests/signing/tests.py new file mode 100644 index 0000000..0b0cacf
- + 1 import time 2 3 from django.core import signing 4 from django.test import TestCase 5 from django.utils.encoding import force_unicode 6 7 class TestSigner(TestCase): 8 9 def test_signature(self): 10 "signature() method should generate a signature" 11 signer = signing.Signer('predictable-secret') 12 signer2 = signing.Signer('predictable-secret2') 13 for s in ( 14 'hello', 15 '3098247:529:087:', 16 u'\u2019'.encode('utf8'), 17 ): 18 self.assertEqual( 19 signer.signature(s), 20 signing.base64_hmac(signer.salt + 'signer', s, 21 'predictable-secret') 22 ) 23 self.assertNotEqual(signer.signature(s), signer2.signature(s)) 24 25 def test_signature_with_salt(self): 26 "signature(value, salt=...) should work" 27 signer = signing.Signer('predictable-secret', salt='extra-salt') 28 self.assertEqual( 29 signer.signature('hello'), 30 signing.base64_hmac('extra-salt' + 'signer', 31 'hello', 'predictable-secret')) 32 self.assertNotEqual( 33 signing.Signer('predictable-secret', salt='one').signature('hello'), 34 signing.Signer('predictable-secret', salt='two').signature('hello')) 35 36 def test_sign_unsign(self): 37 "sign/unsign should be reversible" 38 signer = signing.Signer('predictable-secret') 39 examples = ( 40 'q;wjmbk;wkmb', 41 '3098247529087', 42 '3098247:529:087:', 43 'jkw osanteuh ,rcuh nthu aou oauh ,ud du', 44 u'\u2019', 45 ) 46 for example in examples: 47 self.assertNotEqual( 48 force_unicode(example), force_unicode(signer.sign(example))) 49 self.assertEqual(example, signer.unsign(signer.sign(example))) 50 51 def unsign_detects_tampering(self): 52 "unsign should raise an exception if the value has been tampered with" 53 signer = signing.Signer('predictable-secret') 54 value = 'Another string' 55 signed_value = signer.sign(value) 56 transforms = ( 57 lambda s: s.upper(), 58 lambda s: s + 'a', 59 lambda s: 'a' + s[1:], 60 lambda s: s.replace(':', ''), 61 ) 62 self.assertEqual(value, signer.unsign(signed_value)) 63 for transform in transforms: 64 self.assertRaises( 65 signing.BadSignature, signer.unsign, transform(signed_value)) 66 67 def test_dumps_loads(self): 68 "dumps and loads be reversible for any JSON serializable object" 69 objects = ( 70 ['a', 'list'], 71 'a string', 72 u'a unicode string \u2019', 73 {'a': 'dictionary'}, 74 ) 75 for o in objects: 76 self.assertNotEqual(o, signing.dumps(o)) 77 self.assertEqual(o, signing.loads(signing.dumps(o))) 78 79 def test_decode_detects_tampering(self): 80 "loads should raise exception for tampered objects" 81 transforms = ( 82 lambda s: s.upper(), 83 lambda s: s + 'a', 84 lambda s: 'a' + s[1:], 85 lambda s: s.replace(':', ''), 86 ) 87 value = { 88 'foo': 'bar', 89 'baz': 1, 90 } 91 encoded = signing.dumps(value) 92 self.assertEqual(value, signing.loads(encoded)) 93 for transform in transforms: 94 self.assertRaises( 95 signing.BadSignature, signing.loads, transform(encoded)) 96 97 class TestTimestampSigner(TestCase): 98 99 def test_timestamp_signer(self): 100 value = u'hello' 101 _time = time.time 102 time.time = lambda: 123456789 103 try: 104 signer = signing.TimestampSigner('predictable-key') 105 ts = signer.sign(value) 106 self.assertNotEqual(ts, 107 signing.Signer('predictable-key').sign(value)) 108 109 self.assertEqual(signer.unsign(ts), value) 110 time.time = lambda: 123456800 111 self.assertEqual(signer.unsign(ts, max_age=12), value) 112 self.assertEqual(signer.unsign(ts, max_age=11), value) 113 self.assertRaises( 114 signing.SignatureExpired, signer.unsign, ts, max_age=10) 115 finally: 116 time.time = _time -
new file tests/regressiontests/utils/baseconv.py
diff --git a/tests/regressiontests/utils/baseconv.py b/tests/regressiontests/utils/baseconv.py new file mode 100644 index 0000000..75660d8
- + 1 from unittest import TestCase 2 from django.utils.baseconv import base2, base16, base36, base56, base62, base64, BaseConverter 3 4 class TestBaseConv(TestCase): 5 6 def test_baseconv(self): 7 nums = [-10 ** 10, 10 ** 10] + range(-100, 100) 8 for converter in [base2, base16, base36, base56, base62, base64]: 9 for i in nums: 10 self.assertEqual(i, converter.decode(converter.encode(i))) 11 12 def test_base11(self): 13 base11 = BaseConverter('0123456789-', sign='$') 14 self.assertEqual(base11.encode(1234), '-22') 15 self.assertEqual(base11.decode('-22'), 1234) 16 self.assertEqual(base11.encode(-1234), '$-22') 17 self.assertEqual(base11.decode('$-22'), -1234) 18 19 def test_base20(self): 20 base20 = BaseConverter('0123456789abcdefghij') 21 self.assertEqual(base20.encode(1234), '31e') 22 self.assertEqual(base20.decode('31e'), 1234) 23 self.assertEqual(base20.encode(-1234), '-31e') 24 self.assertEqual(base20.decode('-31e'), -1234) 25 26 def test_base64(self): 27 self.assertEqual(base64.encode(1234), 'JI') 28 self.assertEqual(base64.decode('JI'), 1234) 29 self.assertEqual(base64.encode(-1234), '$JI') 30 self.assertEqual(base64.decode('$JI'), -1234) 31 32 def test_base7(self): 33 base7 = BaseConverter('cjdhel3', sign='g') 34 self.assertEqual(base7.encode(1234), 'hejd') 35 self.assertEqual(base7.decode('hejd'), 1234) 36 self.assertEqual(base7.encode(-1234), 'ghejd') 37 self.assertEqual(base7.decode('ghejd'), -1234) 38 39 def test_exception(self): 40 self.assertRaises(ValueError, BaseConverter, 'abc', sign='a') 41 self.assertTrue(isinstance(BaseConverter('abc', sign='d'), BaseConverter)) -
tests/regressiontests/utils/tests.py
diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index 5c4c060..2b61627 100644
a b from timesince import * 17 17 from datastructures import * 18 18 from tzinfo import * 19 19 from datetime_safe import * 20 from baseconv import *