[Checkins] SVN: z3c.password/ Import new password utility that can
be used to implement high-security
Stephan Richter
srichter at cosmos.phy.tufts.edu
Tue Oct 17 14:08:13 EDT 2006
Log message for revision 70759:
Import new password utility that can be used to implement high-security
passwords. We use this for a bank we are working with.
Changed:
A z3c.password/
A z3c.password/branches/
A z3c.password/tags/
A z3c.password/trunk/
A z3c.password/trunk/src/
A z3c.password/trunk/src/__init__.py
A z3c.password/trunk/src/z3c/
A z3c.password/trunk/src/z3c/__init__.py
A z3c.password/trunk/src/z3c/password/
A z3c.password/trunk/src/z3c/password/DEPENDENCIES.txt
A z3c.password/trunk/src/z3c/password/README.txt
A z3c.password/trunk/src/z3c/password/__init__.py
A z3c.password/trunk/src/z3c/password/field.py
A z3c.password/trunk/src/z3c/password/interfaces.py
A z3c.password/trunk/src/z3c/password/password.py
A z3c.password/trunk/src/z3c/password/principal.py
A z3c.password/trunk/src/z3c/password/tests.py
-=-
Added: z3c.password/trunk/src/__init__.py
===================================================================
--- z3c.password/trunk/src/__init__.py 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/__init__.py 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1 @@
+# Make a package.
Property changes on: z3c.password/trunk/src/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: z3c.password/trunk/src/z3c/__init__.py
===================================================================
--- z3c.password/trunk/src/z3c/__init__.py 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/z3c/__init__.py 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1 @@
+# Make a package.
Property changes on: z3c.password/trunk/src/z3c/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: z3c.password/trunk/src/z3c/password/DEPENDENCIES.txt
===================================================================
--- z3c.password/trunk/src/z3c/password/DEPENDENCIES.txt 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/z3c/password/DEPENDENCIES.txt 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1 @@
+zope.app.authentication
Property changes on: z3c.password/trunk/src/z3c/password/DEPENDENCIES.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.password/trunk/src/z3c/password/README.txt
===================================================================
--- z3c.password/trunk/src/z3c/password/README.txt 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/z3c/password/README.txt 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1,283 @@
+============================
+Advnaced Password Management
+============================
+
+This package provides an API and implementation of a password generation and
+verification utility. A high-security implementation is provided that is
+suitable for banks and other high-security institutions. The package also
+offers a field and a property for those fields.
+
+The Password Utility
+--------------------
+
+The password utilities are located in the ``password`` module.
+
+ >>> from z3c.password import password
+
+The module provides a trivial sample and a high-security implementation of the
+utility. The password utility provides two methods.
+
+ >>> pwd = password.TrivialPasswordUtility()
+
+The first is to verify the password. The first argument is the new password
+and the second, optional argument a reference password, usually the old
+one. When the verification fails an ``InvalidPassword`` exception is raised,
+otherwise the method simply returns. In the case of the trivial password
+utility, this method always returns:
+
+ >>> pwd.verify('foo')
+ >>> pwd.verify('foobar', 'foo')
+
+The second method generates a password conform to the security constraints of
+the password utility. The trivial password utility always returns the password
+"trivial".
+
+ >>> pwd.generate()
+ 'trivial'
+ >>> pwd.generate('foo')
+ 'trivial'
+
+The ``generate()`` method also accepts the optional reference
+password. Finally, each password utility must provide a description explaining
+its security constraints:
+
+ >>> print pwd.description
+ All passwords are accepted and always the "trivial" password is generated.
+
+Let's now look at the high-security password utility. In its constructor you
+can specify several constraints; the minimum and maximum length of the
+password, the maximum of characters of one group (lower letters, upper
+letters, digits, punctuation, other), and the maximum similarity score.
+
+ >>> pwd = password.HighSecurityPasswordUtility()
+ >>> pwd.minLength
+ 8
+ >>> pwd.maxLength
+ 12
+ >>> pwd.groupMax
+ 6
+ >>> pwd.maxSimilarity
+ 0.59999999999999998
+
+- When the password is empty, then the password is invalid:
+
+ >>> pwd.verify(None)
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: No new password specified.
+
+ >>> pwd.verify('')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: No new password specified.
+
+ >>> pwd.verify('', 'other')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: No new password specified.
+
+- Next, it is verified that the password has the correct length:
+
+ >>> pwd.verify('foo')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: New password is too long or too short.
+
+ >>> pwd.verify('foobar-foobar')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: New password is too long or too short.
+
+ >>> pwd.verify('fooBar12')
+
+- Once the length is verified, the password is checked for similarity. If no
+ reference password is provided, then this check always passes:
+
+ >>> pwd.verify('fooBar12')
+
+ >>> pwd.verify('fooBar12', 'fooBAR--')
+
+ >>> pwd.verify('fooBar12', 'foobar12')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: New password too similar to old one.
+
+- The final check ensures that the password does not have too many characters
+ of one group. The groups are: lower letters, upper letters, digits,
+ punctuation, and others.
+
+ >>> pwd.verify('fooBarBlah')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: New password contains too many characters of one group.
+
+ >>> pwd.verify('FOOBARBlah')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: New password contains too many characters of one group.
+
+ >>> pwd.verify('12345678')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: New password contains too many characters of one group.
+
+ >>> pwd.verify('........')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: New password contains too many characters of one group.
+
+Let's now verify a list of password that were provided by a bank:
+
+ >>> for new in ('K7PzX2JZ', 'DznMLIww', 'ks59Ursq', 'YUcsuIrQ', 'bPEUFGSa',
+ ... 'lUmtG0TP', 'ISfUKoTe', 'NKGY0aIJ', 'XyUuSHX4', 'CaFE1R5p'):
+ ... pwd.verify(new)
+
+Let's now generate some passwords. To make them predictable, we specify a seed
+when initializing the utility:
+
+ >>> pwd = password.HighSecurityPasswordUtility(seed=8)
+
+ >>> pwd.generate()
+ '{l%ix~t8R'
+ >>> pwd.generate()
+ 'Us;iwbzM[J'
+
+
+The Password Field
+------------------
+
+The password field can be used to specify an advanced password. It extends the
+standard ``zope.schema`` password field with the ``checker`` attribute. The
+checker is either a password utility (as specified above) or the name of sucha
+a utility. The checker is used to verify whether a password is acceptable or
+not.
+
+Let's now create the field:
+
+ >>> import datetime
+ >>> from zope.app.authentication.password import PlainTextPasswordManager
+ >>> from z3c.password import field
+
+ >>> pwdField = field.Password(
+ ... __name__='password',
+ ... title=u'Password',
+ ... checker=pwd)
+
+Let's validate a value:
+
+ >>> pwdField.validate(u'fooBar12')
+ >>> pwdField.validate(u'fooBar')
+ Traceback (most recent call last):
+ ...
+ InvalidPassword: New password is too long or too short.
+
+
+The Principal Mix-in
+--------------------
+
+The principal mixin is a quick and functional example on how to use the
+password utility and field. The mix-in class defines the following additional
+attributes:
+
+
+- ``passwordExpiresAfter``
+
+ A time delta object that describes for how long the password is valid before
+ a new one has to be specified. If ``None``, the password will never expire.
+
+- ``maxFailedAttempts``
+
+ An integer specifying the amount of failed attempts allowed to check the
+ password before the password is locked and no new password can be provided.
+
+- ``passwordSetOn``
+
+ The date/time at which the password was last set. This value is used to
+ determine the expiration of a password.
+
+- ``failedAttempts``
+
+ This is a counter that keeps track of the amount of failed login attempts
+ since the last successful one. This value is used to determine when to lock
+ the account after the maximum amount of failures has been reached.
+
+Let's now create a principal:
+
+ >>> from zope.app.authentication import principalfolder
+ >>> from z3c.password import principal
+
+ >>> class MyPrincipal(principal.PrincipalMixIn,
+ ... principalfolder.InternalPrincipal):
+ ... pass
+
+ >>> user = MyPrincipal('srichter', '123123', u'Stephan Richter')
+
+Since the password has been immediately set, the ```passwordSetOn`` attribute
+should have a value:
+
+ >>> user.passwordSetOn
+ datetime.datetime(...)
+
+Initially, the amount of failed attempts is zero, ...
+
+ >>> user.failedAttempts
+ 0
+
+but after checking the password incorrectly, the value is updated:
+
+ >>> user.checkPassword('456456')
+ False
+ >>> user.failedAttempts
+ 1
+
+Initially there is no constraint on user, but let's add some:
+
+ >>> user.passwordExpiresAfter
+ >>> user.passwordExpiresAfter = datetime.timedelta(180)
+
+ >>> user.maxFailedAttempts
+ >>> user.maxFailedAttempts = 3
+
+Let's now provide the incorrect password a couple more times:
+
+ >>> user.checkPassword('456456')
+ False
+ >>> user.checkPassword('456456')
+ False
+ >>> user.checkPassword('456456')
+ Traceback (most recent call last):
+ ...
+ TooManyLoginFailures
+
+As you can see, once the maximum mount of attempts is reached, the system does
+not allow you to log in at all anymore. At this point the password has to be
+reset otherwise. However, you can tell the ``check()`` method explicitly to
+ignore the failure count:
+
+ >>> user.checkPassword('456456', ignoreFailures=True)
+ False
+
+Let's now reset the failure count.
+
+ >>> user.failedAttempts = 0
+
+Next we expire password:
+
+ >>> user.passwordSetOn = datetime.datetime.now() + datetime.timedelta(-181)
+
+A corresponding exception should be raised:
+
+ >>> user.checkPassword('456456')
+ Traceback (most recent call last):
+ ...
+ PasswordExpired
+
+Like for the too-many-failures exception above, you can explicitely turn off
+the expiration check:
+
+ >>> user.checkPassword('456456', ignoreExpiration=True)
+ False
+
+It is the responsibility of the presentation code to provide views for those
+two exceptions. For the latter, it is common to allow the user to enter a new
+password after providing the old one as verification.
Property changes on: z3c.password/trunk/src/z3c/password/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.password/trunk/src/z3c/password/__init__.py
===================================================================
--- z3c.password/trunk/src/z3c/password/__init__.py 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/z3c/password/__init__.py 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1 @@
+# Make a package.
Property changes on: z3c.password/trunk/src/z3c/password/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: z3c.password/trunk/src/z3c/password/field.py
===================================================================
--- z3c.password/trunk/src/z3c/password/field.py 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/z3c/password/field.py 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1,43 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Password Field Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.component
+import zope.schema
+from z3c.password import interfaces
+
+class Password(zope.schema.Password):
+
+ def __init__(self, checker=None, **kw):
+ self._checker = checker
+ super(Password, self).__init__(**kw)
+
+ @property
+ def checker(self):
+ if self._checker is None:
+ return None
+ if not isinstance(self._checker, (str, unicode)):
+ return self._checker
+ return zope.component.getUtility(
+ interfaces.IPasswordUtility, self._checker)
+
+ def validate(self, value):
+ super(Password, self).validate(value)
+ old = None
+ if self.context is not None:
+ old = self.get(self.context)
+ self.checker.verify(value, old)
Property changes on: z3c.password/trunk/src/z3c/password/field.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: z3c.password/trunk/src/z3c/password/interfaces.py
===================================================================
--- z3c.password/trunk/src/z3c/password/interfaces.py 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/z3c/password/interfaces.py 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1,120 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Password Utility Interfaces
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import zope.interface
+import zope.schema
+from zope.exceptions.interfaces import UserError
+
+class InvalidPassword(zope.schema.ValidationError):
+ """Invalid Password"""
+
+ def __init__(self, reason):
+ self.reason = reason
+ Exception.__init__(self)
+
+ def __str__(self):
+ return self.reason.encode('utf-8')
+
+
+class PasswordExpired(UserError):
+ """The password has expired."""
+
+ def __init__(self, principal):
+ self.principal = principal
+ UserError.__init__(self)
+
+
+class TooManyLoginFailures(UserError):
+ """The password was entered incorrectly too often."""
+
+ def __init__(self, principal):
+ self.principal = principal
+ UserError.__init__(self)
+
+
+class IPasswordUtility(zope.interface.Interface):
+ """Component to verify and generate passwords.
+
+ The purpose of this utility is to make common password-related tasks, such
+ as verification and creation simple. However, only the collection of those
+ utilites provide an overall net worth.
+ """
+
+ description = zope.schema.Text(
+ title=u'Description',
+ description=u'A description of the password utility.',
+ required=False)
+
+ def verify(new, ref=None):
+ """Check whether the new password is valid.
+
+ When a passward is good, the method simply returns, otherwise an
+ ``InvalidPassword`` exception is raised.
+
+ It is up to the implementation to define the semantics of a valid
+ password. The sematics should ideally be described in the description.
+
+ The ``ref`` argument is a reference password. In many scenarios it
+ will be the old password, so that the method can ensure sufficient
+ dissimilarity between the new and old password.
+ """
+
+ def generate(ref=None):
+ """Generate a valid password.
+
+ The ``ref`` argument is a reference password. In many scenarios it
+ will be the old password, so that the method can ensure sufficient
+ dissimilarity between the new and old password.
+ """
+
+
+class IHighSecurityPasswordUtility(IPasswordUtility):
+ """A password utility for very secure passwords."""
+
+ minLength = zope.schema.Int(
+ title=u'Minimum Length',
+ description=u'The minimum length of the password.',
+ required=False,
+ default=None)
+
+ maxLength = zope.schema.Int(
+ title=u'Maximum Length',
+ description=u'The maximum length of the password.',
+ required=False,
+ default=None)
+
+ @zope.interface.invariant
+ def minMaxLength(task):
+ if task.minLength is not None and task.maxLength is not None:
+ if task.minLength > task.minLength:
+ raise zope.interface.Invalid(
+ u"Minimum lnegth must be greater than the maximum length.")
+
+ groupMax = zope.schema.Int(
+ title=u'Maximum Characters of Group',
+ description=u'The maximum amount of characters that a password can '
+ u'have from one group. The groups are: digits, letters, '
+ u'punctuation.',
+ required=False,
+ default=None)
+
+ maxSimilarity = zope.schema.Float(
+ title=u'Old/New Similarity',
+ description=u'',
+ required=False,
+ default=None)
Property changes on: z3c.password/trunk/src/z3c/password/interfaces.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: z3c.password/trunk/src/z3c/password/password.py
===================================================================
--- z3c.password/trunk/src/z3c/password/password.py 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/z3c/password/password.py 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1,136 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Password Utility Implementation
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import difflib
+import random
+import string
+import time
+import zope.interface
+from zope.schema.fieldproperty import FieldProperty
+
+from z3c.password import interfaces
+
+
+class TrivialPasswordUtility(object):
+ """A trivial password utility."""
+ zope.interface.implements(interfaces.IPasswordUtility)
+
+ description = (u'All passwords are accepted and always the "trivial" '
+ u'password is generated.')
+
+ def verify(self, new, ref=None):
+ '''See interfaces.IPasswordUtility'''
+ return
+
+ def generate(self, ref=None):
+ '''See interfaces.IPasswordUtility'''
+ return 'trivial'
+
+
+class HighSecurityPasswordUtility(object):
+ """An implementation of the high-security password API."""
+ zope.interface.implements(interfaces.IHighSecurityPasswordUtility)
+
+ minLength = FieldProperty(
+ interfaces.IHighSecurityPasswordUtility['minLength'])
+ maxLength = FieldProperty(
+ interfaces.IHighSecurityPasswordUtility['maxLength'])
+ groupMax = FieldProperty(
+ interfaces.IHighSecurityPasswordUtility['groupMax'])
+ maxSimilarity = FieldProperty(
+ interfaces.IHighSecurityPasswordUtility['maxSimilarity'])
+
+ LOWERLETTERS = string.letters[:26]
+ UPPERLETTERS = string.letters[26:]
+ DIGITS = string.digits
+ SPECIALS = string.punctuation
+
+ description = (u'Passwords generated and verified by this utility conform '
+ u'strictly to the specified parameters. See the interface '
+ u'for more details.')
+
+ def __init__(self, minLength=8, maxLength=12, groupMax=6,
+ maxSimilarity=0.6, seed=None):
+ self.minLength = minLength
+ self.maxLength = maxLength
+ self.groupMax = groupMax
+ self.maxSimilarity = maxSimilarity
+ self.random = random.Random(seed or time.time())
+
+ def verify(self, new, ref=None):
+ '''See interfaces.IHighSecurityPasswordUtility'''
+ # 0. Make sure we got a password.
+ if not new:
+ raise interfaces.InvalidPassword(u'No new password specified.')
+ # 1. Make sure the password has the right length.
+ if len(new) < self.minLength or len(new) > self.maxLength:
+ raise interfaces.InvalidPassword(
+ u'New password is too long or too short.')
+ # 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.InvalidPassword(
+ u'New password too similar to old one.')
+ # 3. Ensure that the password's character set is complex enough.
+ num_lower_letters = 0
+ num_upper_letters = 0
+ num_digits = 0
+ num_specials = 0
+ num_others = 0
+ for char in new:
+ if char in self.LOWERLETTERS:
+ num_lower_letters += 1
+ elif char in self.UPPERLETTERS:
+ num_upper_letters += 1
+ elif char in self.DIGITS:
+ num_digits += 1
+ elif char in self.SPECIALS:
+ num_specials += 1
+ else:
+ num_others += 1
+ if (num_lower_letters > self.groupMax or
+ num_upper_letters > self.groupMax or
+ num_digits > self.groupMax or
+ num_specials > self.groupMax or
+ num_others > self.groupMax):
+ raise interfaces.InvalidPassword(
+ u'New password contains too many characters of one group.')
+ return
+
+ def generate(self, ref=None):
+ '''See interfaces.IHighSecurityPasswordUtility'''
+ verified = False
+ while not verified:
+ 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
+ for count in xrange(length):
+ new += self.random.choice(chars)
+ # Verify the new password
+ try:
+ self.verify(new, ref)
+ except interfaces.InvalidPassword:
+ verified = False
+ else:
+ verified = True
+ return new
Property changes on: z3c.password/trunk/src/z3c/password/password.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: z3c.password/trunk/src/z3c/password/principal.py
===================================================================
--- z3c.password/trunk/src/z3c/password/principal.py 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/z3c/password/principal.py 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1,60 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Principal MixIn for Advnaced Password Management
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+import datetime
+from z3c.password import interfaces
+
+class PrincipalMixIn(object):
+ """A Principal Mixin class for ``zope.app.principalfolder``'s internal
+ principal."""
+
+ passwordExpiresAfter = None
+ maxFailedAttempts = None
+
+ passwordSetOn = None
+ failedAttempts = 0
+
+ def getPassword(self):
+ return super(PrincipalMixIn, self).getPassword()
+
+ def setPassword(self, password, passwordManagerName=None):
+ super(PrincipalMixIn, self).setPassword(password, passwordManagerName)
+ self.passwordSetOn = datetime.datetime.now()
+
+ password = property(getPassword, setPassword)
+
+ def checkPassword(self, pwd, ignoreExpiration=False, ignoreFailures=False):
+ # Make sure the password has not been expired
+ if not ignoreExpiration and self.passwordExpiresAfter is not None:
+ expirationDate = self.passwordSetOn + self.passwordExpiresAfter
+ if expirationDate < datetime.datetime.now():
+ raise interfaces.PasswordExpired(self)
+ # Check the password
+ same = super(PrincipalMixIn, self).checkPassword(pwd)
+ # If this was a failed attempt, record it, otherwise reset the failures
+ if same and self.failedAttempts != 0:
+ self.failedAttempts = 0
+ if not same:
+ self.failedAttempts += 1
+ # If the maximum amount of failures has been reached notify the system
+ # by sending an event and then raising an error.
+ if not ignoreFailures and self.maxFailedAttempts is not None:
+ if (self.maxFailedAttempts and
+ self.failedAttempts > self.maxFailedAttempts):
+ raise interfaces.TooManyLoginFailures(self)
+ return same
Property changes on: z3c.password/trunk/src/z3c/password/principal.py
___________________________________________________________________
Name: svn:keywords
+ Id
Added: z3c.password/trunk/src/z3c/password/tests.py
===================================================================
--- z3c.password/trunk/src/z3c/password/tests.py 2006-10-17 17:04:10 UTC (rev 70758)
+++ z3c.password/trunk/src/z3c/password/tests.py 2006-10-17 18:08:12 UTC (rev 70759)
@@ -0,0 +1,44 @@
+##############################################################################
+#
+# Copyright (c) 2006 Lovely Systems and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test Setup
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import doctest
+import unittest
+import zope.component
+from zope.testing.doctestunit import DocFileSuite
+from zope.app.authentication import password
+from zope.app.testing import placelesssetup
+
+def setUp(test):
+ placelesssetup.setUp(test)
+ zope.component.provideUtility(
+ password.PlainTextPasswordManager(), name='Plain Text')
+
+def tearDown(test):
+ placelesssetup.tearDown(test)
+
+def test_suite():
+ return unittest.TestSuite((
+ DocFileSuite('README.txt',
+ setUp=setUp, tearDown=tearDown,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: z3c.password/trunk/src/z3c/password/tests.py
___________________________________________________________________
Name: svn:keywords
+ Id
More information about the Checkins
mailing list