From 922afc350b6c1cdfbe0b9f976a2dae01d723f616 Mon Sep 17 00:00:00 2001 From: Stefan Koegl Date: Tue, 13 Apr 2010 18:20:01 +0200 Subject: [PATCH] Django-magic to prevent cross-site request forgery for POST requests --- mygpo/publisher/templates/publisher/episode.html | 1 + mygpo/publisher/templates/publisher/home.html | 1 + mygpo/publisher/templates/publisher/podcast.html | 1 + mygpo/security.py | 35 ++++++++++++++++++++++++ mygpo/settings.py | 3 ++ mygpo/web/templates/account.html | 1 + mygpo/web/templates/csrf.html | 26 ++++++++++++++++++ mygpo/web/templates/delete_account.html | 1 + mygpo/web/templates/device.html | 3 ++ mygpo/web/templates/episode.html | 1 + mygpo/web/templates/episode_toplist.html | 2 ++ mygpo/web/templates/login.html | 2 ++ mygpo/web/templates/migrate.html | 1 + mygpo/web/templates/podcast.html | 1 + mygpo/web/templates/subscribe.html | 1 + mygpo/web/templates/toplist.html | 2 ++ mygpo/web/users.py | 28 +++++++++++++------ mygpo/web/views/__init__.py | 6 ++-- mygpo/web/views/settings.py | 4 +-- 19 files changed, 107 insertions(+), 13 deletions(-) create mode 100644 mygpo/security.py create mode 100644 mygpo/web/templates/csrf.html diff --git a/mygpo/publisher/templates/publisher/episode.html b/mygpo/publisher/templates/publisher/episode.html index fa97d61c..08169f7e 100644 --- a/mygpo/publisher/templates/publisher/episode.html +++ b/mygpo/publisher/templates/publisher/episode.html @@ -43,6 +43,7 @@ {% endif %}
+ {% csrf_token %} {{ form.as_table }}
diff --git a/mygpo/publisher/templates/publisher/home.html b/mygpo/publisher/templates/publisher/home.html index 02c71f43..c2ec8c9b 100644 --- a/mygpo/publisher/templates/publisher/home.html +++ b/mygpo/publisher/templates/publisher/home.html @@ -29,6 +29,7 @@ {% if user.is_staff %} + {% csrf_token %} {{ form.as_p }}
diff --git a/mygpo/publisher/templates/publisher/podcast.html b/mygpo/publisher/templates/publisher/podcast.html index e3435969..3d1dbef9 100644 --- a/mygpo/publisher/templates/publisher/podcast.html +++ b/mygpo/publisher/templates/publisher/podcast.html @@ -34,6 +34,7 @@

Edit

Please note that information updates from the podcast feed will overwrite any information given here. Please save only what is not contained in the feed.

+ {% csrf_token %} {{ form.as_table }}
diff --git a/mygpo/security.py b/mygpo/security.py new file mode 100644 index 00000000..c094dd25 --- /dev/null +++ b/mygpo/security.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# +# This file is part of my.gpodder.org. +# +# my.gpodder.org is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# my.gpodder.org is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public +# License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with my.gpodder.org. If not, see . +# + +from django.http import HttpResponse +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.contrib.sites.models import Site +from django.utils.translation import ugettext as _ + +def csrf_failure(request, reason=""): + site = Site.objects.get_current() + return render_to_response('csrf.html', { + 'site': site, + 'method': request.method, + 'referer': request.META.get('HTTP_REFERER', _('another site')), + 'path': request.path, + 'get': request.GET, + 'post': request.POST, + }, context_instance=RequestContext(request)) + diff --git a/mygpo/settings.py b/mygpo/settings.py index 690e4976..37440a1e 100644 --- a/mygpo/settings.py +++ b/mygpo/settings.py @@ -81,6 +81,7 @@ TEMPLATE_LOADERS = ( MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.locale.LocaleMiddleware', @@ -127,3 +128,5 @@ HAYSTACK_SITECONF = 'mygpo.search_sites' HAYSTACK_SEARCH_ENGINE = 'whoosh' HAYSTACK_WHOOSH_PATH = os.path.abspath('%s/../search_index' % os.path.dirname(__file__)) +CSRF_FAILURE_VIEW='mygpo.security.csrf_failure' + diff --git a/mygpo/web/templates/account.html b/mygpo/web/templates/account.html index 56e4c2d1..ecb5b370 100644 --- a/mygpo/web/templates/account.html +++ b/mygpo/web/templates/account.html @@ -14,6 +14,7 @@
{% trans "Your settings have been saved." %}
{% endif %} + {% csrf_token %}

{% trans "Contact information and password" %}

