[Checkins] SVN: z3c.password/trunk/ Better error messages for invalid password exceptions.

Marius Gedminas cvs-admin at zope.org
Thu Aug 9 12:22:11 UTC 2012


Log message for revision 127450:
  Better error messages for invalid password exceptions.
  
  When you reject the user's password for being too short or too long,
  it's only polite to tell them the minimum/maximum password length.
  
  This introduces new translatable strings which haven't been translated yet.

Changed:
  U   z3c.password/trunk/CHANGES.txt
  U   z3c.password/trunk/setup.py
  U   z3c.password/trunk/src/z3c/password/README.txt
  U   z3c.password/trunk/src/z3c/password/interfaces.py
  U   z3c.password/trunk/src/z3c/password/password.py

-=-
Modified: z3c.password/trunk/CHANGES.txt
===================================================================
--- z3c.password/trunk/CHANGES.txt	2012-08-09 12:22:02 UTC (rev 127449)
+++ z3c.password/trunk/CHANGES.txt	2012-08-09 12:22:07 UTC (rev 127450)
@@ -2,12 +2,16 @@
 CHANGES
 =======
 
-0.10.2 (unreleased)
+0.11.0 (unreleased)
 -------------------
 
-- Nothing changed yet.
+- Better error messages for invalid password exceptions (when you reject the
+  user's password for being too short or too long, it's only polite to tell
+  them what the minimum/maximum password length is).
 
+  This introduces new translatable strings which haven't been translated yet.
 
+
 0.10.1 (2011-03-28)
 -------------------
 

Modified: z3c.password/trunk/setup.py
===================================================================
--- z3c.password/trunk/setup.py	2012-08-09 12:22:02 UTC (rev 127449)
+++ z3c.password/trunk/setup.py	2012-08-09 12:22:07 UTC (rev 127450)
@@ -23,7 +23,7 @@
 
 setup (
     name='z3c.password',
-    version='0.10.2dev',
+    version='0.11.0dev',
     author = "Stephan Richter, Roger Ineichen and the Zope Community",
     author_email = "zope3-dev at zope.org",
     description = "Password generation and verification utility for Zope3",
@@ -62,9 +62,10 @@
         'zope.component',
         'zope.exceptions',
         'zope.i18nmessageid',
+        'zope.i18n',
         'zope.interface',
         'zope.schema',
         'zope.security',
         ],
     zip_safe = False,
-)
\ No newline at end of file
+)

Modified: z3c.password/trunk/src/z3c/password/README.txt
===================================================================
--- z3c.password/trunk/src/z3c/password/README.txt	2012-08-09 12:22:02 UTC (rev 127449)
+++ z3c.password/trunk/src/z3c/password/README.txt	2012-08-09 12:22:07 UTC (rev 127450)
@@ -81,12 +81,12 @@
   >>> pwd.verify('foo')
   Traceback (most recent call last):
   ...
-  TooShortPassword
+  TooShortPassword: Password is too short (minimum length: 8).
 
   >>> pwd.verify('foobar-foobar')
   Traceback (most recent call last):
   ...
-  TooLongPassword
+  TooLongPassword: Password is too long (maximum length: 12).
 
   >>> pwd.verify('fooBar12')
 
@@ -100,7 +100,7 @@
   >>> pwd.verify('fooBar12', 'foobar12')
   Traceback (most recent call last):
   ...
-  TooSimilarPassword
+  TooSimilarPassword: Password is too similar to old one (similarity 88%, should be at most 60%).
 
 - The final check ensures that the password does not have too many characters
   of one group. The groups are: lower letters, upper letters, digits,
@@ -109,27 +109,27 @@
   >>> pwd.verify('fooBarBlah')
   Traceback (most recent call last):
   ...
-  TooManyGroupCharacters
+  TooManyGroupCharacters: Password contains too many characters of one group (should have at most 6).
 
   >>> pwd.verify('FOOBARBlah')
   Traceback (most recent call last):
   ...
-  TooManyGroupCharacters
+  TooManyGroupCharacters: Password contains too many characters of one group (should have at most 6).
 
   >>> pwd.verify('12345678')
   Traceback (most recent call last):
   ...
