Commit d3ba8cb8 authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

Fixed #13316 -- Modified the default behavior of PasswordInput to prevent...

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
parent 69d1e71f
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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

+24 −19
Original line number Diff line number Diff line
@@ -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

+25 −0
Original line number Diff line number Diff line
@@ -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
+10 −10
Original line number Diff line number Diff line
@@ -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>

+14 −19
Original line number Diff line number Diff line
@@ -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 &quot;quoted&quot; &amp; 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()