Django

Code

Ticket #3285: signedcookies.2.diff

File signedcookies.2.diff, 11.8 kB (added by Marty Alchin <gulopine@gamemusic.org>, 1 year ago)

Complete patch again, with corrected 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 a ``'SignedCookiesMiddleware'`` reference to your 
     45``MIDDLEWARE_CLASSES`` setting to enjoy signature protection for all your views. 
     46One thing to keep in mind is that, while all views are automatically handled, 
     47middleware will only be protected 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 
     69Example 
     70------- 
     71 
     72This is an example of how ``MIDDLEWARE_CLASSES`` might look. 
     73 
     74    MIDDLEWARE_CLASSES = ( 
     75        'django.middleware.common.CommonMiddleware', 
     76        'django.contrib.sessions.middleware.SessionMiddleware', 
     77        'django.contrib.signedcookies.middleware.SignedCookiesMiddleware', 
     78        'django.contrib.auth.middleware.AuthenticationMiddleware', 
     79        'django.middleware.doc.XViewMiddleware', 
     80    ) 
     81 
     82Manual signing 
     83============== 
     84 
     85If only a portion of the project needs signed cookies, there is a manual option 
     86that doesn't rely on middleware. There are two functions provided, which can be 
     87used to manually sign and validate cookie values within views. 
     88 
     89These utilities live in ``django.contrib.signedcookies.utils``. 
     90 
     91sign(key, unsigned_value) 
     92------------------------- 
     93 
     94When setting cookies, a signature can be attached manually by using this simple 
     95function. It takes the cookie's name and value and returns a signed value that 
     96is suitable for being set to a cookie. 
     97 
     98This function will not set the cookie to the response; this still has to be done 
     99according to HttpResponse_. 
     100 
     101.. _HttpResponse: ../request_response/ 
     102 
     103unsign(key, signed_value) 
     104------------------------- 
     105 
     106When receiving a signed cookie from an incoming request, the signature will 
     107still be attached, and must be manually validated and removed using this 
     108function. This single function handles both tasks, raising an error for invalid 
     109or unsigned cookies, and returning the plain, unsigned value when successful. 
     110 
     111Invalid cookies 
     112--------------- 
     113 
     114If the cookie is not signed or invalid, ``unsign`` will raise an exception, 
     115which must be handled by the view to prevent server error responses. There are 
     116multiple exception types that could be thrown, so a simple ``except`` would be 
     117appropriate. 
     118 
     119As shown in the example below, a single try/except block is suitable for 
     120handling errors due to a missing, unsigned or invalid cookie. 
     121 
     122Example 
     123------- 
     124 
     125The following example assumes you have some shopping cart data that can be 
     126stored in a cookie, and will be used to populate some ``ShoppingCart`` object. 
     127 
     128    from django.http import HttpResponse 
     129    from django.contrib.signedcookies import utils 
     130    from myapp.utils import ShoppingCart 
     131 
     132    def view_cart(request): 
     133        response = HttpResponse() 
     134        try: 
     135            cart = ShoppingCart(utils.unsign('cart', request.COOKIES['cart'])) 
     136        except: 
     137            # The cookie was no good, so a new one should be set 
     138            cart = ShoppingCart() 
     139            response.set_cookie('cart', utils.sign('cart', cart.cookie_data)) 
     140         
     141        # Continue processing the shopping cart 
     142         
     143        return respose 
     144 
     145A note about stolen cookies 
     146=========================== 
     147 
     148While cookie signing is a reasonable way to ensure that a cookie has not been 
     149edited, there is no guarantee that it is being requested by the same computer 
     150where it was first set. If a computer is compromised and its cookies stolen, 
     151the thief would be able to use the signed cookie with no interference. 
     152 
     153Therefore, any applications that implement mission-critical functionality with 
     154signed 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, '')