Merge lp:~jjacobs/methanal/verified-password-input into lp:methanal

Proposed by Jonathan Jacobs
Status: Superseded
Proposed branch: lp:~jjacobs/methanal/verified-password-input
Merge into: lp:methanal
Diff against target: 305 lines
4 files modified
methanal/js/Methanal/Tests/TestView.js (+128/-3)
methanal/js/Methanal/View.js (+84/-0)
methanal/themes/methanal-base/methanal-verified-password-input.html (+15/-0)
methanal/view.py (+32/-0)
To merge this branch: bzr merge lp:~jjacobs/methanal/verified-password-input
Reviewer Review Type Date Requested Status
Forrest Aldridge (community) Needs Fixing
Methanal maintainers Pending
Review via email: mp+13134@code.launchpad.net

This proposal has been superseded by a proposal from 2009-10-09.

To post a comment you must log in.
Revision history for this message
Forrest Aldridge (forrest-aldridge) wrote :

 1. The docstring in TestView.js on line 834 is an incomplete sentence and is slightly unclear.
 2. This is a matter of preference really, but I think it would be clearer if the variable c in View.js in lines 1828-1831 were renamed to 'criterion'

review: Needs Fixing
111. By Jonathan Jacobs

Rename variable for clarity.

112. By Jonathan Jacobs

Finish incomplete docstring.

113. By Jonathan Jacobs

Don't bother saving and restoring input node values.

114. By Jonathan Jacobs

