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
This commit is contained in:
Russell Keith-Magee 2010-08-06 14:25:58 +00:00
parent 69d1e71fad
commit d3ba8cb88b
5 changed files with 74 additions and 49 deletions

View File

@ -229,7 +229,7 @@ class TextInput(Input):
class PasswordInput(Input): class PasswordInput(Input):
input_type = 'password' input_type = 'password'
def __init__(self, attrs=None, render_value=True): def __init__(self, attrs=None, render_value=False):
super(PasswordInput, self).__init__(attrs) super(PasswordInput, self).__init__(attrs)
self.render_value = render_value self.render_value = render_value

View File

@ -6,7 +6,7 @@ Widgets
.. module:: django.forms.widgets .. module:: django.forms.widgets
:synopsis: Django's built-in form widgets. :synopsis: Django's built-in form widgets.
.. currentmodule:: django.forms .. currentmodule:: django.forms
A widget is Django's representation of a HTML input element. The widget 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 .. attribute:: PasswordInput.render_value
Determines whether the widget will have a value filled in when the 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 .. class:: HiddenInput
@ -50,7 +55,7 @@ commonly used groups of widgets:
Date input as a simple text box: ``<input type='text' ...>`` Date input as a simple text box: ``<input type='text' ...>``
Takes one optional argument: Takes one optional argument:
.. attribute:: DateInput.format .. attribute:: DateInput.format
The format in which this field's initial value will be displayed. 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' ...>`` Date/time input as a simple text box: ``<input type='text' ...>``
Takes one optional argument: Takes one optional argument:
.. attribute:: DateTimeInput.format .. attribute:: DateTimeInput.format
The format in which this field's initial value will be displayed. 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 If no ``format`` argument is provided, the default format is ``'%Y-%m-%d
%H:%M:%S'``. %H:%M:%S'``.
@ -77,11 +82,11 @@ commonly used groups of widgets:
Time input as a simple text box: ``<input type='text' ...>`` Time input as a simple text box: ``<input type='text' ...>``
Takes one optional argument: Takes one optional argument:
.. attribute:: TimeInput.format .. attribute:: TimeInput.format
The format in which this field's initial value will be displayed. 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'``. If no ``format`` argument is provided, the default format is ``'%H:%M:%S'``.
.. versionchanged:: 1.1 .. versionchanged:: 1.1
@ -98,15 +103,15 @@ commonly used groups of widgets:
Takes one optional argument: Takes one optional argument:
.. attribute:: CheckboxInput.check_test .. 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 and returns ``True`` if the checkbox should be checked for
that value. that value.
.. class:: Select .. class:: Select
Select widget: ``<select><option ...>...</select>`` Select widget: ``<select><option ...>...</select>``
Requires that your field provides :attr:`~Field.choices`. Requires that your field provides :attr:`~Field.choices`.
.. class:: NullBooleanSelect .. class:: NullBooleanSelect
@ -123,22 +128,22 @@ commonly used groups of widgets:
.. class:: RadioSelect .. class:: RadioSelect
A list of radio buttons: A list of radio buttons:
.. code-block:: html .. code-block:: html
<ul> <ul>
<li><input type='radio' ...></li> <li><input type='radio' ...></li>
... ...
</ul> </ul>
Requires that your field provides :attr:`~Field.choices`. Requires that your field provides :attr:`~Field.choices`.
.. class:: CheckboxSelectMultiple .. class:: CheckboxSelectMultiple
A list of checkboxes: A list of checkboxes:
.. code-block:: html .. code-block:: html
<ul> <ul>
<li><input type='checkbox' ...></li> <li><input type='checkbox' ...></li>
... ...
@ -155,7 +160,7 @@ commonly used groups of widgets:
Takes two optional arguments, ``date_format`` and ``time_format``, which Takes two optional arguments, ``date_format`` and ``time_format``, which
work just like the ``format`` argument for ``DateInput`` and ``TimeInput``. work just like the ``format`` argument for ``DateInput`` and ``TimeInput``.
.. versionchanged:: 1.1 .. versionchanged:: 1.1
The ``date_format`` and ``time_format`` arguments were not supported in Django 1.0. The ``date_format`` and ``time_format`` arguments were not supported in Django 1.0.

View File

@ -18,6 +18,31 @@ fixes and an easy upgrade path from Django 1.2.
Backwards-incompatible changes in 1.3 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 Features deprecated in 1.3

