Fixed #15075 - No longer possible to alter the form_list in FormWizard.process_step

Thanks to niels, stas for the report, and stas for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15196 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Luke Plant 2011-01-13 21:58:11 +00:00
parent 1222bd6f6f
commit a6b9dbc57c
2 changed files with 61 additions and 31 deletions

View File

@ -233,6 +233,8 @@ class WizardPageOneForm(forms.Form):
class WizardPageTwoForm(forms.Form): class WizardPageTwoForm(forms.Form):
field = forms.CharField() field = forms.CharField()
class WizardPageTwoAlternativeForm(forms.Form):
field = forms.CharField()
class WizardPageThreeForm(forms.Form): class WizardPageThreeForm(forms.Form):
field = forms.CharField() field = forms.CharField()
@ -351,7 +353,8 @@ class WizardTests(TestCase):
def test_14498(self): def test_14498(self):
""" """
Regression test for ticket #14498. Regression test for ticket #14498. All previous steps' forms should be
validated.
""" """
that = self that = self
@ -391,3 +394,26 @@ class WizardTests(TestCase):
"wizard_step": "1"} "wizard_step": "1"}
wizard(DummyRequest(POST=data)) wizard(DummyRequest(POST=data))
self.assertTrue(reached[0]) self.assertTrue(reached[0])
def test_15075(self):
"""
Regression test for ticket #15075. Allow modifying wizard's form_list
in process_step.
"""
that = self
class WizardWithProcessStep(WizardClass):
def process_step(self, request, form, step):
if step == 0:
self.form_list[1] = WizardPageTwoAlternativeForm
if step == 1:
that.assertTrue(isinstance(form, WizardPageTwoAlternativeForm))
wizard = WizardWithProcessStep([WizardPageOneForm,
WizardPageTwoForm,
WizardPageThreeForm])
data = {"0-field": "test",
"1-field": "test2",
"hash_0": "7e9cea465f6a10a6fb47fcea65cb9a76350c9a5c",
"wizard_step": "1"}
wizard(DummyRequest(POST=data))

View File

@ -90,6 +90,40 @@ class FormWizard(object):
if current_step >= self.num_steps(): if current_step >= self.num_steps():
raise Http404('Step %s does not exist' % current_step) raise Http404('Step %s does not exist' % current_step)
# Validate and process all the previous forms before instantiating the
# current step's form in case self.process_step makes changes to
# self.form_list.
# If any of them fails validation, that must mean the validator relied
# on some other input, such as an external Web site.
# It is also possible that alidation might fail under certain attack
# situations: an attacker might be able to bypass previous stages, and
# generate correct security hashes for all the skipped stages by virtue
# of:
# 1) having filled out an identical form which doesn't have the
# validation (and does something different at the end),
# 2) or having filled out a previous version of the same form which
# had some validation missing,
# 3) or previously having filled out the form when they had more
# privileges than they do now.
#
# Since the hashes only take into account values, and not other other
# validation the form might do, we must re-do validation now for
# security reasons.
previous_form_list = []
for i in range(current_step):
f = self.get_form(i, request.POST)
if not self._check_security_hash(request.POST.get("hash_%d" % i, ''),
request, f):
return self.render_hash_failure(request, i)
if not f.is_valid():
return self.render_revalidation_failure(request, i, f)
else:
self.process_step(request, f, i)
previous_form_list.append(f)
# Process the current step. If it's valid, go to the next step or call # Process the current step. If it's valid, go to the next step or call
# done(), depending on whether any steps remain. # done(), depending on whether any steps remain.
if request.method == 'POST': if request.method == 'POST':
@ -98,36 +132,6 @@ class FormWizard(object):
form = self.get_form(current_step) form = self.get_form(current_step)
if form.is_valid(): if form.is_valid():
# Validate all the forms. If any of them fail validation, that
# must mean the validator relied on some other input, such as
# an external Web site.
# It is also possible that validation might fail under certain
# attack situations: an attacker might be able to bypass previous
# stages, and generate correct security hashes for all the
# skipped stages by virtue of:
# 1) having filled out an identical form which doesn't have the
# validation (and does something different at the end),
# 2) or having filled out a previous version of the same form
# which had some validation missing,
# 3) or previously having filled out the form when they had
# more privileges than they do now.
#
# Since the hashes only take into account values, and not other
# other validation the form might do, we must re-do validation
# now for security reasons.
previous_form_list = [self.get_form(i, request.POST) for i in range(current_step)]
for i, f in enumerate(previous_form_list):
if not self._check_security_hash(request.POST.get("hash_%d" % i, ''), request, f):
return self.render_hash_failure(request, i)
if not f.is_valid():
return self.render_revalidation_failure(request, i, f)
else:
self.process_step(request, f, i)
# Now progress to processing this step:
self.process_step(request, form, current_step) self.process_step(request, form, current_step)
next_step = current_step + 1 next_step = current_step + 1