-  TooManyGroupCharacters
+  TooManyGroupCharacters: Password contains too many characters of one group (should have at most 6).
 
   >>> pwd.verify('........')
   Traceback (most recent call last):
   ...
-  TooManyGroupCharacters
+  TooManyGroupCharacters: Password contains too many characters of one group (should have at most 6).
 
   >>> pwd.verify(unichr(0x0e1)*8)
   Traceback (most recent call last):
   ...
-  TooManyGroupCharacters
+  TooManyGroupCharacters: Password contains too many characters of one group (should have at most 6).
 
 Let's now verify a list of password that were provided by a bank:
 
@@ -167,12 +167,12 @@
   >>> pwd.verify('FOOBAR123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersLowerLetter
+  TooFewGroupCharactersLowerLetter: Password does not contain enough characters of lowercase letters (should have at least 5).
 
   >>> pwd.verify('foobAR123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersLowerLetter
+  TooFewGroupCharactersLowerLetter: Password does not contain enough characters of lowercase letters (should have at least 5).
 
   >>> pwd.verify('foobaR123')
 
@@ -191,12 +191,12 @@
   >>> pwd.verify('foobar123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersUpperLetter
+  TooFewGroupCharactersUpperLetter: Password does not contain enough characters of uppercase letters (should have at least 5).
 
   >>> pwd.verify('FOOBar123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersUpperLetter
+  TooFewGroupCharactersUpperLetter: Password does not contain enough characters of uppercase letters (should have at least 5).
 
   >>> pwd.verify('fOOBAR123')
 
@@ -215,12 +215,12 @@
   >>> pwd.verify('foobar123')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersDigits
+  TooFewGroupCharactersDigits: Password does not contain enough characters of digits (should have at least 5).
 
   >>> pwd.verify('FOOBa1234')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersDigits
+  TooFewGroupCharactersDigits: Password does not contain enough characters of digits (should have at least 5).
 
   >>> pwd.verify('fOBA12345')
 
@@ -239,12 +239,12 @@
   >>> pwd.verify('foo(bar)')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersSpecials
+  TooFewGroupCharactersSpecials: Password does not contain enough characters of special characters (should have at least 5).
 
   >>> pwd.verify('FO.#(Ba1)')
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersSpecials
+  TooFewGroupCharactersSpecials: Password does not contain enough characters of special characters (should have at least 5).
 
   >>> pwd.verify('fO.,;()5')
 
@@ -262,12 +262,12 @@
   >>> pwd.verify('foobar'+unichr(0x0c3)+unichr(0x0c4))
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersOthers
+  TooFewGroupCharactersOthers: Password does not contain enough characters of other characters (should have at least 5).
 
   >>> pwd.verify('foobar'+unichr(0x0c3)+unichr(0x0c4)+unichr(0x0e1))
   Traceback (most recent call last):
   ...
-  TooFewGroupCharactersOthers
+  TooFewGroupCharactersOthers: Password does not contain enough characters of other characters (should have at least 5).
 
   >>> pwd.verify('fOO'+unichr(0x0e1)*5)
 
@@ -288,12 +288,12 @@
   >>> pwd.verify('foofoo1212')
   Traceback (most recent call last):
   ...
-  TooFewUniqueCharacters
+  TooFewUniqueCharacters: Password does not contain enough unique characters (should have at least 5).
 
   >>> pwd.verify('FOOfoo2323')
   Traceback (most recent call last):
   ...
-  TooFewUniqueCharacters
+  TooFewUniqueCharacters: Password does not contain enough unique characters (should have at least 5).
 
   >>> pwd.verify('fOOBAR123')
 
@@ -312,12 +312,12 @@
   >>> pwd.verify('foofoo1212')
   Traceback (most recent call last):
   ...
-  TooFewUniqueLetters
+  TooFewUniqueLetters: Password does not contain enough unique letters (should have at least 5).
 
   >>> pwd.verify('FOOBfoob2323')
   Traceback (most recent call last):
   ...
-  TooFewUniqueLetters
+  TooFewUniqueLetters: Password does not contain enough unique letters (should have at least 5).
 
   >>> pwd.verify('fOOBAR123')
 
@@ -356,7 +356,7 @@
   >>> pwdField.validate(u'fooBar')
   Traceback (most recent call last):
   ...
-  TooShortPassword
+  TooShortPassword: Password is too short (minimum length: 8).
 
 Validation must work on bound fields too:
 
@@ -379,7 +379,7 @@
   >>> bound.validate(u'fooBar')
   Traceback (most recent call last):
   ...
-  TooShortPassword
+  TooShortPassword: Password is too short (minimum length: 8).
 
 Let's create a principal without the PrincipalMixIn:
 
@@ -394,7 +394,7 @@
   >>> bound.validate(u'fooBar')
   Traceback (most recent call last):
   ...
-  TooShortPassword
+  TooShortPassword: Password is too short (minimum length: 8).
 
 
 Other common usecase is to do a utility and specify it's name as checker.
@@ -415,7 +415,7 @@
   >>> pwdField.validate(u'fooBar')
   Traceback (most recent call last):
   ...
-  TooShortPassword
+  TooShortPassword: Password is too short (minimum length: 8).
 
 
 Edge cases.
@@ -456,4 +456,4 @@
 
 Validation silently succeeds:
 
-  >>> bound.validate(u'fooBar12')
\ No newline at end of file
+  >>> bound.validate(u'fooBar12')

Modified: z3c.password/trunk/src/z3c/password/interfaces.py
===================================================================
--- z3c.password/trunk/src/z3c/password/interfaces.py	2012-08-09 12:22:02 UTC (rev 127449)
+++ z3c.password/trunk/src/z3c/password/interfaces.py	2012-08-09 12:22:07 UTC (rev 127450)
@@ -18,27 +18,77 @@
 __docformat__ = "reStructuredText"
 import zope.interface
 import zope.schema
+from zope.i18n import translate
 
 from z3c.password import MessageFactory as _
 
 class InvalidPassword(zope.schema.ValidationError):
     """Invalid Password"""
 
+    i18n_message = None
+
+    def __str__(self):
+        if self.i18n_message:
+            return translate(self.i18n_message)
+        return super(InvalidPassword, self).__str__()
+
+    def doc(self):
+        if self.i18n_message:
+            return self.i18n_message
+        return self.__class__.__doc__
+
+
 class NoPassword(InvalidPassword):
     __doc__ = _('''No new password specified.''')
 
 class TooShortPassword(InvalidPassword):
     __doc__ = _('''Password is too short.''')
 
+    def __init__(self, minLength=None):
+        super(TooShortPassword, self).__init__()
+        self.minLength = minLength
+        if minLength is not None:
+            self.i18n_message = _(
+                'Password is too short (minimum length: ${minLength}).',
+                mapping=dict(minLength=minLength))
+
 class TooLongPassword(InvalidPassword):
     __doc__ = _('''Password is too long.''')
 
+    def __init__(self, maxLength=None):
+        super(TooLongPassword, self).__init__()
+        self.maxLength = maxLength
+        if maxLength is not None:
+            self.i18n_message = _(
+                'Password is too long (maximum length: ${maxLength}).',
+                mapping=dict(maxLength=maxLength))
+
 class TooSimilarPassword(InvalidPassword):
     __doc__ = _('''Password is too similar to old one.''')
 
+    def __init__(self, similarity=None, maxSimilarity=None):
+        super(TooSimilarPassword, self).__init__()
+        self.similarity = similarity
+        self.maxSimilarity = maxSimilarity
+        if similarity is not None and maxSimilarity is not None:
+            self.i18n_message = _(
+                'Password is too similar to old one'
+                ' (similarity ${similarity}%, should be at most ${maxSimilarity}%).',
+                mapping=dict(similarity=int(round(similarity * 100)),
+                             maxSimilarity=int(round(maxSimilarity * 100))))
+
 class TooManyGroupCharacters(InvalidPassword):
     __doc__ = _('''Password contains too many characters of one group.''')
 
+    def __init__(self, groupMax=None):
+        super(TooManyGroupCharacters, self).__init__()
+        self.groupMax = groupMax
+        if groupMax is not None:
+            self.i18n_message = _(
+                'Password contains too many characters of one group'
+                ' (should have at most ${groupMax}).',
+                mapping=dict(groupMax=groupMax))
+
 class TooFewGroupCharacters(InvalidPassword):
     __doc__ = _('''Password does not contain enough characters of one group.''')
 
@@ -46,27 +96,90 @@
     __doc__ = _(
         '''Password does not contain enough characters of lowercase letters.''')
 
+    def __init__(self, minLowerLetter=None):
+        super(TooFewGroupCharactersLowerLetter, self).__init__()
+        self.minLowerLetter = minLowerLetter
+        if minLowerLetter is not None:
+            self.i18n_message = _(
+                'Password does not contain enough characters of lowercase letters'
+                ' (should have at least ${minLowerLetter}).',
+                mapping=dict(minLowerLetter=minLowerLetter))
+
 class TooFewGroupCharactersUpperLetter(TooFewGroupCharacters):
     __doc__ = _(
         '''Password does not contain enough characters of uppercase letters.''')
 
+    def __init__(self, minUpperLetter=None):
+        super(TooFewGroupCharactersUpperLetter, self).__init__()
+        self.minUpperLetter = minUpperLetter
+        if minUpperLetter is not None:
+            self.i18n_message = _(
+                'Password does not contain enough characters of uppercase letters'
+                ' (should have at least ${minUpperLetter}).',
+                mapping=dict(minUpperLetter=minUpperLetter))
+
 class TooFewGroupCharactersDigits(TooFewGroupCharacters):
     __doc__ = _('''Password does not contain enough characters of digits.''')
 
+    def __init__(self, minDigits=None):
+        super(TooFewGroupCharactersDigits, self).__init__()
+        self.minDigits = minDigits
+        if minDigits is not None:
+            self.i18n_message = _(
+                'Password does not contain enough characters of digits'
+                ' (should have at least ${minDigits}).',
+                mapping=dict(minDigits=minDigits))
+
 class TooFewGroupCharactersSpecials(TooFewGroupCharacters):
     __doc__ = _(
         '''Password does not contain enough characters of special characters.''')
 
+    def __init__(self, minSpecials=None):
+        super(TooFewGroupCharactersSpecials, self).__init__()
+        self.minSpecials = minSpecials
+        if minSpecials is not None:
+            self.i18n_message = _(
+                'Password does not contain enough characters of special characters'
+                ' (should have at least ${minSpecials}).',
+                mapping=dict(minSpecials=minSpecials))
+
 class TooFewGroupCharactersOthers(TooFewGroupCharacters):
     __doc__ = _(
         '''Password does not contain enough characters of other characters.''')
 
+    def __init__(self, minOthers=None):
+        super(TooFewGroupCharactersOthers, self).__init__()
+        self.minOthers = minOthers
+        if minOthers is not None:
+            self.i18n_message = _(
+                'Password does not contain enough characters of other characters'
+                ' (should have at least ${minOthers}).',
+                mapping=dict(minOthers=minOthers))
+
 class TooFewUniqueCharacters(InvalidPassword):
     __doc__ = _('''Password does not contain enough unique characters.''')
 
+    def __init__(self, minUniqueCharacters=None):
+        super(TooFewUniqueCharacters, self).__init__()
+        self.minUniqueCharacters = minUniqueCharacters
+        if minUniqueCharacters is not None:
+            self.i18n_message = _(
+                'Password does not contain enough unique characters'
+                ' (should have at least ${minUniqueCharacters}).',
+                mapping=dict(minUniqueCharacters=minUniqueCharacters))
+
 class TooFewUniqueLetters(InvalidPassword):
     __doc__ = _('''Password does not contain enough unique letters.''')
 
+    def __init__(self, minUniqueLetters=None):
+        super(TooFewUniqueLetters, self).__init__()
+        self.minUniqueLetters = minUniqueLetters
+        if minUniqueLetters is not None:
+            self.i18n_message = _(
+                'Password does not contain enough unique letters'
+                ' (should have at least ${minUniqueLetters}).',
+                mapping=dict(minUniqueLetters=minUniqueLetters))
+
 class PasswordExpired(Exception):
     __doc__ = _('''The password has expired.''')
 

Modified: z3c.password/trunk/src/z3c/password/password.py
===================================================================
--- z3c.password/trunk/src/z3c/password/password.py	2012-08-09 12:22:02 UTC (rev 127449)
+++ z3c.password/trunk/src/z3c/password/password.py	2012-08-09 12:22:07 UTC (rev 127450)
@@ -97,9 +97,10 @@
         self.minUniqueLetters = minUniqueLetters
 
     def _checkSimilarity(self, new, ref):
-        sm = difflib.SequenceMatcher(None, new, ref)
-        if sm.ratio() > self.maxSimilarity:
-            raise interfaces.TooSimilarPassword()
+        similarity = difflib.SequenceMatcher(None, new, ref).ratio()
+        if similarity > self.maxSimilarity:
+            raise interfaces.TooSimilarPassword(
+                similarity=similarity, maxSimilarity=self.maxSimilarity)
 
     def verify(self, new, ref=None):
         '''See interfaces.IHighSecurityPasswordUtility'''
@@ -108,9 +109,9 @@
             raise interfaces.NoPassword()
         # 1. Make sure the password has the right length.
         if len(new) < self.minLength:
-            raise interfaces.TooShortPassword()
+            raise interfaces.TooShortPassword(minLength=self.minLength)
         if len(new) > self.maxLength:
-            raise interfaces.TooLongPassword()
+            raise interfaces.TooLongPassword(maxLength=self.maxLength)
         # 2. Ensure that the password is sufficiently different to the old
         #    one.
         if ref is not None:
@@ -142,35 +143,43 @@
             num_digits > self.groupMax or
             num_specials > self.groupMax or
             num_others > self.groupMax):
-            raise interfaces.TooManyGroupCharacters()
+            raise interfaces.TooManyGroupCharacters(
+                groupMax=self.groupMax)
 
         if (self.minLowerLetter is not None
             and num_lower_letters < self.minLowerLetter):
-            raise interfaces.TooFewGroupCharactersLowerLetter()
+            raise interfaces.TooFewGroupCharactersLowerLetter(
+                        minLowerLetter=self.minLowerLetter)
 
         if (self.minUpperLetter is not None
             and num_upper_letters < self.minUpperLetter):
-            raise interfaces.TooFewGroupCharactersUpperLetter()
+            raise interfaces.TooFewGroupCharactersUpperLetter(
+                        minUpperLetter=self.minUpperLetter)
 
         if (self.minDigits is not None
             and num_digits < self.minDigits):
-            raise interfaces.TooFewGroupCharactersDigits()
+            raise interfaces.TooFewGroupCharactersDigits(
+                        minDigits=self.minDigits)
 
         if (self.minSpecials is not None
             and num_specials < self.minSpecials):
-            raise interfaces.TooFewGroupCharactersSpecials()
+            raise interfaces.TooFewGroupCharactersSpecials(
+                        minSpecials=self.minSpecials)
 
         if (self.minOthers is not None
             and num_others < self.minOthers):
-            raise interfaces.TooFewGroupCharactersOthers()
+            raise interfaces.TooFewGroupCharactersOthers(
+                        minOthers=self.minOthers)
 
         if (self.minUniqueCharacters is not None
             and len(uniqueChars) < self.minUniqueCharacters):
-            raise interfaces.TooFewUniqueCharacters()
+            raise interfaces.TooFewUniqueCharacters(
+                        minUniqueCharacters=self.minUniqueCharacters)
 
         if (self.minUniqueLetters is not None
             and len(uniqueLetters) < self.minUniqueLetters):
-            raise interfaces.TooFewUniqueLetters()
+            raise interfaces.TooFewUniqueLetters(
+                        minUniqueLetters=self.minUniqueLetters)
 
         return
 
@@ -225,4 +234,4 @@
         self.lockOutPeriod = lockOutPeriod
         self.maxFailedAttempts = maxFailedAttempts
         self.disallowPasswordReuse = disallowPasswordReuse
-        self.failedAttemptCheck = failedAttemptCheck
\ No newline at end of file
+        self.failedAttemptCheck = failedAttemptCheck



More information about the checkins mailing list