Skip to content

Commit

Permalink
let's see how this works
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Groszer committed Jan 29, 2010
1 parent fa97a84 commit 68b7af1
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 4 deletions.
12 changes: 12 additions & 0 deletions src/z3c/password/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ def __init__(self, principal):
self.principal = principal
Exception.__init__(self, self.__doc__)

TML_CHECK_ALL = 'all'
TML_CHECK_NONRESOURCE = 'nonres'
TML_CHECK_POSTONLY = 'post'

class AccountLocked(Exception):
__doc__ = _('The account is locked, because the password was '
'entered incorrectly too often.')
Expand Down Expand Up @@ -302,6 +306,14 @@ class IPasswordOptionsUtility(zope.interface.Interface):
required=False,
default=None)

failedAttemptCheck = zope.schema.Choice(
title=_(u'Failed password check method'),
description=_(u'Failed password check method. '
'All requests, non-reqource requests, POST requests.'),
required=False,
values=[TML_CHECK_ALL, TML_CHECK_NONRESOURCE, TML_CHECK_POSTONLY],
default=TML_CHECK_ALL )

disallowPasswordReuse = zope.schema.Bool(
title=_(u'Disallow Password Reuse'),
description=_(u'Do not allow to set a previously set password again.'),
Expand Down
60 changes: 57 additions & 3 deletions src/z3c/password/principal.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import datetime
import persistent.list
import zope.component
from zope.security.management import getInteraction

from z3c.password import interfaces

class PrincipalMixIn(object):
Expand All @@ -31,6 +33,7 @@ class PrincipalMixIn(object):
#e.g. for changePasswordOnNextLogin

failedAttempts = 0
failedAttemptCheck = interfaces.TML_CHECK_ALL
maxFailedAttempts = None
lastFailedAttempt = None
lockOutPeriod = None
Expand Down Expand Up @@ -115,9 +118,7 @@ def checkPassword(self, pwd, ignoreExpiration=False, ignoreFailures=False):
add = 0
else:
#failed attempt, record it, increase counter
self.failedAttempts += 1
self.lastFailedAttempt = self.now()
add = 1
add = self.checkFailedAttempt()

# If the maximum amount of failures has been reached notify the
# system by raising an error.
Expand All @@ -132,6 +133,46 @@ def checkPassword(self, pwd, ignoreExpiration=False, ignoreFailures=False):

return same

def _getRequest(self):
interaction = getInteraction()
try:
return interaction.participations[0]
except IndexError:
return None

def checkFailedAttempt(self):
#failed attempt, record it, increase counter
#(in case we have to)
validRequest = True
fac = self._failedAttemptCheck()
if fac == interfaces.TML_CHECK_ALL:
validRequest = True
else:
request = self._getRequest()
if request is None:
validRequest = True
else:
if fac == interfaces.TML_CHECK_NONRESOURCE:
url = request.getURL()
if '/@@/' in url:
#this is a resource
validRequest = False
else:
validRequest = True
elif fac == interfaces.TML_CHECK_POSTONLY:
if request.method == 'POST':
#this is a POST request
validRequest = True
else:
validRequest = False

if validRequest:
self.failedAttempts += 1
self.lastFailedAttempt = self.now()
return 1
else:
return 0

def tooManyLoginFailures(self, add = 0):
attempts = self._maxFailedAttempts()
#this one needs to be >=, because... data just does not
Expand Down Expand Up @@ -199,6 +240,19 @@ def _lockOutPeriod(self):
else:
return self.lockOutPeriod

def _failedAttemptCheck(self):
if self.failedAttemptCheck is not None:
return self.failedAttemptCheck

options = self._optionsUtility()
if options is None:
return self.failedAttemptCheck
else:
if options.failedAttemptCheck is not None:
return options.failedAttemptCheck
else:
return self.failedAttemptCheck

def _maxFailedAttempts(self):
if self.maxFailedAttempts is not None:
return self.maxFailedAttempts
Expand Down
96 changes: 95 additions & 1 deletion src/z3c/password/principal.txt
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ and it's not None then the principal's property takes priority.
Let's now create a principal:

>>> from zope.app.authentication import principalfolder
>>> from z3c.password import interfaces
>>> from z3c.password import principal

>>> class MyPrincipal(principal.PrincipalMixIn,
Expand All @@ -130,6 +131,9 @@ The good password validates fine:
>>> user.checkPassword('123123')
True

failedAttempts
--------------

Initially, the amount of failed attempts is zero, ...

>>> user.failedAttempts
Expand Down Expand Up @@ -180,6 +184,97 @@ Let's now reset the failure count.

>>> user.failedAttempts = 0


failedAttempts, non-resource
----------------------------

>>> import zope.security.management
>>> from z3c.password import testing

>>> user.failedAttemptCheck = interfaces.TML_CHECK_NONRESOURCE

>>> request = testing.TestBrowserRequest('http://localhost/@@/logo.gif')

>>> zope.security.management.getInteraction().add(request)

>>> user.failedAttempts
0

>>> user.checkPassword('456456')
False

>>> user.failedAttempts
0

>>> zope.security.management.getInteraction().remove(request)

>>> request = testing.TestBrowserRequest('http://localhost/loginform.html',
... 'POST')

>>> zope.security.management.getInteraction().add(request)

>>> user.checkPassword('456456')
False

>>> user.failedAttempts
1

>>> user.failedAttempts = 0

>>> zope.security.management.getInteraction().remove(request)

>>> user.checkPassword('456456')
False

>>> user.failedAttempts
1

failedAttempts, POST
--------------------

>>> user.failedAttempts = 0

>>> user.failedAttemptCheck = interfaces.TML_CHECK_POSTONLY

>>> request = testing.TestBrowserRequest('http://localhost/index.html', 'GET')

>>> zope.security.management.getInteraction().add(request)

>>> user.failedAttempts
0

>>> user.checkPassword('456456')
False

>>> user.failedAttempts
0

>>> zope.security.management.getInteraction().remove(request)

>>> request = testing.TestBrowserRequest('http://localhost/loginform.html',
... 'POST')

>>> zope.security.management.getInteraction().add(request)

>>> user.checkPassword('456456')
False

>>> user.failedAttempts
1

>>> user.failedAttempts = 0

>>> zope.security.management.getInteraction().remove(request)

>>> user.checkPassword('456456')
False

>>> user.failedAttempts
1

expired password
----------------

Next we expire the password:

>>> NOW = datetime.datetime(2009, 6, 14, 13, 0) + datetime.timedelta(181)
Expand Down Expand Up @@ -220,7 +315,6 @@ To check the new features we need a utility that provides the options.

>>> import zope.interface
>>> import zope.component
>>> from z3c.password import interfaces
>>> from z3c.password.password import PasswordOptionsUtility
>>> poptions = PasswordOptionsUtility()
>>> zope.component.provideUtility(poptions)
Expand Down
12 changes: 12 additions & 0 deletions src/z3c/password/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ def setUp(test):

def tearDown(test):
placelesssetup.tearDown(test)


class TestBrowserRequest():
"""pretty dumb test request"""

def __init__(self, url, method='GET'):
self.URL = url
self.method = method
self.interaction = None

def getURL(self):
return self.URL

0 comments on commit 68b7af1

Please sign in to comment.