Throw a real exception.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'methanal/js/Methanal/Tests/TestView.js'
2--- methanal/js/Methanal/Tests/TestView.js 2009-10-04 11:18:09 +0000
3+++ methanal/js/Methanal/Tests/TestView.js 2009-10-09 17:35:23 +0000
4@@ -168,11 +168,13 @@
5 * Set the input node's value to C{value} and passes the result of
6 * C{control.getValue} to C{control.baseValidator}.
7 */
8- function assertValidInput(self, control, value) {
9+ function assertValidInput(self, control, value, msg) {
10 var oldValue = control.inputNode.value;
11 control.inputNode.value = value;
12- var msg = (Methanal.Util.repr(value) + ' is NOT valid input for ' +
13- Methanal.Util.repr(control));
14+ if (msg === undefined) {
15+ msg = (Methanal.Util.repr(value) + ' is NOT valid input for ' +
16+ Methanal.Util.repr(control));
17+ }
18 self.assertIdentical(
19 control.baseValidator(control.getValue()), undefined, msg);
20 control.inputNode.value = oldValue;
21@@ -754,6 +756,129 @@
22
23
24 /**
25+ * Tests for L{Methanal.View.VerifiedPasswordInput}.
26+ */
27+Methanal.Tests.TestView.BaseTestTextInput.subclass(Methanal.Tests.TestView, 'TestVerifiedPasswordInput').methods(
28+ function setUp(self) {
29+ self.controlType = Methanal.View.VerifiedPasswordInput;
30+ },
31+
32+
33+ /**
34+ * Perform some setup tasks for password validation asserting.
35+ */
36+ function _assertPassword(self, fn, control, password, confirmPassword) {
37+ if (confirmPassword === undefined) {
38+ confirmPassword = password;
39+ }
40+ var oldPasswordValue = control._confirmPasswordNode.value;
41+ control._confirmPasswordNode.value = confirmPassword;
42+ fn();
43+ control._confirmPasswordNode.value = oldPasswordValue;
44+ },
45+
46+
47+ /**
48+ * Assert that C{password}, and optionally C{confirmPassword}, are a good
49+ * input.
50+ */
51+ function assertGoodPassword(self, control, password, confirmPassword) {
52+ self._assertPassword(function () {
53+ self.assertValidInput(
54+ control, password,
55+ Methanal.Util.repr(password) + ' is NOT a good password');
56+ }, control, password, confirmPassword);
57+ },
58+
59+
60+ /**
61+ * Assert that C{password}, and optionally C{confirmPassword}, are a bad
62+ * input.
63+ */
64+ function assertBadPassword(self, control, password, confirmPassword) {
65+ self._assertPassword(function () {
66+ self.assertInvalidInput(
67+ control, password,
68+ Methanal.Util.repr(password) + ' IS a good password');
69+ }, control, password, confirmPassword);
70+ },
71+
72+
73+ function createControl(self, args) {
74+ var control = Methanal.Tests.TestView.TestVerifiedPasswordInput.upcall(
75+ self, 'createControl', args);
76+ Methanal.Tests.TestView.makeWidgetChildNode(
77+ control, 'input', 'confirmPassword');
78+ return control;
79+ },
80+
81+
82+ /**
83+ * Validation will fail under the following conditions:
84+ * 1. The input and confirmPasswordNode node values don't match.
85+ * 2. If either of the above node values have no length (are blank).
86+ */
87+ function test_inputValidation(self) {
88+ self.testControl({value: null},
89+ function (control) {
90+ // Test condition 1
91+ self.assertBadPassword(control, 'match', 'no match');
92+ self.assertGoodPassword(control, 'match', 'match');
93+ // Test condition 2
94+ self.assertBadPassword(control, '', '');
95+ });
96+ },
97+
98+
99+ /**
100+ * Changing the password strength criteria results in different
101+ */
102+ function test_strengthCriteria(self) {
103+ // Override the default criteria of 5 or more characters.
104+ self.testControl({value: null, minPasswordLength: 3},
105+ function (control) {
106+ self.assertBadPassword(control, '12');
107+ self.assertGoodPassword(control, '123');
108+
109+ control.setStrengthCriteria(['ALPHA']);
110+ self.assertGoodPassword(control, 'Abc');
111+ self.assertBadPassword(control, '123');
112+
113+ control.setStrengthCriteria(['NUMERIC']);
114+ self.assertBadPassword(control, 'Abc');
115+ self.assertGoodPassword(control, '123');
116+
117+ control.setStrengthCriteria(['ALPHA', 'NUMERIC']);
118+ self.assertBadPassword(control, 'Abc');
119+ self.assertBadPassword(control, '123');
120+ self.assertGoodPassword(control, 'Abc123');
121+
122+ control.setStrengthCriteria(['MIXEDCASE']);
123+ self.assertGoodPassword(control, 'Abc');
124+ self.assertGoodPassword(control, 'abC');
125+ self.assertBadPassword(control, 'abc');
126+ self.assertBadPassword(control, '123');
127+
128+ control.setStrengthCriteria(['SYMBOLS']);
129+ self.assertGoodPassword(control, '!@#_');
130+ self.assertBadPassword(control, ' ');
131+ self.assertBadPassword(control, 'abc');
132+
133+ control.setStrengthCriteria([]);
134+ self.assertGoodPassword(control, '!@#_');
135+ self.assertGoodPassword(control, 'abc');
136+ self.assertGoodPassword(control, '123');
137+
138+ self.assertThrows(Error,
139+ function () {
140+ control.setStrengthCriteria(['DANGERWILLROBINSON']);
141+ });
142+ });
143+ });
144+
145+
146+
147+/**
148 * Tests for L{Methanal.View.InputContainer}.
149 */
150 Methanal.Tests.TestView.BaseTestTextInput.subclass(Methanal.Tests.TestView, 'TestFormGroup').methods(
151
152=== modified file 'methanal/js/Methanal/View.js'
153--- methanal/js/Methanal/View.js 2009-10-05 00:15:43 +0000
154+++ methanal/js/Methanal/View.js 2009-10-09 17:35:23 +0000
155@@ -1793,3 +1793,87 @@
156 return 'Percentage values must be between 0% and 100%'
157 }
158 });
159+
160+
161+
162+/**
163+ * Password input with a verification field and strength checking.
164+ */
165+Methanal.View.TextInput.subclass(Methanal.View, 'VerifiedPasswordInput').methods(
166+ function __init__(self, node, args) {
167+ Methanal.View.VerifiedPasswordInput.upcall(
168+ self, '__init__', node, args);
169+ self._minPasswordLength = args.minPasswordLength || 5;
170+ self.setStrengthCriteria(args.strengthCriteria || []);
171+ },
172+
173+
174+ function nodeInserted(self) {
175+ self._confirmPasswordNode = self.nodeById('confirmPassword');
176+ Methanal.View.VerifiedPasswordInput.upcall(self, 'nodeInserted');
177+ },
178+
179+
180+ /**
181+ * Set the password strength criteria.
182+ *
183+ * @type criteria: C{Array} of C{String}
184+ * @param criteria: An array of names, matching those found in
185+ * L{Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA}, indicating
186+ * the password strength criteria
187+ */
188+ function setStrengthCriteria(self, criteria) {
189+ var fns = Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA;
190+ for (var i = 0; i < criteria.length; ++i) {
191+ var c = criteria[i];
192+ if (fns[c] === undefined) {
193+ c = Methanal.Util.repr(c);
194+ throw new Error('Unknown strength criterion: ' + c);
195+ }
196+ }
197+ self._strengthCriteria = criteria;
198+ },
199+
200+
201+ /**
202+ * Override this method to change the definition of a 'strong' password.
203+ */
204+ function passwordIsStrong(self, password) {
205+ if (password.length < self._minPasswordLength) {
206+ return false;
207+ }
208+
209+ var fns = Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA;
210+ for (var i = 0; i < self._strengthCriteria.length; ++i) {
211+ var fn = fns[self._strengthCriteria[i]];
212+ if (!fn(password)) {
213+ return false;
214+ }
215+ }
216+ return true;
217+ },
218+
219+
220+ /**
221+ * This default validator ensures that the password is strong and that
222+ * the password given in both fields have length > 0 and match exactly.
223+ */
224+ function baseValidator(self, value) {
225+ if (value !== self._confirmPasswordNode.value || value === null ||
226+ self._confirmPasswordNode.value === null) {
227+ return 'Passwords do not match.';
228+ }
229+
230+ if (!self.passwordIsStrong(value)) {
231+ return 'Password is too weak.';
232+ }
233+ });
234+
235+
236+
237+Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA = {
238+ 'ALPHA': function (value) { return /[a-zA-Z]/.test(value); },
239+ 'NUMERIC': function (value) { return /[0-9]/.test(value); },
240+ 'MIXEDCASE': function (value) {
241+ return /[a-z]/.test(value) && /[A-Z]/.test(value); },
242+ 'SYMBOLS': function (value) { return /[^A-Za-z0-9\s]/.test(value); }};
243
244=== added file 'methanal/themes/methanal-base/methanal-verified-password-input.html'
245--- methanal/themes/methanal-base/methanal-verified-password-input.html 1970-01-01 00:00:00 +0000
246+++ methanal/themes/methanal-base/methanal-verified-password-input.html 2009-10-09 17:35:23 +0000
247@@ -0,0 +1,15 @@
248+<div xmlns:nevow="http://nevow.com/ns/nevow/0.1" xmlns:athena="http://divmod.org/ns/athena/0.7" nevow:render="liveElement">
249+ <input class="methanal-input" type="password">
250+ <nevow:attr name="value" nevow:render="value" />
251+ <athena:handler event="onchange" handler="onChange" />
252+ <athena:handler event="onkeyup" handler="onKeyUp" />
253+ <athena:handler event="onblur" handler="onBlur" />
254+ <athena:handler event="onfocus" handler="onFocus" />
255+ </input>
256+ <input class="methanal-input" type="password" id="confirmPassword">
257+ <nevow:attr name="value" nevow:render="value" />
258+ <athena:handler event="onchange" handler="onChange" />
259+ </input>
260+ <span style="display: none;" class="friendly-representation" id="displayValue" />
261+ <span class="methanal-error" id="error" />
262+</div>
263
264=== modified file 'methanal/view.py'
265--- methanal/view.py 2009-10-04 10:17:26 +0000
266+++ methanal/view.py 2009-10-09 17:35:23 +0000
267@@ -511,6 +511,38 @@
268
269
270
271+class VerifiedPasswordInput(TextInput):
272+ """
273+ Password input with verification and strength checking.
274+
275+ @type minPasswordLength: C{int}
276+ @ivar minPasswordLength: Minimum acceptable password length, or C{None}
277+ to use the default client-side value
278+
279+ @type strengthCriteria: C{list} of C{unicode}
280+ @ivar strengthCriteria: A list of criteria names for password strength
281+ testing, or C{None} for no additional strength criteria. See
282+ L{Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA} in the
283+ Javascript source for possible values
284+ """
285+ fragmentName = 'methanal-verified-password-input'
286+ jsClass = u'Methanal.View.VerifiedPasswordInput'
287+
288+
289+ def __init__(self, minPasswordLength=None, strengthCriteria=None, **kw):
290+ super(VerifiedPasswordInput, self).__init__(**kw)
291+ self.minPasswordLength = minPasswordLength
292+ if strengthCriteria is None:
293+ strengthCriteria = []
294+ self.strengthCriteria = strengthCriteria
295+
296+
297+ def getArgs(self):
298+ return {u'minPasswordLength': self.minPasswordLength,
299+ u'strengthCriteria': self.strengthCriteria}
300+
301+
302+
303 class ChoiceInput(FormInput):
304 """
305 Abstract input with multiple options.

Subscribers

People subscribed via source and target branches