Ticket #3285: signedcookies.diff

File signedcookies.diff, 11.2 KB (added by Marty Alchin <gulopine@…>, 12 years ago)

A more complete patch, including documentation

  • django/contrib/signedcookies/middleware.py

     
     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
     24 No newline at end of file
  • django/contrib/signedcookies/utils.py

     
     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)
     25 No newline at end of file
  • docs/add_ons.txt

     
    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

     
     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.
     138 No newline at end of file
  • tests/regressiontests/signedcookies/tests.py

     
     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, '')
     59 No newline at end of file
Back to Top