diff options
| author | Russell Keith-Magee <russell@keith-magee.com> | 2010-08-06 14:25:58 +0000 |
|---|---|---|
| committer | Russell Keith-Magee <russell@keith-magee.com> | 2010-08-06 14:25:58 +0000 |
| commit | d3ba8cb88b2f3939398c9c54e5aa5c6e7495e56f (patch) | |
| tree | e6cda6a74742fac01fb99c28ed7f9f4fc15048a9 | |
| parent | 69d1e71fad3046792f737b26c7da75e8f373eb44 (diff) | |
Fixed #13316 -- Modified the default behavior of PasswordInput to prevent reflecting passwords on form failure. Thanks to clouserw for the report.
Although this changes nothing at a functional level, this is BACKWARDS INCOMPATIBLE from a UX perspective for anyone that wants passwords to be reflected to the user on form failure. See the 1.3 release notes for details.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@13498 bcc190cf-cafb-0310-a4f2-bffc1f526a37
| -rw-r--r-- | django/forms/widgets.py | 2 | ||||
| -rw-r--r-- | docs/ref/forms/widgets.txt | 43 | ||||
| -rw-r--r-- | docs/releases/1.3.txt | 25 | ||||
| -rw-r--r-- | tests/regressiontests/forms/forms.py | 20 | ||||
| -rw-r--r-- | tests/regressiontests/forms/widgets.py | 33 |
5 files changed, 74 insertions, 49 deletions
diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 1480527d9d..7a812ecc84 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -229,7 +229,7 @@ class TextInput(Input): class PasswordInput(Input): input_type = 'password' - def __init__(self, attrs=None, render_value=True): + def __init__(self, attrs=None, render_value=False): super(PasswordInput, self).__init__(attrs) self.render_value = render_value diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 1fc2bfa85d..e82fae7761 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -6,7 +6,7 @@ Widgets .. module:: django.forms.widgets :synopsis: Django's built-in form widgets. - + .. currentmodule:: django.forms A widget is Django's representation of a HTML input element. The widget @@ -29,7 +29,12 @@ commonly used groups of widgets: .. attribute:: PasswordInput.render_value Determines whether the widget will have a value filled in when the - form is re-displayed after a validation error (default is ``True``). + form is re-displayed after a validation error (default is ``False``). + +.. versionchanged:: 1.3 + The default value for + :attr:`~PasswordInput.render_value` was + changed from ``True`` to ``False`` .. class:: HiddenInput @@ -50,7 +55,7 @@ commonly used groups of widgets: Date input as a simple text box: ``<input type='text' ...>`` Takes one optional argument: - + .. attribute:: DateInput.format The format in which this field's initial value will be displayed. @@ -64,11 +69,11 @@ commonly used groups of widgets: Date/time input as a simple text box: ``<input type='text' ...>`` Takes one optional argument: - + .. attribute:: DateTimeInput.format - + The format in which this field's initial value will be displayed. - + If no ``format`` argument is provided, the default format is ``'%Y-%m-%d %H:%M:%S'``. @@ -77,11 +82,11 @@ commonly used groups of widgets: Time input as a simple text box: ``<input type='text' ...>`` Takes one optional argument: - + .. attribute:: TimeInput.format - + The format in which this field's initial value will be displayed. - + If no ``format`` argument is provided, the default format is ``'%H:%M:%S'``. .. versionchanged:: 1.1 @@ -98,15 +103,15 @@ commonly used groups of widgets: Takes one optional argument: .. attribute:: CheckboxInput.check_test - - A callable that takes the value of the CheckBoxInput + + A callable that takes the value of the CheckBoxInput and returns ``True`` if the checkbox should be checked for - that value. + that value. .. class:: Select Select widget: ``<select><option ...>...</select>`` - + Requires that your field provides :attr:`~Field.choices`. .. class:: NullBooleanSelect @@ -123,22 +128,22 @@ commonly used groups of widgets: .. class:: RadioSelect A list of radio buttons: - + .. code-block:: html - + <ul> <li><input type='radio' ...></li> ... </ul> - + Requires that your field provides :attr:`~Field.choices`. .. class:: CheckboxSelectMultiple A list of checkboxes: - + .. code-block:: html - + <ul> <li><input type='checkbox' ...></li> ... @@ -155,7 +160,7 @@ commonly used groups of widgets: Takes two optional arguments, ``date_format`` and ``time_format``, which work just like the ``format`` argument for ``DateInput`` and ``TimeInput``. - + .. versionchanged:: 1.1 The ``date_format`` and ``time_format`` arguments were not supported in Django 1.0. diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index b0d0397055..d64d161aa4 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -18,6 +18,31 @@ fixes and an easy upgrade path from Django 1.2. Backwards-incompatible changes in 1.3 ===================================== +PasswordInput default rendering behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Prior to Django 1.3, a :class:`~django.forms.PasswordInput` would render +data values like any other form. If a form submission raised an error, +the password that was submitted would be reflected to the client as form +data populating the form for resubmission. + +This had the potential to leak passwords, as any failed password +attempt would cause the password that was typed to be sent back to the +client. + +In Django 1.3, the default behavior of +:class:`~django.forms.PasswordInput` is to suppress the display of +password values. This change doesn't alter the way form data is +validated or handled. It only affects the user experience with +passwords on a form when they make an error submitting form data (such +as on unsuccessful logins, or when completing a registration form). + +If you want restore the pre-Django 1.3 behavior, you need to pass in a +custom widget to your form that sets the ``render_value`` argument:: + + class LoginForm(forms.Form): + username = forms.CharField(max_length=100) + password = forms.PasswordField(widget=forms.PasswordInput(render_value=True)) Features deprecated in 1.3 diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index 58051fd133..85eced26ad 100644 --- a/tests/regressiontests/forms/forms.py +++ b/tests/regressiontests/forms/forms.py @@ -705,13 +705,13 @@ Form.clean() is required to return a dictionary of all clean data. >>> print f.as_table() <tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr> <tr><th>Username:</th><td><input type="text" name="username" value="adrian" maxlength="10" /></td></tr> -<tr><th>Password1:</th><td><input type="password" name="password1" value="foo" /></td></tr> -<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr> +<tr><th>Password1:</th><td><input type="password" name="password1" /></td></tr> +<tr><th>Password2:</th><td><input type="password" name="password2" /></td></tr> >>> print f.as_ul() <li><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></li> <li>Username: <input type="text" name="username" value="adrian" maxlength="10" /></li> -<li>Password1: <input type="password" name="password1" value="foo" /></li> -<li>Password2: <input type="password" name="password2" value="bar" /></li> +<li>Password1: <input type="password" name="password1" /></li> +<li>Password2: <input type="password" name="password2" /></li> >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) >>> f.errors {} @@ -1589,8 +1589,8 @@ Case 2: POST with erroneous data (a redisplayed form, with errors). <table> <tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr> <tr><th>Username:</th><td><ul class="errorlist"><li>Ensure this value has at most 10 characters (it has 23).</li></ul><input type="text" name="username" value="this-is-a-long-username" maxlength="10" /></td></tr> -<tr><th>Password1:</th><td><input type="password" name="password1" value="foo" /></td></tr> -<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr> +<tr><th>Password1:</th><td><input type="password" name="password1" /></td></tr> +<tr><th>Password2:</th><td><input type="password" name="password2" /></td></tr> </table> <input type="submit" /> </form> @@ -1719,8 +1719,8 @@ the list of errors is empty). You can also use it in {% if %} statements. >>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)})) <form action=""> <p><label>Your username: <input type="text" name="username" value="django" maxlength="10" /></label></p> -<p><label>Password: <input type="password" name="password1" value="foo" /></label></p> -<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p> +<p><label>Password: <input type="password" name="password1" /></label></p> +<p><label>Password (again): <input type="password" name="password2" /></label></p> <input type="submit" /> </form> >>> t = Template('''<form action=""> @@ -1734,8 +1734,8 @@ the list of errors is empty). You can also use it in {% if %} statements. <form action=""> <ul class="errorlist"><li>Please make sure your passwords match.</li></ul> <p><label>Your username: <input type="text" name="username" value="django" maxlength="10" /></label></p> -<p><label>Password: <input type="password" name="password1" value="foo" /></label></p> -<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p> +<p><label>Password: <input type="password" name="password1" /></label></p> +<p><label>Password (again): <input type="password" name="password2" /></label></p> <input type="submit" /> </form> diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py index 39d7d569a3..b8ec789649 100644 --- a/tests/regressiontests/forms/widgets.py +++ b/tests/regressiontests/forms/widgets.py @@ -62,6 +62,17 @@ u'<input type="text" class="special" name="email" />' u'<input type="password" name="email" />' >>> w.render('email', None) u'<input type="password" name="email" />' +>>> w.render('email', 'secret') +u'<input type="password" name="email" />' + +The render_value argument lets you specify whether the widget should render +its value. For security reasons, this is off by default. + +>>> w = PasswordInput(render_value=True) +>>> w.render('email', '') +u'<input type="password" name="email" />' +>>> w.render('email', None) +u'<input type="password" name="email" />' >>> w.render('email', 'test@example.com') u'<input type="password" name="email" value="test@example.com" />' >>> w.render('email', 'some "quoted" & ampersanded value') @@ -70,36 +81,20 @@ u'<input type="password" name="email" value="some "quoted" & amper u'<input type="password" name="email" value="test@example.com" class="fun" />' You can also pass 'attrs' to the constructor: ->>> w = PasswordInput(attrs={'class': 'fun'}) +>>> w = PasswordInput(attrs={'class': 'fun'}, render_value=True) >>> w.render('email', '') u'<input type="password" class="fun" name="email" />' >>> w.render('email', 'foo@example.com') u'<input type="password" class="fun" value="foo@example.com" name="email" />' 'attrs' passed to render() get precedence over those passed to the constructor: ->>> w = PasswordInput(attrs={'class': 'pretty'}) +>>> w = PasswordInput(attrs={'class': 'pretty'}, render_value=True) >>> w.render('email', '', attrs={'class': 'special'}) u'<input type="password" class="special" name="email" />' >>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}) u'<input type="password" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />' -The render_value argument lets you specify whether the widget should render -its value. You may want to do this for security reasons. ->>> w = PasswordInput(render_value=True) ->>> w.render('email', 'secret') -u'<input type="password" name="email" value="secret" />' ->>> w = PasswordInput(render_value=False) ->>> w.render('email', '') -u'<input type="password" name="email" />' ->>> w.render('email', None) -u'<input type="password" name="email" />' ->>> w.render('email', 'secret') -u'<input type="password" name="email" />' ->>> w = PasswordInput(attrs={'class': 'fun'}, render_value=False) ->>> w.render('email', 'secret') -u'<input type="password" class="fun" name="email" />' - # HiddenInput Widget ############################################################ >>> w = HiddenInput() @@ -1286,7 +1281,7 @@ class SelectAndTextWidget(forms.MultiWidget): forms.TextInput ] super(SelectAndTextWidget, self).__init__(widgets) - + def _set_choices(self, choices): """ When choices are set for this widget, we want to pass those along to the Select widget |
