Django

Code

Ticket #3285: signedcookies.diff

File signedcookies.diff, 11.2 kB (added by Marty Alchin <gulopine@gamemusic.org>, 3 years ago)

A more complete patch, including documentation

  • django/contrib/signedcookies/middleware.py

    old new  
     1import utils 
     2 
     3class SignedCookiesMiddleware(object): 
     4 
     5    def process_request(self, request): 
     6        for (key, signed_value) in request.COOKIES.items(): 
     7            try: 
     8                request.COOKIES[key] = utils.unsign(key, signed_value) 
     9            except: 
     10                del request.COOKIES[key] 
     11 
     12    def process_response(self, request, response): 
     13        for (key, morsel) in response.cookies.items(): 
     14            if morsel['expires'] == 0 and morsel['max-age'] == 0: 
     15                continue 
     16            response.set_cookie(key, utils.sign(key, morsel.value), 
     17                max_age=morsel['max-age'], 
     18                expires=morsel['expires'], 
     19                path=morsel['path'], 
     20                domain=morsel['domain'], 
     21                secure=morsel['secure'] 
     22            ) 
     23        return response 
  • django/contrib/signedcookies/utils.py

    old new  
     1import re 
     2 
     3from django.conf import settings 
     4 
     5try: 
     6    from hashlib import md5 as hash 
     7except ImportError: 
     8    from md5 import new as hash 
     9 
     10regex = re.compile(r'([0-9a-f]+):(.*)$') 
     11 
     12def get_digest(key, value): 
     13    string = ':'.join([settings.SECRET_KEY, key, value]) 
     14    return hash(string).hexdigest() 
     15 
     16def unsign(key, signed_value): 
     17    "Given a key and signed value, return the unsigned value" 
     18    (signature, unsigned_value) = regex.match(signed_value).groups() 
     19    assert get_digest(key, unsigned_value) == signature 
     20    return unsigned_value 
     21 
     22def sign(key, unsigned_value): 
     23    "Given a key and unsigned value, return the signed value" 
     24    return '%s:%s' % (get_digest(key, unsigned_value), unsigned_value) 
  • docs/add_ons.txt

    old new  
    182182 
    183183.. _redirects documentation: ../redirects/ 
    184184 
     185signedcookies 
     186============= 
     187 
     188An easy way to add extra security to cookie-based applications. 
     189 
     190See the `signed cookies documentation`_. 
     191 
     192.. _signed cookies documentation: ../signed_cookies/ 
     193 
    185194sites 
    186195===== 
    187196 
  • docs/signed_cookies.txt

    old new  
     1============== 
     2Signed cookies 
     3============== 
     4 
     5While cookies are a convenient way to store basic persistent data in client 
     6browsers, users are free to edit them at will, making them inherently insecure. 
     7By attaching signatures to cookies, you can confidently trust that their values 
     8are safe from editing. 
     9 
     10How it works 
     11============ 
     12 
     13Cookies are signed by attaching a digest to the cookie value that can be 
     14verified when the cookie is sent back to the server. This digest is created from 
     15the cookie's name and value, along with the site's secret_. 
     16 
     17Essentially, this creates a set of four values that must all remain intact in 
     18order for the cookie to validate properly. If any one of these is altered on a 
     19client computer, the cookie will be invalid. 
     20 
     21 * ``settings.SECRET_KEY`` 
     22 * Name 
     23 * Value 
     24 * Signature 
     25 
     26This set of values ensures that users are unable to rename the cookie or edit 
     27its value without generating a new signature. Since the secret is known 
     28only to site administrators, users will be unable to generate signatures that 
     29will validate with altered values. 
     30 
     31.. _secret: ../settings/#secret-key 
     32 
     33Automatic signing 
     34================= 
     35 
     36The easiest and most reliable way to sign your cookies is to use the provided 
     37middleware, which will automatically handle everything behind the scenes. In 
     38fact, aside from a single line in your settings, using the middleware requires 
     39no changes in your code. 
     40 
     41Activating the middleware 
     42------------------------- 
     43 
     44Simply add ``'SignedCookiesMiddleware'`` to your ``MIDDLEWARE_CLASSES`` setting 
     45to enjoy signature protection for all your views. One thing to keep in mind is 
     46that, while all views are automatically handled, middleware will only be 
     47protected if they are positioned *after* signed cookies. 
     48 
     49Invalid cookies 
     50--------------- 
     51 
     52Signing and validation are both handled transparently to views, with invalid 
     53cookies being silently removed from ``request.COOKIES`` prior to executing the 
     54view. This means that views will see no difference between an unsigned cookie 
     55and no cookie at all. 
     56 
     57As with a missing cookie, a proper cookie can simply be set within the view, 
     58which will then be signed properly and will validate on the next request. 
     59 
     60A note about sessions 
     61--------------------- 
     62 
     63Though the `sessions framework`_ uses cookies, it only stores the ID, which is 
     64considered safe. This means that it can safely be placed before signed cookies 
     65in your ``MIDDLEWARE_CLASSES``. 
     66 
     67.. _sessions framework: ../sessions/ 
     68 
     69Manual signing 
     70============== 
     71 
     72If only a portion of the project needs signed cookies, there is a manual option 
     73that doesn't rely on middleware. There are two functions provided, which can be 
     74used to manually sign and validate cookie values within views. 
     75 
     76These utilities live in ``django.contrib.signedcookies.utils``. 
     77 
     78sign(key, unsigned_value) 
     79------------------------- 
     80 
     81When setting cookies, a signature can be attached manually by using this simple 
     82function. It takes the cookie's name and value and returns a signed value that 
     83is suitable for being set to a cookie. 
     84 
     85This function will not set the cookie to the response; this still has to be done 
     86according to HttpResponse_. 
     87 
     88.. _HttpResponse: ../request_response/ 
     89 
     90unsign(key, signed_value) 
     91------------------------- 
     92 
     93When receiving a signed cookie from an incoming request, the signature will 
     94still be attached, and must be manually validated and removed using this 
     95function. This single function handles both tasks, raising an error for invalid 
     96or unsigned cookies, and returning the plain, unsigned value when successful. 
     97 
     98Invalid cookies 
     99--------------- 
     100 
     101If the cookie is not signed or invalid, ``unsign`` will raise an exception, 
     102which must be handled by the view to prevent server error responses. There are 
     103multiple exception types that could be thrown, so a simple ``except`` would be 
     104appropriate. 
     105 
     106As shown in the example below, a single try/except block is suitable for 
     107handling errors due to a missing, unsigned or invalid cookie. 
     108 
     109Example 
     110------- 
     111 
     112The following example assumes you have some shopping cart data that can be 
     113stored in a cookie, and will be used to populate some ``ShoppingCart`` object. 
     114 
     115    from django.contrib.signedcookies import utils 
     116    from myapp.utils import ShoppingCart 
     117     
     118    def view_cart(request): 
     119        try: 
     120            cart = ShoppingCart(utils.unsign('cart', request.COOKIES['cart'])) 
     121        except: 
     122            # The cookie was no good, so a new one should be set 
     123            cart = ShoppingCart() 
     124            response.set_cookie('cart', utils.sign('cart', cart.cookie_data)) 
     125         
     126        # Continue processing the shopping cart 
     127 
     128A note about stolen cookies 
     129=========================== 
     130 
     131While cookie signing is a reasonable way to ensure that a cookie has not been 
     132edited, there is no guarantee that it is being requested by the same computer 
     133where it was first set. If a computer is compromised and its cookies stolen, 
     134the thief would be able to use the signed cookie with no interference. 
     135 
     136Therefore, any applications that implement mission-critical functionality with 
     137signed cookies should take extra precautions to anticipate cookie theft. 
  • tests/regressiontests/signedcookies/tests.py

    old new  
     1import unittest 
     2 
     3from django.http import HttpRequest, HttpResponse 
     4 
     5from django.contrib.signedcookies import middleware, utils 
     6 
     7class SignedCookiesTestCase(unittest.TestCase): 
     8 
     9    def setUp(self): 
     10        self.middleware = middleware.SignedCookiesMiddleware() 
     11        (self.key, self.unsigned_value) = ('key', 'value') 
     12        self.signature = utils.get_digest(self.key, self.unsigned_value) 
     13        self.signed_value = '%s:%s' % (self.signature, self.unsigned_value) 
     14 
     15    def test_sign(self): 
     16        "Make sure cookie values can be signed directly" 
     17        signed_value = utils.sign(self.key, self.unsigned_value) 
     18        self.assertEqual(signed_value, self.signed_value) 
     19 
     20    def test_unsign(self): 
     21        "Make sure cookie values can be unsigned directly" 
     22        unsigned_value = utils.unsign(self.key, self.signed_value) 
     23        self.assertEqual(unsigned_value, self.unsigned_value) 
     24 
     25    def test_unsigned_cookie(self): 
     26        "Make sure process_request removes unsigned cookies" 
     27        request = HttpRequest() 
     28        request.COOKIES[self.key] = self.unsigned_value 
     29        self.middleware.process_request(request) 
     30        self.assert_(self.key not in request.COOKIES) 
     31 
     32    def test_invalid_cookie(self): 
     33        "Make sure process_request removes invalid cookies" 
     34        request = HttpRequest() 
     35        request.COOKIES[self.key] = self.signed_value + 'test' 
     36        self.middleware.process_request(request) 
     37        self.assert_(self.key not in request.COOKIES) 
     38 
     39    def test_valid_cookie(self): 
     40        "Make sure process_request removes signatures from valid cookies" 
     41        request = HttpRequest() 
     42        request.COOKIES[self.key] = self.signed_value 
     43        self.middleware.process_request(request) 
     44        self.assertEqual(request.COOKIES[self.key], self.unsigned_value) 
     45 
     46    def test_set_cookie(self): 
     47        "Make sure process_response signs cookies appropriately" 
     48        response = HttpResponse() 
     49        response.set_cookie(self.key, self.unsigned_value) 
     50        self.middleware.process_response(HttpRequest(), response) 
     51        self.assertEqual(response.cookies[self.key].value, self.signed_value) 
     52 
     53    def test_delete_cookie(self): 
     54        "Make sure process_response leaves deleted cookies alone" 
     55        response = HttpResponse() 
     56        response.delete_cookie(self.key) 
     57        self.middleware.process_response(HttpRequest(), response) 
     58        self.assertEqual(response.cookies[self.key].value, '')