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

Proposed by Jonathan Jacobs
Status: Merged
Approved by: Tristan Seligmann
Approved revision: 114
Merged at revision: not available
Proposed branch: lp:~jjacobs/methanal/verified-password-input
Merge into: lp:methanal
Diff against target: 306 lines
4 files modified
methanal/js/Methanal/Tests/TestView.js (+119/-5)
methanal/js/Methanal/View.js (+91/-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
Tristan Seligmann Approve
Forrest Aldridge (community) Needs Fixing
Review via email: mp+13138@code.launchpad.net

This proposal supersedes a proposal from 2009-10-09.

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

 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
Revision history for this message
Tristan Seligmann (mithrandi) wrote :

1. Saving and restoring control._confirmPasswordNode seems pointless; nothing relies on this behaviour, and the first password input isn't saved and restored, so it's somewhat inconsistent.

2. In general, the pattern "throw new Error('Foo bar:' + c);" is better written as "throw new FooBar(c);"; embedding the exception "class" in text makes it challenging to catch the specific exception without doing something yucky like substring matching. In this specific case, setStrengthCriteria should probably throw UnknownStrengthCriterion or InvalidStrengthCriterion or something like that.

review: Needs Fixing
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
Revision history for this message
Jonathan Jacobs (jjacobs) wrote :

> 1. The docstring in TestView.js on line 834 is an incomplete sentence and is
> slightly unclear.

Oops, sorry.

> 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'

It was probably renamed to reduce it's width and then something else happened that stopped that from mattering.

Revision history for this message
Jonathan Jacobs (jjacobs) wrote :

> 1. Saving and restoring control._confirmPasswordNode seems pointless; nothing
> relies on this behaviour, and the first password input isn't saved and
> restored, so it's somewhat inconsistent.

This behaviour has been removed, it didn't seem to matter to anything, I guess if you need a clean slate, write another test case.

> 2. In general, the pattern "throw new Error('Foo bar:' + c);" is better
> written as "throw new FooBar(c);"; embedding the exception "class" in text
> makes it challenging to catch the specific exception without doing something
> yucky like substring matching. In this specific case, setStrengthCriteria
> should probably throw UnknownStrengthCriterion or InvalidStrengthCriterion or
> something like that.

I was being lazy.

Revision history for this message
Tristan Seligmann (mithrandi) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'methanal/js/Methanal/Tests/TestView.js'
--- methanal/js/Methanal/Tests/TestView.js 2009-10-04 11:18:09 +0000
+++ methanal/js/Methanal/Tests/TestView.js 2009-10-09 22:42:12 +0000
@@ -168,14 +168,14 @@
168 * Set the input node's value to C{value} and passes the result of168 * Set the input node's value to C{value} and passes the result of
169 * C{control.getValue} to C{control.baseValidator}.169 * C{control.getValue} to C{control.baseValidator}.
170 */170 */
171 function assertValidInput(self, control, value) {171 function assertValidInput(self, control, value, msg) {
172 var oldValue = control.inputNode.value;
173 control.inputNode.value = value;172 control.inputNode.value = value;
174 var msg = (Methanal.Util.repr(value) + ' is NOT valid input for ' +173 if (msg === undefined) {
175 Methanal.Util.repr(control));174 msg = (Methanal.Util.repr(value) + ' is NOT valid input for ' +
175 Methanal.Util.repr(control));
176 }
176 self.assertIdentical(177 self.assertIdentical(
177 control.baseValidator(control.getValue()), undefined, msg);178 control.baseValidator(control.getValue()), undefined, msg);
178 control.inputNode.value = oldValue;
179 },179 },
180180
181181
@@ -754,6 +754,120 @@
754754
755755
756/**756/**
757 * Tests for L{Methanal.View.VerifiedPasswordInput}.
758 */
759Methanal.Tests.TestView.BaseTestTextInput.subclass(Methanal.Tests.TestView, 'TestVerifiedPasswordInput').methods(
760 function setUp(self) {
761 self.controlType = Methanal.View.VerifiedPasswordInput;
762 },
763
764
765 /**
766 * Assert that C{password}, and optionally C{confirmPassword}, are a good
767 * input.
768 */
769 function assertGoodPassword(self, control, password, confirmPassword) {
770 if (confirmPassword === undefined) {
771 confirmPassword = password;
772 }
773 control._confirmPasswordNode.value = confirmPassword;
774 self.assertValidInput(
775 control, password,
776 Methanal.Util.repr(password) + ' is NOT a good password');
777 },
778
779
780 /**
781 * Assert that C{password}, and optionally C{confirmPassword}, are a bad
782 * input.
783 */
784 function assertBadPassword(self, control, password, confirmPassword) {
785 if (confirmPassword === undefined) {
786 confirmPassword = password;
787 }
788 control._confirmPasswordNode.value = confirmPassword;
789 self.assertInvalidInput(
790 control, password,
791 Methanal.Util.repr(password) + ' IS a good password');
792 },
793
794
795 function createControl(self, args) {
796 var control = Methanal.Tests.TestView.TestVerifiedPasswordInput.upcall(
797 self, 'createControl', args);
798 Methanal.Tests.TestView.makeWidgetChildNode(
799 control, 'input', 'confirmPassword');
800 return control;
801 },
802
803
804 /**
805 * Validation will fail under the following conditions:
806 * 1. The input and confirmPasswordNode node values don't match.
807 * 2. If either of the above node values have no length (are blank).
808 */
809 function test_inputValidation(self) {
810 self.testControl({value: null},
811 function (control) {
812 // Test condition 1
813 self.assertBadPassword(control, 'match', 'no match');
814 self.assertGoodPassword(control, 'match', 'match');
815 // Test condition 2
816 self.assertBadPassword(control, '', '');
817 });
818 },
819
820
821 /**
822 * Changing the password strength criteria results in different validation
823 * criteria for the control.
824 */
825 function test_strengthCriteria(self) {
826 // Override the default criteria of 5 or more characters.
827 self.testControl({value: null, minPasswordLength: 3},
828 function (control) {
829 self.assertBadPassword(control, '12');
830 self.assertGoodPassword(control, '123');
831
832 control.setStrengthCriteria(['ALPHA']);
833 self.assertGoodPassword(control, 'Abc');
834 self.assertBadPassword(control, '123');
835
836 control.setStrengthCriteria(['NUMERIC']);
837 self.assertBadPassword(control, 'Abc');
838 self.assertGoodPassword(control, '123');
839
840 control.setStrengthCriteria(['ALPHA', 'NUMERIC']);
841 self.assertBadPassword(control, 'Abc');
842 self.assertBadPassword(control, '123');
843 self.assertGoodPassword(control, 'Abc123');
844
845 control.setStrengthCriteria(['MIXEDCASE']);
846 self.assertGoodPassword(control, 'Abc');
847 self.assertGoodPassword(control, 'abC');
848 self.assertBadPassword(control, 'abc');
849 self.assertBadPassword(control, '123');
850
851 control.setStrengthCriteria(['SYMBOLS']);
852 self.assertGoodPassword(control, '!@#_');
853 self.assertBadPassword(control, ' ');
854 self.assertBadPassword(control, 'abc');
855
856 control.setStrengthCriteria([]);
857 self.assertGoodPassword(control, '!@#_');
858 self.assertGoodPassword(control, 'abc');
859 self.assertGoodPassword(control, '123');
860
861 self.assertThrows(Methanal.View.InvalidStrengthCriterion,
862 function () {
863 control.setStrengthCriteria(['DANGERWILLROBINSON']);
864 });
865 });
866 });
867
868
869
870/**
757 * Tests for L{Methanal.View.InputContainer}.871 * Tests for L{Methanal.View.InputContainer}.
758 */872 */
759Methanal.Tests.TestView.BaseTestTextInput.subclass(Methanal.Tests.TestView, 'TestFormGroup').methods(873Methanal.Tests.TestView.BaseTestTextInput.subclass(Methanal.Tests.TestView, 'TestFormGroup').methods(
760874
=== modified file 'methanal/js/Methanal/View.js'
--- methanal/js/Methanal/View.js 2009-10-05 00:15:43 +0000
+++ methanal/js/Methanal/View.js 2009-10-09 22:42:12 +0000
@@ -1793,3 +1793,94 @@
1793 return 'Percentage values must be between 0% and 100%'1793 return 'Percentage values must be between 0% and 100%'
1794 }1794 }
1795 });1795 });
1796
1797
1798
1799/**
1800 * An invalid password strength criterion was specified.
1801 */
1802Divmod.Error.subclass(Methanal.View, 'InvalidStrengthCriterion');
1803
1804
1805
1806/**
1807 * Password input with a verification field and strength checking.
1808 */
1809Methanal.View.TextInput.subclass(Methanal.View, 'VerifiedPasswordInput').methods(
1810 function __init__(self, node, args) {
1811 Methanal.View.VerifiedPasswordInput.upcall(
1812 self, '__init__', node, args);
1813 self._minPasswordLength = args.minPasswordLength || 5;
1814 self.setStrengthCriteria(args.strengthCriteria || []);
1815 },
1816
1817
1818 function nodeInserted(self) {
1819 self._confirmPasswordNode = self.nodeById('confirmPassword');
1820 Methanal.View.VerifiedPasswordInput.upcall(self, 'nodeInserted');
1821 },
1822
1823
1824 /**
1825 * Set the password strength criteria.
1826 *
1827 * @type criteria: C{Array} of C{String}
1828 * @param criteria: An array of names, matching those found in
1829 * L{Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA}, indicating
1830 * the password strength criteria
1831 */
1832 function setStrengthCriteria(self, criteria) {
1833 var fns = Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA;
1834 for (var i = 0; i < criteria.length; ++i) {
1835 var criterion = criteria[i];
1836 if (fns[criterion] === undefined) {
1837 criterion = Methanal.Util.repr(criterion);
1838 throw Methanal.View.InvalidStrengthCriterion(criterion);
1839 }
1840 }
1841 self._strengthCriteria = criteria;
1842 },
1843
1844
1845 /**
1846 * Override this method to change the definition of a 'strong' password.
1847 */
1848 function passwordIsStrong(self, password) {
1849 if (password.length < self._minPasswordLength) {
1850 return false;
1851 }
1852
1853 var fns = Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA;
1854 for (var i = 0; i < self._strengthCriteria.length; ++i) {
1855 var fn = fns[self._strengthCriteria[i]];
1856 if (!fn(password)) {
1857 return false;
1858 }
1859 }
1860 return true;
1861 },
1862
1863
1864 /**
1865 * This default validator ensures that the password is strong and that
1866 * the password given in both fields have length > 0 and match exactly.
1867 */
1868 function baseValidator(self, value) {
1869 if (value !== self._confirmPasswordNode.value || value === null ||
1870 self._confirmPasswordNode.value === null) {
1871 return 'Passwords do not match.';
1872 }
1873
1874 if (!self.passwordIsStrong(value)) {
1875 return 'Password is too weak.';
1876 }
1877 });
1878
1879
1880
1881Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA = {
1882 'ALPHA': function (value) { return /[a-zA-Z]/.test(value); },
1883 'NUMERIC': function (value) { return /[0-9]/.test(value); },
1884 'MIXEDCASE': function (value) {
1885 return /[a-z]/.test(value) && /[A-Z]/.test(value); },
1886 'SYMBOLS': function (value) { return /[^A-Za-z0-9\s]/.test(value); }};
17961887
=== added file 'methanal/themes/methanal-base/methanal-verified-password-input.html'
--- methanal/themes/methanal-base/methanal-verified-password-input.html 1970-01-01 00:00:00 +0000
+++ methanal/themes/methanal-base/methanal-verified-password-input.html 2009-10-09 22:42:12 +0000
@@ -0,0 +1,15 @@
1<div xmlns:nevow="http://nevow.com/ns/nevow/0.1" xmlns:athena="http://divmod.org/ns/athena/0.7" nevow:render="liveElement">
2 <input class="methanal-input" type="password">
3 <nevow:attr name="value" nevow:render="value" />
4 <athena:handler event="onchange" handler="onChange" />
5 <athena:handler event="onkeyup" handler="onKeyUp" />
6 <athena:handler event="onblur" handler="onBlur" />
7 <athena:handler event="onfocus" handler="onFocus" />
8 </input>
9 <input class="methanal-input" type="password" id="confirmPassword">
10 <nevow:attr name="value" nevow:render="value" />
11 <athena:handler event="onchange" handler="onChange" />
12 </input>
13 <span style="display: none;" class="friendly-representation" id="displayValue" />
14 <span class="methanal-error" id="error" />
15</div>
016
=== modified file 'methanal/view.py'
--- methanal/view.py 2009-10-04 10:17:26 +0000
+++ methanal/view.py 2009-10-09 22:42:12 +0000
@@ -511,6 +511,38 @@
511511
512512
513513
514class VerifiedPasswordInput(TextInput):
515 """
516 Password input with verification and strength checking.
517
518 @type minPasswordLength: C{int}
519 @ivar minPasswordLength: Minimum acceptable password length, or C{None}
520 to use the default client-side value
521
522 @type strengthCriteria: C{list} of C{unicode}
523 @ivar strengthCriteria: A list of criteria names for password strength
524 testing, or C{None} for no additional strength criteria. See
525 L{Methanal.View.VerifiedPasswordInput.STRENGTH_CRITERIA} in the
526 Javascript source for possible values
527 """
528 fragmentName = 'methanal-verified-password-input'
529 jsClass = u'Methanal.View.VerifiedPasswordInput'
530
531
532 def __init__(self, minPasswordLength=None, strengthCriteria=None, **kw):
533 super(VerifiedPasswordInput, self).__init__(**kw)
534 self.minPasswordLength = minPasswordLength
535 if strengthCriteria is None:
536 strengthCriteria = []
537 self.strengthCriteria = strengthCriteria
538
539
540 def getArgs(self):
541 return {u'minPasswordLength': self.minPasswordLength,
542 u'strengthCriteria': self.strengthCriteria}
543
544
545
514class ChoiceInput(FormInput):546class ChoiceInput(FormInput):
515 """547 """
516 Abstract input with multiple options.548 Abstract input with multiple options.

Subscribers

People subscribed via source and target branches