diff --git a/mygpo/web/templates/csrf.html b/mygpo/web/templates/csrf.html new file mode 100644 index 00000000..3f67411c --- /dev/null +++ b/mygpo/web/templates/csrf.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% load i18n %} + +{% load menu %} +{% block mainmenu %}{{ "/"|main_menu }}{% endblock %} +{% block sectionmenu %}{{ "/"|section_menu }}{% endblock %} + +{% block title %}{% trans "Attention!" %}{% endblock %} + +{% block content %} +

{% trans "Attention, Attention!" %}

+ +

{% blocktrans %}It seems that {{ referer }} has linked to {{ site }} to get you to change some data. This will possibly change some of your data / settings!. Do you really want to continue? If in doubt, chose No. +{% endblocktrans %}

+ + + {% csrf_token %} + {% for key, value in post.iteritems %} + + {% endfor %} + + {% trans "No" %} + + +{% endblock %} + diff --git a/mygpo/web/templates/delete_account.html b/mygpo/web/templates/delete_account.html index b5af0f6e..ab3fee13 100644 --- a/mygpo/web/templates/delete_account.html +++ b/mygpo/web/templates/delete_account.html @@ -23,6 +23,7 @@

{% trans "Delete your Account?" %}

{% trans "Do you really want to delete your account?" %}

+ {% csrf_token %} diff --git a/mygpo/web/templates/device.html b/mygpo/web/templates/device.html index 05ec2c16..57d4a075 100644 --- a/mygpo/web/templates/device.html +++ b/mygpo/web/templates/device.html @@ -69,6 +69,7 @@ {% if has_sync_targets %} + {% csrf_token %} {{ sync_form.as_p }} @@ -80,12 +81,14 @@ {% endif %} + {% csrf_token %} {{ device_form.as_p }}

{% trans "Delete" %}

+ {% csrf_token %}
diff --git a/mygpo/web/templates/episode.html b/mygpo/web/templates/episode.html index 30a09ef8..28236209 100644 --- a/mygpo/web/templates/episode.html +++ b/mygpo/web/templates/episode.html @@ -76,6 +76,7 @@ {% endfor %} + {% csrf_token %}
- diff --git a/mygpo/web/templates/episode_toplist.html b/mygpo/web/templates/episode_toplist.html index bb9ae2b8..49b945a3 100644 --- a/mygpo/web/templates/episode_toplist.html +++ b/mygpo/web/templates/episode_toplist.html @@ -49,6 +49,7 @@ {% if languages|length_is:"0" %}

Currently showing all languages. Show a specific one: + {% csrf_token %} {% for key, name in all_languages.iteritems %} {% if not key in languages %} diff --git a/mygpo/web/templates/login.html b/mygpo/web/templates/login.html index 2d922de9..31a05cc6 100644 --- a/mygpo/web/templates/login.html +++ b/mygpo/web/templates/login.html @@ -10,6 +10,7 @@ {% block content %} {% if restore_password_form %} + {% csrf_token %}
@@ -52,6 +53,7 @@ {% endif %} + {% csrf_token %}
diff --git a/mygpo/web/templates/migrate.html b/mygpo/web/templates/migrate.html index 474dc546..ab463d90 100644 --- a/mygpo/web/templates/migrate.html +++ b/mygpo/web/templates/migrate.html @@ -8,6 +8,7 @@

{% blocktrans with username|striptags as user_name %}The new {{ url }} uses usernames instead of email addresses. Based on your email address, we think you might like {{user_name}}, but you can change that if you want to.{% endblocktrans %}

+ {% csrf_token %} Username:

diff --git a/mygpo/web/templates/podcast.html b/mygpo/web/templates/podcast.html index e94e77e0..65a5cbec 100644 --- a/mygpo/web/templates/podcast.html +++ b/mygpo/web/templates/podcast.html @@ -43,6 +43,7 @@ {% endif %} + {% csrf_token %}

{{ privacy_form.public }} {{ privacy_form.public.label_tag }}

diff --git a/mygpo/web/templates/subscribe.html b/mygpo/web/templates/subscribe.html index bc252f80..bf480909 100644 --- a/mygpo/web/templates/subscribe.html +++ b/mygpo/web/templates/subscribe.html @@ -29,6 +29,7 @@

{% trans "Add a podcast" %}

{% if can_subscribe %} + {% csrf_token %} {{ form.as_p }} diff --git a/mygpo/web/templates/toplist.html b/mygpo/web/templates/toplist.html index a43c821d..077c437b 100644 --- a/mygpo/web/templates/toplist.html +++ b/mygpo/web/templates/toplist.html @@ -56,6 +56,7 @@ {% if languages|length_is:"0" %}

