Django

Code

Ticket #510: csrf-protection2.patch

File csrf-protection2.patch, 7.5 kB (added by Simon Willison, 3 years ago)

Improved patch; now uses module methods for token checking and creation.

  • models/auth.py

    old new  
    222222    def _module_log_action(user_id, content_type_id, object_id, object_repr, action_flag, change_message=''): 
    223223        e = LogEntry(None, None, user_id, content_type_id, object_id, object_repr[:200], action_flag, change_message) 
    224224        e.save() 
     225 
     226class CsrfToken(meta.Model): 
     227    token = meta.CharField(maxlength=200) 
     228    issued = meta.DateTimeField(auto_now=True) 
     229    user = meta.ForeignKey(User) 
     230    class META: 
     231        module_name = 'csrf' 
     232        verbose_name_plural = 'CSRF tokens' 
     233        db_table = 'auth_csrf_tokens' 
     234        ordering = ('-issued',) 
     235 
     236    def __repr__(self): 
     237        return self.token 
     238 
     239    def _module_create_token(user_id): 
     240        import md5, random 
     241        token = md5.new(str(random.random())).hexdigest() 
     242        ct = CsrfToken(None, token,  
     243            datetime.datetime.now(), user_id) 
     244        ct.save() 
     245        return ct 
     246 
     247    def _module_check_token(token, user_id): 
     248        try: 
     249            token = get_object(token__exact = token, 
     250                select = {'user_id': user_id}) 
     251        except CsrfTokenDoesNotExist: 
     252            return False 
     253        token.delete() 
     254        return True 
  • conf/admin_templates/delete_confirmation_generic.html

    old new  
    22 
    33{% block content %} 
    44 
     5{% if csrf_failed %}<p class="errornote">CSRF protection: Please re-submit.</p>{% endif %} 
     6 
    57{% if perms_lacking %} 
    68    <p>Deleting the {{ object_name }} "{{ object }}" would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:</p> 
    79    <ul> 
     
    1517    <form action="" method="post"> 
    1618    <input type="hidden" name="post" value="yes" /> 
    1719    <input type="submit" value="Yes, I'm sure" /> 
     20    {% load csrf %}{% csrf_token %} 
    1821    </form> 
    1922{% endif %} 
    2023 
  • templatetags/csrf.py

    old new  
     1from django.core import template 
     2from django.models.auth import csrf 
     3import random, md5, datetime 
     4 
     5class CsrfTokenNode(template.Node): 
     6    def render(self, context): 
     7        token = csrf.create_token(context['user'].id) 
     8        return '<input type="hidden" name="csrf_token" value="%s">' % token.token 
     9 
     10def csrf_token(parser, token): 
     11    """ 
     12    {% csrf_token %} 
     13    """ 
     14    return CsrfTokenNode() 
     15 
     16template.register_tag('csrf_token', csrf_token) 
  • views/admin/main.py

    old new  
    33from django.core import formfields, meta, template_loader 
    44from django.core.exceptions import Http404, ObjectDoesNotExist, PermissionDenied 
    55from django.core.extensions import DjangoContext as Context 
    6 from django.models.auth import log 
     6from django.models.auth import log, csrf 
    77from django.utils.html import strip_tags 
    88from django.utils.httpwrappers import HttpResponse, HttpResponseRedirect 
    99from django.utils.text import capfirst, get_text_list 
     
    588588    if opts.admin.save_on_top: 
    589589        t.extend(_get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects)) 
    590590    t.append('{% if form.error_dict %}<p class="errornote">Please correct the error{{ form.error_dict.items|pluralize }} below.</p>{% endif %}\n') 
     591    t.append('{% if csrf_failed %}<p class="errornote">CSRF protection: Please re-submit.</p>{% endif %}\n') 
    591592    for fieldset_name, options in admin_field_objs: 
    592593        t.append('<fieldset class="module aligned %s">\n\n' % options.get('classes', '')) 
    593594        if fieldset_name: 
     
    687688        t.append('<li id="p{%% firstof %(x)s %%}"><span id="handlep{%% firstof %(x)s %%}">{{ object|truncatewords:"5" }}</span></li>' % \ 
    688689            {'x': ' '.join(['object.%s' % o.pk.name for o in ordered_objects])}) 
    689690        t.append('{% endfor %}</ul>{% endif %}\n') 
     691    t.append('{% load csrf %}{% csrf_token %}\n') 
    690692    t.append('</form>\n</div>\n{% endblock %}') 
    691693    return ''.join(t) 
    692694 
     
    764766    if not request.user.has_perm(app_label + '.' + opts.get_add_permission()): 
    765767        raise PermissionDenied 
    766768    manipulator = mod.AddManipulator() 
     769    csrf_failed = False 
    767770    if request.POST: 
    768771        new_data = request.POST.copy() 
    769772        if opts.has_field_type(meta.FileField): 
    770773            new_data.update(request.FILES) 
    771774        errors = manipulator.get_validation_errors(new_data) 
    772         if not errors and not request.POST.has_key("_preview"): 
     775        # Check the csrf_token 
     776        csrf_failed = not csrf.check_token(request.POST.get('csrf_token', ''), request.user.id) 
     777        if not errors and not csrf_failed and not request.POST.has_key("_preview"): 
    773778            for f in opts.many_to_many: 
    774779                if f.rel.raw_id_admin: 
    775780                    new_data.setlist(f.name, new_data[f.name].split(",")) 
     
    844849        'title': 'Add %s' % opts.verbose_name, 
    845850        "form": form, 
    846851        "is_popup": request.REQUEST.has_key("_popup"), 
     852        "csrf_failed": csrf_failed, 
    847853    }) 
    848854    if object_id_override is not None: 
    849855        c['object_id'] = object_id_override 
     
    864870        raise Http404 
    865871 
    866872    inline_related_objects = opts.get_inline_related_objects() 
     873    csrf_failed = False 
    867874    if request.POST: 
    868875        new_data = request.POST.copy() 
    869876        if opts.has_field_type(meta.FileField): 
    870877            new_data.update(request.FILES) 
    871878 
    872879        errors = manipulator.get_validation_errors(new_data) 
    873         if not errors and not request.POST.has_key("_preview"): 
     880        # Check the csrf_token 
     881        csrf_failed = not csrf.check_token(request.POST.get('csrf_token', ''), request.user.id) 
     882        if not errors and not csrf_failed and not request.POST.has_key("_preview"): 
    874883            for f in opts.many_to_many: 
    875884                if f.rel.raw_id_admin: 
    876885                    new_data.setlist(f.name, new_data[f.name].split(",")) 
     
    969978        "form": form, 
    970979        'object_id': object_id, 
    971980        'original': manipulator.original_object, 
    972         'is_popup' : request.REQUEST.has_key('_popup'), 
     981        'is_popup': request.REQUEST.has_key('_popup'), 
     982        'csrf_failed': csrf_failed, 
    973983    }) 
    974984    raw_template = _get_template(opts, app_label, change=True) 
    975985#     return HttpResponse(raw_template, mimetype='text/plain') 
     
    10731083    deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, strip_tags(repr(obj))), []] 
    10741084    perms_needed = sets.Set() 
    10751085    _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1) 
    1076  
    1077     if request.POST: # The user has already confirmed the deletion. 
     1086    csrf_failed = False 
     1087    if request.POST: 
     1088        csrf_failed = not csrf.check_token(request.POST.get('csrf_token', ''), request.user.id) 
     1089    if request.POST and not csrf_failed: # The user has already confirmed the deletion. 
    10781090        if perms_needed: 
    10791091            raise PermissionDenied 
    10801092        obj_repr = repr(obj) 
     
    10891101        "object": obj, 
    10901102        "deleted_objects": deleted_objects, 
    10911103        "perms_lacking": perms_needed, 
     1104        "csrf_failed": csrf_failed, 
    10921105    }) 
    10931106    return HttpResponse(t.render(c), mimetype='text/html; charset=utf-8') 
    10941107