2006-12-06 04:51:25 +08:00
|
|
|
"""
|
|
|
|
Formtools Preview application.
|
|
|
|
"""
|
|
|
|
|
|
|
|
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
|
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
|
2011-03-31 01:35:01 +08:00
|
|
|
from django.contrib.formtools.utils import form_hmac
|
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:
|
2013-08-05 00:17:10 +08:00
|
|
|
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"
|
2010-11-21 21:47:38 +08:00
|
|
|
f = self.form(auto_id=self.get_auto_id(), initial=self.get_initial(request))
|
2006-12-30 03:42:56 +08:00
|
|
|
return render_to_response(self.form_template,
|
2010-11-21 21:47:38 +08:00
|
|
|
self.get_context(request, f),
|
2006-12-30 03:42:56 +08:00
|
|
|
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."
|
2010-11-21 21:47:38 +08:00
|
|
|
f = self.form(request.POST, auto_id=self.get_auto_id())
|
|
|
|
context = self.get_context(request, f)
|
2006-12-06 04:51:25 +08:00
|
|
|
if f.is_valid():
|
2010-11-21 21:47:38 +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)
|
2011-03-31 01:35:01 +08:00
|
|
|
return constant_time_compare(token, expected)
|
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
|
|
|
|
2006-12-06 04:51:25 +08:00
|
|
|
def post_post(self, request):
|
|
|
|
"Validates the POST data. If valid, calls done(). Else, redisplays form."
|
2010-11-21 21:47:38 +08:00
|
|
|
f = self.form(request.POST, auto_id=self.get_auto_id())
|
2006-12-06 04:51:25 +08:00
|
|
|
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,
|
2010-11-21 21:47:38 +08:00
|
|
|
self.get_context(request, f),
|
2006-12-30 03:42:56 +08:00
|
|
|
context_instance=RequestContext(request))
|
2006-12-06 04:51:25 +08:00
|
|
|
|
|
|
|
# METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
|
|
|
|
|
2010-11-21 21:47:38 +08:00
|
|
|
def get_auto_id(self):
|
|
|
|
"""
|
|
|
|
Hook to override the ``auto_id`` kwarg for the form. Needed when
|
|
|
|
rendering two form previews in the same template.
|
|
|
|
"""
|
|
|
|
return AUTO_ID
|
|
|
|
|
|
|
|
def get_initial(self, request):
|
|
|
|
"""
|
|
|
|
Takes a request argument and returns a dictionary to pass to the form's
|
|
|
|
``initial`` kwarg when the form is being created from an HTTP get.
|
|
|
|
"""
|
|
|
|
return {}
|
|
|
|
|
|
|
|
def get_context(self, request, form):
|
|
|
|
"Context for template rendering."
|
|
|
|
return {'form': form, 'stage_field': self.unused_name('stage'), 'state': self.state}
|
|
|
|
|
|
|
|
|
2006-12-06 04:51:25 +08:00
|
|
|
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.
|
|
|
|
"""
|
2011-03-31 01:35:01 +08:00
|
|
|
return form_hmac(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__)
|