~ubuntuone-pqm-team/canonical-identity-provider/trunk

« back to all changes in this revision

Viewing changes to identityprovider/middleware/csrf.py

  • Committer: Danny Tamez
  • Date: 2010-04-21 15:29:24 UTC
  • Revision ID: danny.tamez@canonical.com-20100421152924-lq1m92tstk2iz75a
Canonical SSO Provider (Open Source) - Initial Commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2010 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""
 
5
Taken verbatim from Django 1.1.1.
 
6
 
 
7
Cross Site Request Forgery Middleware.
 
8
 
 
9
This module provides a middleware that implements protection
 
10
against request forgeries from other sites.
 
11
"""
 
12
 
 
13
import re
 
14
import itertools
 
15
try:
 
16
    from functools import wraps
 
17
except ImportError:
 
18
    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
 
19
 
 
20
from django.conf import settings
 
21
from django.http import HttpResponseForbidden
 
22
from django.utils.hashcompat import md5_constructor
 
23
from django.utils.safestring import mark_safe
 
24
from django.utils.translation import ugettext as _
 
25
 
 
26
_ERROR_MSG = mark_safe(_('<html xmlns="http://www.w3.org/1999/xhtml" '
 
27
                         'xml:lang="en"><body><h1>403 Forbidden</h1><p>'
 
28
                         'Cross Site Request Forgery detected. Request '
 
29
                         'aborted.</p></body></html>'))
 
30
 
 
31
_POST_FORM_RE = \
 
32
    re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)',
 
33
               re.IGNORECASE)
 
34
 
 
35
_HTML_TYPES = ('text/html', 'application/xhtml+xml')
 
36
 
 
37
 
 
38
def _make_token(session_id):
 
39
    return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
 
40
 
 
41
 
 
42
class CsrfViewMiddleware(object):
 
43
    """
 
44
    Middleware that requires a present and correct csrfmiddlewaretoken
 
45
    for POST requests that have an active session.
 
46
    """
 
47
    def process_view(self, request, callback, callback_args, callback_kwargs):
 
48
        if request.method == 'POST':
 
49
            if getattr(callback, 'csrf_exempt', False):
 
50
                return None
 
51
 
 
52
            if request.is_ajax():
 
53
                return None
 
54
 
 
55
            try:
 
56
                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
 
57
            except KeyError:
 
58
                # No session, no check required
 
59
                return None
 
60
 
 
61
            csrf_token = _make_token(session_id)
 
62
            # check incoming token
 
63
            try:
 
64
                request_csrf_token = request.POST['csrfmiddlewaretoken']
 
65
            except KeyError:
 
66
                return HttpResponseForbidden(_ERROR_MSG)
 
67
 
 
68
            if request_csrf_token != csrf_token:
 
69
                return HttpResponseForbidden(_ERROR_MSG)
 
70
 
 
71
        return None
 
72
 
 
73
 
 
74
class CsrfResponseMiddleware(object):
 
75
    """
 
76
    Middleware that post-processes a response to add a
 
77
    csrfmiddlewaretoken if the response/request have an active
 
78
    session.
 
79
    """
 
80
    def process_response(self, request, response):
 
81
        if getattr(response, 'csrf_exempt', False):
 
82
            return response
 
83
 
 
84
        csrf_token = None
 
85
        try:
 
86
            # This covers a corner case in which the outgoing response
 
87
            # both contains a form and sets a session cookie.  This
 
88
            # really should not be needed, since it is best if views
 
89
            # that create a new session (login pages) also do a
 
90
            # redirect, as is done by all such view functions in
 
91
            # Django.
 
92
            cookie = response.cookies[settings.SESSION_COOKIE_NAME]
 
93
            csrf_token = _make_token(cookie.value)
 
94
        except KeyError:
 
95
            # Normal case - look for existing session cookie
 
96
            try:
 
97
                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
 
98
                csrf_token = _make_token(session_id)
 
99
            except KeyError:
 
100
                # no incoming or outgoing cookie
 
101
                pass
 
102
 
 
103
        if csrf_token is not None and \
 
104
                response['Content-Type'].split(';')[0] in _HTML_TYPES:
 
105
 
 
106
            # ensure we don't add the 'id' attribute twice (HTML validity)
 
107
            idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
 
108
                                            itertools.repeat(''))
 
109
 
 
110
            def add_csrf_field(match):
 
111
                """Returns the matched <form> tag plus the added <input>
 
112
                element
 
113
                """
 
114
                return mark_safe(
 
115
                    match.group() + "<div style='display:none;'>" +  \
 
116
                    "<input type='hidden' " + idattributes.next() +  \
 
117
                    " name='csrfmiddlewaretoken' value='" + csrf_token + \
 
118
                    "' /></div>")
 
119
 
 
120
            # Modify any POST forms
 
121
            response.content = _POST_FORM_RE.sub(add_csrf_field,
 
122
                                                 response.content)
 
123
        return response
 
124
 
 
125
 
 
126
class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware):
 
127
    """Django middleware that adds protection against Cross Site
 
128
    Request Forgeries by adding hidden form fields to POST forms and
 
129
    checking requests for the correct value.
 
130
 
 
131
    In the list of middlewares, SessionMiddleware is required, and
 
132
    must come after this middleware.  CsrfMiddleWare must come after
 
133
    compression middleware.
 
134
 
 
135
    If a session ID cookie is present, it is hashed with the
 
136
    SECRET_KEY setting to create an authentication token.  This token
 
137
    is added to all outgoing POST forms and is expected on all
 
138
    incoming POST requests that have a session ID cookie.
 
139
 
 
140
    If you are setting cookies directly, instead of using Django's
 
141
    session framework, this middleware will not work.
 
142
 
 
143
    CsrfMiddleWare is composed of two middleware, CsrfViewMiddleware
 
144
    and CsrfResponseMiddleware which can be used independently.
 
145
    """
 
146
    pass
 
147
 
 
148
 
 
149
def csrf_response_exempt(view_func):
 
150
    """
 
151
    Modifies a view function so that its response is exempt
 
152
    from the post-processing of the CSRF middleware.
 
153
    """
 
154
    def wrapped_view(*args, **kwargs):
 
155
        resp = view_func(*args, **kwargs)
 
156
        resp.csrf_exempt = True
 
157
        return resp
 
158
    return wraps(view_func)(wrapped_view)
 
159
 
 
160
 
 
161
def csrf_view_exempt(view_func):
 
162
    """
 
163
    Marks a view function as being exempt from CSRF view protection.
 
164
    """
 
165
    # We could just do view_func.csrf_exempt = True, but decorators
 
166
    # are nicer if they don't have side-effects, so we return a new
 
167
    # function.
 
168
    def wrapped_view(*args, **kwargs):
 
169
        return view_func(*args, **kwargs)
 
170
    wrapped_view.csrf_exempt = True
 
171
    return wraps(view_func)(wrapped_view)
 
172
 
 
173
 
 
174
def csrf_exempt(view_func):
 
175
    """
 
176
    Marks a view function as being exempt from the CSRF checks
 
177
    and post processing.
 
178
 
 
179
    This is the same as using both the csrf_view_exempt and
 
180
    csrf_response_exempt decorators.
 
181
    """
 
182
    return csrf_response_exempt(csrf_view_exempt(view_func))