summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2010-08-06 14:25:58 +0000
committerRussell Keith-Magee <russell@keith-magee.com>2010-08-06 14:25:58 +0000
commitd3ba8cb88b2f3939398c9c54e5aa5c6e7495e56f (patch)
treee6cda6a74742fac01fb99c28ed7f9f4fc15048a9
parent69d1e71fad3046792f737b26c7da75e8f373eb44 (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.py2
-rw-r--r--docs/ref/forms/widgets.txt43
-rw-r--r--docs/releases/1.3.txt25
-rw-r--r--tests/regressiontests/forms/forms.py20
-rw-r--r--tests/regressiontests/forms/widgets.py33
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 &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()
@@ -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