Ticket #3285: signedcookies.2.diff

File signedcookies.2.diff, 11.8 KB (added by Marty Alchin <gulopine@…>, 17 years ago)

Complete patch again, with corrected 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 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.
     155 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