[Checkins] SVN: z3c.password/branches/adamg-evenhigher/src/z3c/password/ save my work

Adam Groszer agroszer at gmail.com
Fri Jun 19 08:19:22 EDT 2009


Log message for revision 101136:
  save my work
  

Changed:
  U   z3c.password/branches/adamg-evenhigher/src/z3c/password/interfaces.py
  U   z3c.password/branches/adamg-evenhigher/src/z3c/password/password.py

-=-
Modified: z3c.password/branches/adamg-evenhigher/src/z3c/password/interfaces.py
===================================================================
--- z3c.password/branches/adamg-evenhigher/src/z3c/password/interfaces.py	2009-06-19 09:45:51 UTC (rev 101135)
+++ z3c.password/branches/adamg-evenhigher/src/z3c/password/interfaces.py	2009-06-19 12:19:21 UTC (rev 101136)
@@ -40,6 +40,9 @@
 class TooManyGroupCharacters(InvalidPassword):
     __doc__ = _('''Password contains too many characters of one group.''')
 
+class TooFewGroupCharacters(InvalidPassword):
+    __doc__ = _('''Password does not contain enough characters of one group.''')
+
 class PasswordExpired(Exception):
     __doc__ = _('''The password has expired.''')
 
@@ -135,7 +138,95 @@
         required=False,
         default=None)
 
+    minLowerLetter = zope.schema.Int(
+        title=_(u'Minimum Number of Lowercase letters'),
+        description=_(u'The minimum amount of lowercase letters that a '
+                      u'password must have.'),
+        required=False,
+        default=None)
 
