Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

[1.2.X] Fixed #15075 - No longer possible to alter the form_list in F…

…ormWizard.process_step

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

Backport of [15196] from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@15197 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 444bda0be0926b0a9c1bdd03a8adb3a24a4be43a 1 parent 924bad8
@spookylukey spookylukey authored
Showing with 61 additions and 31 deletions.
  1. +28 −1 django/contrib/formtools/tests.py
  2. +33 −30 django/contrib/formtools/wizard.py
View
29 django/contrib/formtools/tests.py
@@ -146,6 +146,9 @@ class WizardPageOneForm(forms.Form):
class WizardPageTwoForm(forms.Form):
field = forms.CharField()
+class WizardPageTwoAlternativeForm(forms.Form):
+ field = forms.CharField()
+
class WizardPageThreeForm(forms.Form):
field = forms.CharField()
@@ -194,7 +197,8 @@ def test_step_increments(self):
def test_14498(self):
"""
- Regression test for ticket #14498.
+ Regression test for ticket #14498. All previous steps' forms should be
+ validated.
"""
that = self
reached = [False]
@@ -237,3 +241,26 @@ def done(self, request, form_list):
"wizard_step": "1"}
wizard(DummyRequest(POST=data))
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": "2fdbefd4c0cad51509478fbacddf8b13",
+ "wizard_step": "1"}
+ wizard(DummyRequest(POST=data))
View
63 django/contrib/formtools/wizard.py
@@ -68,6 +68,39 @@ def __call__(self, request, *args, **kwargs):
if current_step >= self.num_steps():
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 request.POST.get("hash_%d" % i, '') != self.security_hash(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
# done(), depending on whether any steps remain.
if request.method == 'POST':
@@ -76,36 +109,6 @@ def __call__(self, request, *args, **kwargs):
form = self.get_form(current_step)
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 request.POST.get("hash_%d" % i, '') != self.security_hash(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)
next_step = current_step + 1
Please sign in to comment.
Something went wrong with that request. Please try again.