Currently showing all languages. Show a specific one:
+ {% csrf_token %} {% for key, name in all_languages.iteritems %} {% if not key in languages %} diff --git a/mygpo/web/users.py b/mygpo/web/users.py index db6d15e7..1af02cec 100644 --- a/mygpo/web/users.py +++ b/mygpo/web/users.py @@ -21,6 +21,7 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from django.template.defaultfilters import slugify +from django.template import RequestContext from registration.forms import RegistrationForm from registration.views import activate, register from registration.models import RegistrationProfile @@ -49,7 +50,7 @@ def login_user(request): 'url': Site.objects.get_current(), 'next': request.GET.get('next', ''), 'restore_password_form': form, - }) + }, context_instance=RequestContext(request)) username = request.POST['user'] password = request.POST['pwd'] @@ -59,13 +60,13 @@ def login_user(request): return render_to_response('login.html', { 'error_message': _('Wrong username or password.'), 'next': request.POST.get('next', ''), - }) + }, context_instance=RequestContext(request)) if not user.is_active: return render_to_response('login.html', { 'error_message': _('Please activate your account first.'), 'activation_needed': True, - }) + }, context_instance=RequestContext(request)) login(request, user) current_site = Site.objects.get_current() @@ -75,7 +76,8 @@ def login_user(request): return render_to_response('migrate.html', { 'url': current_site, 'username': user - }) + }, context_instance=RequestContext(request)) + except UserProfile.DoesNotExist: profile, c = UserProfile.objects.get_or_create(user=user) @@ -95,9 +97,19 @@ def migrate_user(request): if user.username != username: current_site = Site.objects.get_current() if User.objects.filter(username__exact=username).count() > 0: - return render_to_response('migrate.html', {'error_message': '%s is already taken' % username, 'url': current_site, 'username': user.username}) + return render_to_response('migrate.html', { + 'error_message': '%s is already taken' % username, + 'url': current_site, + 'username': user.username + }, context_instance=RequestContext(request)) + if slugify(username) != username.lower(): - return render_to_response('migrate.html', {'error_message': '%s is not a valid username. Please use characters, numbers, underscore and dash only.' % username, 'url': current_site, 'username': user.username}) + return render_to_response('migrate.html', { + 'error_message': '%s is not a valid username. Please use characters, numbers, underscore and dash only.' % username, + 'url': current_site, + 'username': user.username + }, context_instance=RequestContext(request)) + else: user.username = username user.save() @@ -131,7 +143,7 @@ def restore_password(request): error_message = _('User does not exist.') return render_to_response('password_reset_failed.html', { 'error_message': error_message - }) + }, context_instance=RequestContext(request)) site = Site.objects.get_current() pwd = "".join(random.sample(string.letters+string.digits, 8)) @@ -140,5 +152,5 @@ def restore_password(request): user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) user.set_password(pwd) user.save() - return render_to_response('password_reset.html') + return render_to_response('password_reset.html', context_instance=RequestContext(request)) diff --git a/mygpo/web/views/__init__.py b/mygpo/web/views/__init__.py index c994afdd..b4768b0c 100644 --- a/mygpo/web/views/__init__.py +++ b/mygpo/web/views/__init__.py @@ -461,7 +461,7 @@ def resend_activation(request): form = ResendActivationForm() return render_to_response('registration/resend_activation.html', { 'form': form, - }) + }, context_instance=RequestContext(request)) site = Site.objects.get_current() form = ResendActivationForm(request.POST) @@ -490,7 +490,7 @@ def resend_activation(request): return render_to_response('registration/resend_activation.html', { 'form': form, 'error_message' : e - }) + }, context_instance=RequestContext(request)) try: @@ -500,7 +500,7 @@ def resend_activation(request): #old versions of django-registration send registration mails from RegistrationManager RegistrationProfile.objects.send_activation_email(profile, site) - return render_to_response('registration/resent_activation.html') + return render_to_response('registration/resent_activation.html', context_instance=RequestContext(request)) @requires_token(object='subscriptions', action='r', denied_template='user_subscriptions_denied.html') diff --git a/mygpo/web/views/settings.py b/mygpo/web/views/settings.py index 8985c32e..87498639 100644 --- a/mygpo/web/views/settings.py +++ b/mygpo/web/views/settings.py @@ -82,14 +82,14 @@ def account(request): def delete_account(request): if request.method == 'GET': - return render_to_response('delete_account.html') + return render_to_response('delete_account.html', context_instance=RequestContext(request)) request.user.is_active = False request.user.save() logout(request) return render_to_response('delete_account.html', { 'success': True - }) + }, context_instance=RequestContext(request)) @login_required -- 2.11.4.GIT