Commit 238be699 authored by Ivan Tyagov's avatar Ivan Tyagov

Define isAuthenticationPolicyEnabled API and use it respectively.

Allow low level password validity checks (if
isAuthenticationPolicyEnabled only).
Adjust tests to handle better password history.
parent 161e5c9c
......@@ -42,6 +42,11 @@ class IEncryptedPassword(Interface):
Check the password, usefull when changing password
"""
def checkPasswordValueAcceptable(value):
"""
Check if the password value is acceptable - i.e. follows site rules.
"""
def setEncodedPassword(value, format='default'):
"""
Set an already encoded password.
......
......@@ -60,4 +60,5 @@ class ILoginAccountProvider(Interface):
Return if password has already been used.
"""
\ No newline at end of file
......@@ -63,6 +63,19 @@ class EncryptedPasswordMixin:
return pw_validate(self.getPassword(), value)
return False
def checkPasswordValueAcceptable(self, value):
"""
Check the password. This method is defined explicitly, because:
- we want to apply an authentication policy which itself may contain explicit password rules
"""
if not self.getPortalObject().portal_preferences.isAuthenticationPolicyEnabled():
# not a policy so basically all passwords are accceptable
return True
result = self.isPasswordValid(value)
if result <= 0:
raise ValueError, "Bad password (%s)." %result
def checkUserCanChangePassword(self):
if not _checkPermission(Permissions.SetOwnPassword, self):
raise AccessControl_Unauthorized('setPassword')
......@@ -87,6 +100,7 @@ class EncryptedPasswordMixin:
def _setPassword(self, value):
self.checkUserCanChangePassword()
self.checkPasswordValueAcceptable(value)
self._forceSetPassword(value)
security.declarePublic('setPassword')
......
......@@ -77,7 +77,7 @@ class LoginAccountProviderMixin:
Return if password has already been used.
"""
preferred_number_of_last_password_to_check = self.portal_preferences.getPreferredNumberOfLastPasswordToCheck()
password_list = self.getLastChangedPasswordValueList() + [self.getPassword()]
password_list = self.getLastChangedPasswordValueList()
password_list.reverse()
for encoded_password in password_list[:preferred_number_of_last_password_to_check]:
if pw_validate(encoded_password, password):
......
......@@ -71,6 +71,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
last_name = 'Last',
**kw)
person.validate()
assignment = person.newContent(portal_type = 'Assignment')
assignment.open()
# Setup auth policy
preference = portal.portal_preferences.newContent(
......@@ -78,7 +80,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
title = 'Authentication',
preferred_max_authentication_failure = 3,
preferred_authentication_failure_check_duration = 600,
preferred_authentication_failure_block_duration = 600)
preferred_authentication_failure_block_duration = 600,
preferred_authentication_policy_enabled = True)
preference.enable()
self.stepTic()
......@@ -97,7 +100,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
Test that a recataloging works for Web Site documents
"""
portal = self.getPortal()
self.assertTrue(portal.ERP5Site_isAuthenticationPolicyEnabled())
self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test')
......@@ -116,7 +119,6 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for i in range (0, 1000):
self.assertEqual(3, len(person.notifyLoginFailure()))
#import pdb; pdb.set_trace()
self.assertTrue(person.isLoginBlocked())
# set check back interval to actualy disable blocking
......@@ -155,7 +157,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
Test password history.
"""
portal = self.getPortal()
self.assertTrue(portal.ERP5Site_isAuthenticationPolicyEnabled())
self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test')
......@@ -170,23 +172,58 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self._clearCache()
before = DateTime()
old_password = person.getPassword()
person.setPassword('12345678')
self.stepTic()
# password change date should be saved as well hashed old password value
old_password = person.getPassword()
self.assertTrue(person.getLastPasswordModificationDate() > before)
self.assertEqual([old_password], person.getLastChangedPasswordValueList())
self.assertEqual([old_password], person.getLastChangedPasswordValueList())
# .. test one more time to check history of password is saved in a list
before = DateTime()
old_password1 = person.getPassword()
person.setPassword('123456789')
self.stepTic()
old_password1 = person.getPassword()
# password change date should be saved as well hashed old password value
self.assertTrue(person.getLastPasswordModificationDate() > before)
self.assertEqual([old_password, old_password1], person.getLastChangedPasswordValueList())
# other methods (_setPassword)...
before = DateTime()
person._setPassword('123456789-1')
self.stepTic()
old_password2 = person.getPassword()
self.assertTrue(person.getLastPasswordModificationDate() > before)
self.assertEqual([old_password, old_password1, old_password2], person.getLastChangedPasswordValueList())
# other methods (_forceSetPassword)...
before = DateTime()
person._forceSetPassword('123456789-2')
self.stepTic()
old_password3 = person.getPassword()
self.assertTrue(person.getLastPasswordModificationDate() > before)
self.assertEqual([old_password, old_password1, old_password2, old_password3], person.getLastChangedPasswordValueList())
# other methods (setEncodedPassword)...
before = DateTime()
person.setEncodedPassword('123456789-3')
self.stepTic()
old_password4 = person.getPassword()
self.assertTrue(person.getLastPasswordModificationDate() > before)
self.assertEqual([old_password, old_password1, old_password2, old_password3, old_password4], \
person.getLastChangedPasswordValueList())
# other methods (edit)...
before = DateTime()
person.edit(password = '123456789-4')
self.stepTic()
old_password5 = person.getPassword()
self.assertTrue(person.getLastPasswordModificationDate() > before)
self.assertEqual([old_password, old_password1, old_password2, old_password3, old_password4, old_password5], \
person.getLastChangedPasswordValueList())
def test_03_PasswordValidity(self):
"""
......@@ -201,7 +238,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
'([\\\\$\\\\!\\\\#\\\\%]+)' # (!, $, #, %)
]
self.assertTrue(portal.ERP5Site_isAuthenticationPolicyEnabled())
self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test')
......@@ -228,12 +265,15 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
preference.setPreferredNumberOfLastPasswordToCheck(3)
self.stepTic()
self._clearCache()
self.assertTrue(person.isPasswordValid('12345678'))
self.assertEqual(1, person.isPasswordValid('12345678'))
person.setPassword('12345678')
self.stepTic()
self.assertEqual(-3, person.isPasswordValid('87654321')) # if we try to change now we should fail with any password
# if we try to change now we should fail with any password
self.assertEqual(-3, person.isPasswordValid('87654321'))
self.assertRaises(ValueError, person.setPassword, '87654321')
preference.setPreferredMinPasswordLifetimeDuration(0) # remove restriction
self.stepTic()
self._clearCache()
......@@ -242,40 +282,47 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
# password not used in previous X passwords
preference.setPreferredMinPasswordLength(None) # disable for now
self._cleanUpPerson(person)
self._clearCache()
person.setPassword('12345678')
self._clearCache()
self.stepTic()
person.setPassword('12345678-new')
self.stepTic()
self.assertEqual(-4, person.isPasswordValid('12345678')) # if we try to change now we should fail with this EXACT password
self.assertEqual(-4, person.isPasswordValid('12345678-new')) # if we try to change now we should fail with this EXACT password
self.assertRaises(ValueError, person.setPassword, '12345678-new')
self.assertTrue(person.isPasswordValid('12345678_')) # it's OK with another one not used yet
for password in ['a','b','c','d', 'e']:
for password in ['a','b','c','d', 'e', 'f']:
person.setPassword(password)
self.stepTic()
self.assertTrue(person.isPasswordValid('12345678'))
self.assertTrue(person.isPasswordValid('a'))
self.assertTrue(person.isPasswordValid('b'))
self.assertEqual(1, person.isPasswordValid('12345678-new'))
self.assertEqual(1, person.isPasswordValid('a'))
self.assertEqual(1, person.isPasswordValid('b'))
# only last 3 (including current one are invalid)
self.assertEqual(-4, person.isPasswordValid('c'))
#import pdb; pdb.set_trace()
self.assertEqual(-4, person.isPasswordValid('d'))
self.assertEqual(-4, person.isPasswordValid('e'))
self.assertEqual(-4, person.isPasswordValid('e'))
self.assertEqual(-4, person.isPasswordValid('f'))
# if we remove restricted then all password are usable
preference.setPreferredNumberOfLastPasswordToCheck(None)
self._clearCache()
self.stepTic()
self.assertTrue(person.isPasswordValid('c'))
self.assertTrue(person.isPasswordValid('d'))
self.assertTrue(person.isPasswordValid('e'))
self.assertEqual(1, person.isPasswordValid('d'))
self.assertEqual(1, person.isPasswordValid('e'))
self.assertEqual(1, person.isPasswordValid('f'))
# if we set only last password to check
preference.setPreferredNumberOfLastPasswordToCheck(1)
self._clearCache()
self.stepTic()
self.assertTrue(person.isPasswordValid('c'))
self.assertTrue(person.isPasswordValid('d'))
self.assertEqual(-4, person.isPasswordValid('e'))
self.assertEqual(1, person.isPasswordValid('c'))
self.assertEqual(1, person.isPasswordValid('d'))
self.assertEqual(1, person.isPasswordValid('e'))
self.assertEqual(-4, person.isPasswordValid('f'))
preference.setPreferredRegularExpressionGroupList(regular_expression_list)
preference.setPreferredMinPasswordLength(7)
preference.setPreferredNumberOfLastPasswordToCheck(None)
......@@ -383,7 +430,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
portal = self.getPortal()
request = self.app.REQUEST
self.assertTrue(portal.ERP5Site_isAuthenticationPolicyEnabled())
self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test')
......@@ -426,7 +473,20 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.assertFalse(person.isPasswordExpired())
#import pdb; pdb.set_trace()
self.assertFalse(request['is_user_account_password_expired_warning_on'])
def test_05_HttpResponse(self):
"""
Check HTTP responses
"""
portal = self.getPortal()
request = self.app.REQUEST
person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test')
# XXX: finish
path = portal.absolute_url_path() + '/view?__ac_name=%s&__ac_password=%s' %('test', 'test')
response = self.publish(path)
print path
print response
def test_suite():
suite = unittest.TestSuite()
......
......@@ -271,5 +271,31 @@ class PreferenceTool(BaseTool):
finally:
setSecurityManager(security_manager)
security.declarePublic('isAuthenticationPolicyEnabled')
def isAuthenticationPolicyEnabled(self) :
"""
Return True if authentication policy is enabled.
This method exists here due to bootstrap issues.
It should work even if erp5_authentication_policy bt5 is not installed.
"""
# XXX: define an interface
def _isAuthenticationPolicyEnabled():
portal_preferences = self.getPortalObject().portal_preferences
method_id = 'isPreferredAuthenticationPolicyEnabled'
method = getattr(self, method_id, None)
if method is not None and method():
return True
return False
tv = getTransactionalVariable()
tv_key = 'PreferenceTool._isAuthenticationPolicyEnabled.%s' % getSecurityManager().getUser()
if tv.get(tv_key, None) is None:
_isAuthenticationPolicyEnabled = CachingMethod(_isAuthenticationPolicyEnabled,
id='PortalPreferences_isAuthenticationPolicyEnabled',
cache_factory='erp5_content_short')
tv[tv_key] = _isAuthenticationPolicyEnabled()
return tv[tv_key]
InitializeClass(PreferenceTool)
......@@ -171,15 +171,12 @@ class ERP5UserManager(BasePlugin):
except _AuthenticationFailure:
authentication_result = None
method = getattr(self, 'ERP5Site_isAuthenticationPolicyEnabled', None)
if method is None or (method is not None and not method()):
if not self.getPortalObject().portal_preferences.isAuthenticationPolicyEnabled():
# stop here, no authentication policy enabled
# so just return authentication check result
# XXX: move to ERP5 Site API
return authentication_result
# authentication policy enabled, we need person object anyway
# XXX: every request is a MySQL call
user_list = self.getUserByLogin(credentials.get('login'))
if not user_list:
# not an ERP5 Person object
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment