2006-12-06 04:51:25 +08:00
|
|
|
"""
|
|
|
|
Formtools Preview application.
|
|
|
|
"""
|
|
|
|
|
2008-08-02 13:56:57 +08:00
|
|
|
import cPickle as pickle
|
|
|
|
|
2006-12-06 04:51:25 +08:00
|
|
|
from django.conf import settings
|
|
|
|
from django.http import Http404
|
|
|
|
from django.shortcuts import render_to_response
|
2006-12-30 08:06:28 +08:00
|
|
|
from django.template.context import RequestContext
|
2008-08-02 13:56:57 +08:00
|
|
|
from django.utils.hashcompat import md5_constructor
|
Fixed #14445 - Use HMAC and constant-time comparison functions where needed.
All adhoc MAC applications have been updated to use HMAC, using SHA1 to
generate unique keys for each application based on the SECRET_KEY, which is
common practice for this situation. In all cases, backwards compatibility
with existing hashes has been maintained, aiming to phase this out as per
the normal deprecation process. In this way, under most normal
circumstances the old hashes will have expired (e.g. by session expiration
etc.) before they become invalid.
In the case of the messages framework and the cookie backend, which was
already using HMAC, there is the possibility of a backwards incompatibility
if the SECRET_KEY is shorter than the default 50 bytes, but the low
likelihood and low impact meant compatibility code was not worth it.
All known instances where tokens/hashes were compared using simple string
equality, which could potentially open timing based attacks, have also been
fixed using a constant-time comparison function.
There are no known practical attacks against the existing implementations,
so these security improvements will not be backported.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@14218 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2010-10-15 04:54:30 +08:00
|
|
|
from django.utils.crypto import constant_time_compare
|
2008-08-27 04:19:12 +08:00
|
|
|
from django.contrib.formtools.utils import security_hash
|
2006-12-06 04:51:25 +08:00
|
|
|
|
|
|
|
AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
|
|
|
|
|
|
|
|
class FormPreview(object):
|
|
|
|
preview_template = 'formtools/preview.html'
|
|
|
|
form_template = 'formtools/form.html'
|
|
|
|
|
|
|
|
# METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
|
|
|
|
|
|
|
|
def __init__(self, form):
|
|
|
|
# form should be a Form class, not an instance.
|
|
|
|
self.form, self.state = form, {}
|
|
|
|
|
|
|
|
def __call__(self, request, *args, **kwargs):
|
|
|
|
stage = {'1': 'preview', '2': 'post'}.get(request.POST.get(self.unused_name('stage')), 'preview')
|
|
|
|
self.parse_params(*args, **kwargs)
|
|
|
|
try:
|
|
|
|
method = getattr(self, stage + '_' + request.method.lower())
|
|
|
|
except AttributeError:
|
|
|
|
raise Http404
|
|
|
|
return method(request)
|
|
|
|
|
|
|
|
def unused_name(self, name):
|
|
|
|
"""
|
|
|
|
Given a first-choice name, adds an underscore to the name until it
|
|
|
|
reaches a name that isn't claimed by any field in the form.
|
|
|
|
|
|
|
|
This is calculated rather than being hard-coded so that no field names
|
|
|
|
are off-limits for use in the form.
|
|
|
|
"""
|
|
|
|
while 1:
|
|
|
|
try:
|
2007-05-20 02:54:35 +08:00
|
|
|
f = self.form.base_fields[name]
|
2006-12-06 04:51:25 +08:00
|
|
|
except KeyError:
|
|
|
|
break # This field name isn't being used by the form.
|
|
|
|
name += '_'
|
|
|
|
return name
|
|
|
|
|
|
|
|
def preview_get(self, request):
|
|
|
|
"Displays the form"
|
|
|
|
f = self.form(auto_id=AUTO_ID)
|
2006-12-30 03:42:56 +08:00
|
|
|
return render_to_response(self.form_template,
|
|
|
|
{'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
|
|
|
|
context_instance=RequestContext(request))
|
2006-12-06 04:51:25 +08:00
|
|
|
|
|
|
|
def preview_post(self, request):
|
|
|
|
"Validates the POST data. If valid, displays the preview page. Else, redisplays form."
|
|
|
|
f = self.form(request.POST, auto_id=AUTO_ID)
|
|
|
|
context = {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}
|
|
|
|
if f.is_valid():
|
2010-02-22 13:00:36 +08:00
|
|
|
self.process_preview(request, f, context)
|
2006-12-06 04:51:25 +08:00
|
|
|
context['hash_field'] = self.unused_name('hash')
|
|
|
|
context['hash_value'] = self.security_hash(request, f)
|
2006-12-30 03:42:56 +08:00
|
|
|
return render_to_response(self.preview_template, context, context_instance=RequestContext(request))
|
2006-12-06 04:51:25 +08:00
|
|
|
else:
|
2006-12-30 03:42:56 +08:00
|
|
|
return render_to_response(self.form_template, context, context_instance=RequestContext(request))
|
2006-12-06 04:51:25 +08:00
|
|
|
|
Fixed #14445 - Use HMAC and constant-time comparison functions where needed.
All adhoc MAC applications have been updated to use HMAC, using SHA1 to
generate unique keys for each application based on the SECRET_KEY, which is
common practice for this situation. In all cases, backwards compatibility
with existing hashes has been maintained, aiming to phase this out as per
the normal deprecation process. In this way, under most normal
circumstances the old hashes will have expired (e.g. by session expiration
etc.) before they become invalid.
In the case of the messages framework and the cookie backend, which was
already using HMAC, there is the possibility of a backwards incompatibility
if the SECRET_KEY is shorter than the default 50 bytes, but the low
likelihood and low impact meant compatibility code was not worth it.
All known instances where tokens/hashes were compared using simple string
equality, which could potentially open timing based attacks, have also been
fixed using a constant-time comparison function.
There are no known practical attacks against the existing implementations,
so these security improvements will not be backported.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@14218 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2010-10-15 04:54:30 +08:00
|
|
|
def _check_security_hash(self, token, request, form):
|
|
|
|
expected = self.security_hash(request, form)
|
|
|
|
if constant_time_compare(token, expected):
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
# Fall back to Django 1.2 method, for compatibility with forms that
|
|
|
|
# are in the middle of being used when the upgrade occurs. However,
|
|
|
|
# we don't want to do this fallback if a subclass has provided their
|
|
|
|
# own security_hash method - because they might have implemented a
|
|
|
|
# more secure method, and this would punch a hole in that.
|
|
|
|
|
|
|
|
# PendingDeprecationWarning <- left here to remind us that this
|
|
|
|
# compatibility fallback should be removed in Django 1.5
|
|
|
|
FormPreview_expected = FormPreview.security_hash(self, request, form)
|
|
|
|
if expected == FormPreview_expected:
|
|
|
|
# They didn't override security_hash, do the fallback:
|
|
|
|
old_expected = security_hash(request, form)
|
|
|
|
return constant_time_compare(token, old_expected)
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2006-12-06 04:51:25 +08:00
|
|
|
def post_post(self, request):
|
|
|
|
"Validates the POST data. If valid, calls done(). Else, redisplays form."
|
|
|
|
f = self.form(request.POST, auto_id=AUTO_ID)
|
|
|
|
if f.is_valid():
|
Fixed #14445 - Use HMAC and constant-time comparison functions where needed.
All adhoc MAC applications have been updated to use HMAC, using SHA1 to
generate unique keys for each application based on the SECRET_KEY, which is
common practice for this situation. In all cases, backwards compatibility
with existing hashes has been maintained, aiming to phase this out as per
the normal deprecation process. In this way, under most normal
circumstances the old hashes will have expired (e.g. by session expiration
etc.) before they become invalid.
In the case of the messages framework and the cookie backend, which was
already using HMAC, there is the possibility of a backwards incompatibility
if the SECRET_KEY is shorter than the default 50 bytes, but the low
likelihood and low impact meant compatibility code was not worth it.
All known instances where tokens/hashes were compared using simple string
equality, which could potentially open timing based attacks, have also been
fixed using a constant-time comparison function.
There are no known practical attacks against the existing implementations,
so these security improvements will not be backported.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@14218 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2010-10-15 04:54:30 +08:00
|
|
|
if not self._check_security_hash(request.POST.get(self.unused_name('hash'), ''),
|
|
|
|
request, f):
|
2006-12-06 04:51:25 +08:00
|
|
|
return self.failed_hash(request) # Security hash failed.
|
2007-05-15 00:24:51 +08:00
|
|
|
return self.done(request, f.cleaned_data)
|
2006-12-06 04:51:25 +08:00
|
|
|
else:
|
2006-12-30 03:42:56 +08:00
|
|
|
return render_to_response(self.form_template,
|
|
|
|
{'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
|
|
|
|
context_instance=RequestContext(request))
|
2006-12-06 04:51:25 +08:00
|
|
|
|
|
|
|
# METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
|
|
|
|
|
|
|
|
def parse_params(self, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Given captured args and kwargs from the URLconf, saves something in
|
|
|
|
self.state and/or raises Http404 if necessary.
|
|
|
|
|
|
|
|
For example, this URLconf captures a user_id variable:
|
|
|
|
|
|
|
|
(r'^contact/(?P<user_id>\d{1,6})/$', MyFormPreview(MyForm)),
|
|
|
|
|
|
|
|
In this case, the kwargs variable in parse_params would be
|
|
|
|
{'user_id': 32} for a request to '/contact/32/'. You can use that
|
|
|
|
user_id to make sure it's a valid user and/or save it for later, for
|
|
|
|
use in done().
|
|
|
|
"""
|
|
|
|
pass
|
|
|
|
|
2010-02-22 13:00:36 +08:00
|
|
|
def process_preview(self, request, form, context):
|
|
|
|
"""
|
2010-02-27 13:38:17 +08:00
|
|
|
Given a validated form, performs any extra processing before displaying
|
|
|
|
the preview page, and saves any extra data in context.
|
|
|
|
"""
|
|
|
|
pass
|
2010-02-22 13:00:36 +08:00
|
|
|
|
2006-12-06 04:51:25 +08:00
|
|
|
def security_hash(self, request, form):
|
|
|
|
"""
|
2008-08-27 04:19:12 +08:00
|
|
|
Calculates the security hash for the given HttpRequest and Form instances.
|
2006-12-06 04:51:25 +08:00
|
|
|
|
2008-08-27 04:19:12 +08:00
|
|
|
Subclasses may want to take into account request-specific information,
|
2006-12-06 04:51:25 +08:00
|
|
|
such as the IP address.
|
|
|
|
"""
|
2008-08-27 04:19:12 +08:00
|
|
|
return security_hash(request, form)
|
2006-12-06 04:51:25 +08:00
|
|
|
|
|
|
|
def failed_hash(self, request):
|
|
|
|
"Returns an HttpResponse in the case of an invalid security hash."
|
|
|
|
return self.preview_post(request)
|
|
|
|
|
|
|
|
# METHODS SUBCLASSES MUST OVERRIDE ########################################
|
|
|
|
|
2007-05-15 00:24:51 +08:00
|
|
|
def done(self, request, cleaned_data):
|
|
|
|
"""
|
|
|
|
Does something with the cleaned_data and returns an
|
|
|
|
HttpResponseRedirect.
|
|
|
|
"""
|
2006-12-06 04:51:25 +08:00
|
|
|
raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__)
|