+    minUpperLetter = zope.schema.Int(
+        title=_(u'Minimum Number of Uppercase letters'),
+        description=_(u'The minimum amount of uppercase letters that a '
+                      u'password must have.'),
+        required=False,
+        default=None)
+
+    minDigits = zope.schema.Int(
+        title=_(u'Minimum Number of Numeric digits'),
+        description=_(u'The minimum amount of numeric digits that a '
+                      u'password must have.'),
+        required=False,
+        default=None)
+
+    minSpecials = zope.schema.Int(
+        title=_(u'Minimum Number of Special characters'),
+        description=_(u'The minimum amount of special characters that a '
+                      u'password must have.'),
+        required=False,
+        default=None)
+
+    #WARNING! generating a password with Others is... not always sane
+    #think twice before you use it
+    minOthers = zope.schema.Int(
+        title=_(u'Minimum Number of Other characters'),
+        description=_(u'The minimum amount of other characters that a '
+                      u'password must have.'),
+        required=False,
+        default=None)
+
+    @zope.interface.invariant
+    def saneMinimums(task):
+        minl = 0
+        if task.minLowerLetter:
+            if task.minLowerLetter > task.groupMax:
+                raise zope.interface.Invalid(
+                    u"Any group minimum length must NOT be greater than "
+                    u"the maximum group length.")
+
+            minl += task.minLowerLetter
+        if task.minUpperLetter:
+            if task.minUpperLetter > task.groupMax:
+                raise zope.interface.Invalid(
+                    u"Any group minimum length must NOT be greater than "
+                    u"the maximum group length.")
+
+            minl += task.minUpperLetter
+        if task.minDigits:
+            if task.minDigits > task.groupMax:
+                raise zope.interface.Invalid(
+                    u"Any group minimum length must NOT be greater than "
+                    u"the maximum group length.")
+
+            minl += task.minDigits
+        if task.minSpecials:
+            if task.minSpecials > task.groupMax:
+                raise zope.interface.Invalid(
+                    u"Any group minimum length must NOT be greater than "
+                    u"the maximum group length.")
+
+            minl += task.minSpecials
+        if task.minOthers:
+            if task.minOthers > task.groupMax:
+                raise zope.interface.Invalid(
+                    u"Any group minimum length must NOT be greater than "
+                    u"the maximum group length.")
+
+            minl += task.minOthers
+
+        #if task.minLength is not None:
+        #    if minl > task.minLength:
+        #        raise zope.interface.Invalid(
+        #            u"Sum of group minimum lengths must NOT be greater than "
+        #            u"the minimum length.")
+
+        if task.maxLength is not None:
+            if minl > task.maxLength:
+                raise zope.interface.Invalid(
+                    u"Sum of group minimum lengths must NOT be greater than "
+                    u"the maximum password length.")
+
+
 class IPasswordOptionsUtility(zope.interface.Interface):
     """Different general security options.
 

Modified: z3c.password/branches/adamg-evenhigher/src/z3c/password/password.py
===================================================================
--- z3c.password/branches/adamg-evenhigher/src/z3c/password/password.py	2009-06-19 09:45:51 UTC (rev 101135)
+++ z3c.password/branches/adamg-evenhigher/src/z3c/password/password.py	2009-06-19 12:19:21 UTC (rev 101136)
@@ -54,6 +54,16 @@
         interfaces.IHighSecurityPasswordUtility['groupMax'])
     maxSimilarity = FieldProperty(
         interfaces.IHighSecurityPasswordUtility['maxSimilarity'])
+    minLowerLetter = FieldProperty(
+        interfaces.IHighSecurityPasswordUtility['minLowerLetter'])
+    minUpperLetter = FieldProperty(
+        interfaces.IHighSecurityPasswordUtility['minUpperLetter'])
+    minDigits = FieldProperty(
+        interfaces.IHighSecurityPasswordUtility['minDigits'])
+    minSpecials = FieldProperty(
+        interfaces.IHighSecurityPasswordUtility['minSpecials'])
+    minOthers = FieldProperty(
+        interfaces.IHighSecurityPasswordUtility['minOthers'])
 
     LOWERLETTERS = string.letters[:26]
     UPPERLETTERS = string.letters[26:]
@@ -72,6 +82,11 @@
         self.maxSimilarity = maxSimilarity
         self.random = random.Random(seed or time.time())
 
+    def _checkSimilarity(self, new, ref):
+        sm = difflib.SequenceMatcher(None, new, ref)
+        if sm.ratio() > self.maxSimilarity:
+            raise interfaces.TooSimilarPassword()
+
     def verify(self, new, ref=None):
         '''See interfaces.IHighSecurityPasswordUtility'''
         # 0. Make sure we got a password.
@@ -85,9 +100,7 @@
         # 2. Ensure that the password is sufficiently different to the old
         #    one.
         if ref is not None:
-            sm = difflib.SequenceMatcher(None, new, ref)
-            if sm.ratio() > self.maxSimilarity:
-                raise interfaces.TooSimilarPassword()
+            self._checkSimilarity(new, ref)
         # 3. Ensure that the password's character set is complex enough.
         num_lower_letters = 0
         num_upper_letters = 0
@@ -111,8 +124,33 @@
             num_specials > self.groupMax or
             num_others > self.groupMax):
             raise interfaces.TooManyGroupCharacters()
+
+        if (self.minLowerLetter is not None
+            and num_lower_letters < self.minLowerLetter):
+            raise interfaces.TooFewGroupCharacters()
+
+        if (self.minUpperLetter is not None
+            and num_upper_letters < self.minUpperLetter):
+            raise interfaces.TooFewGroupCharacters()
+
+        if (self.minDigits is not None
+            and num_digits < self.minDigits):
+            raise interfaces.TooFewGroupCharacters()
+
+        if (self.minSpecials is not None
+            and num_specials < self.minSpecials):
+            raise interfaces.TooFewGroupCharacters()
+
+        if (self.minOthers is not None
+            and num_others < self.minOthers):
+            raise interfaces.TooFewGroupCharacters()
+
         return
 
+    def _randomOther(self):
+        #override if you want an other range
+        return unichr(self.random.randint(0x0a1, 0x0ff))
+
     def generate(self, ref=None):
         '''See interfaces.IHighSecurityPasswordUtility'''
         verified = False
@@ -120,11 +158,32 @@
             new = ''
             # Determine the length of the password
             length = self.random.randint(self.minLength, self.maxLength)
+
             # Generate the password
             chars = self.LOWERLETTERS + self.UPPERLETTERS + \
                     self.DIGITS + self.SPECIALS
+
+            if (self.minOthers is not None
+                and self.minOthers > 0):
+                # unichr(0x0ffff) is a placeholder for Others
+                # this is deliberately this way, because a unicode
+                # range of 0x0a1...0x010ffff is rather a big string
+                chars += unichr(0x0ffff)
+
             for count in xrange(length):
                 new += self.random.choice(chars)
+
+            if (self.minOthers is not None
+                and self.minOthers > 0):
+                # replace now placeholders with random other characters
+                newest = ''
+                for c in new:
+                    if c == unichr(0x0ffff):
+                        newest += self._randomOther()
+                    else:
+                        newest += c
+                new = newest
+
             # Verify the new password
             try:
                 self.verify(new, ref)



More information about the Checkins mailing list