View File

@ -705,13 +705,13 @@ Form.clean() is required to return a dictionary of all clean data.
>>> print f.as_table() >>> print f.as_table()
<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr> <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>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>Password1:</th><td><input type="password" name="password1" /></td></tr>
<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr> <tr><th>Password2:</th><td><input type="password" name="password2" /></td></tr>
>>> print f.as_ul() >>> print f.as_ul()
<li><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></li> <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>Username: <input type="text" name="username" value="adrian" maxlength="10" /></li>
<li>Password1: <input type="password" name="password1" value="foo" /></li> <li>Password1: <input type="password" name="password1" /></li>
<li>Password2: <input type="password" name="password2" value="bar" /></li> <li>Password2: <input type="password" name="password2" /></li>
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
>>> f.errors >>> f.errors
{} {}
@ -1589,8 +1589,8 @@ Case 2: POST with erroneous data (a redisplayed form, with errors).
<table> <table>
<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr> <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>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>Password1:</th><td><input type="password" name="password1" /></td></tr>
<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr> <tr><th>Password2:</th><td><input type="password" name="password2" /></td></tr>
</table> </table>
<input type="submit" /> <input type="submit" />
</form> </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)})) >>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)}))
<form action=""> <form action="">
<p><label>Your username: <input type="text" name="username" value="django" maxlength="10" /></label></p> <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: <input type="password" name="password1" /></label></p>
<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p> <p><label>Password (again): <input type="password" name="password2" /></label></p>
<input type="submit" /> <input type="submit" />
</form> </form>
>>> t = Template('''<form action=""> >>> t = Template('''<form action="">
@ -1734,8 +1734,8 @@ the list of errors is empty). You can also use it in {% if %} statements.
<form action=""> <form action="">
<ul class="errorlist"><li>Please make sure your passwords match.</li></ul> <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>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: <input type="password" name="password1" /></label></p>
<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p> <p><label>Password (again): <input type="password" name="password2" /></label></p>
<input type="submit" /> <input type="submit" />
</form> </form>

View File

@ -62,6 +62,17 @@ u'<input type="text" class="special" name="email" />'
u'<input type="password" name="email" />' u'<input type="password" name="email" />'
>>> w.render('email', None) >>> w.render('email', None)
u'<input type="password" name="email" />' 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') >>> w.render('email', 'test@example.com')
u'<input type="password" name="email" value="test@example.com" />' u'<input type="password" name="email" value="test@example.com" />'
>>> w.render('email', 'some "quoted" & ampersanded value') >>> w.render('email', 'some "quoted" & ampersanded value')
@ -70,36 +81,20 @@ u'<input type="password" name="email" value="some &quot;quoted&quot; &amp; amper
u'<input type="password" name="email" value="test@example.com" class="fun" />' u'<input type="password" name="email" value="test@example.com" class="fun" />'
You can also pass 'attrs' to the constructor: You can also pass 'attrs' to the constructor:
>>> w = PasswordInput(attrs={'class': 'fun'}) >>> w = PasswordInput(attrs={'class': 'fun'}, render_value=True)
>>> w.render('email', '') >>> w.render('email', '')
u'<input type="password" class="fun" name="email" />' u'<input type="password" class="fun" name="email" />'
>>> w.render('email', 'foo@example.com') >>> w.render('email', 'foo@example.com')
u'<input type="password" class="fun" value="foo@example.com" name="email" />' u'<input type="password" class="fun" value="foo@example.com" name="email" />'
'attrs' passed to render() get precedence over those passed to the constructor: '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'}) >>> w.render('email', '', attrs={'class': 'special'})
u'<input type="password" class="special" name="email" />' u'<input type="password" class="special" name="email" />'
>>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}) >>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
u'<input type="password" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />' 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 ############################################################ # HiddenInput Widget ############################################################
>>> w = HiddenInput() >>> w = HiddenInput()
@ -1286,7 +1281,7 @@ class SelectAndTextWidget(forms.MultiWidget):
forms.TextInput forms.TextInput
] ]
super(SelectAndTextWidget, self).__init__(widgets) super(SelectAndTextWidget, self).__init__(widgets)
def _set_choices(self, choices): def _set_choices(self, choices):
""" """
When choices are set for this widget, we want to pass those along to the Select widget When choices are set for this widget, we want to pass those along to the Select widget