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): ...@@ -42,6 +42,11 @@ class IEncryptedPassword(Interface):
Check the password, usefull when changing password 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'): def setEncodedPassword(value, format='default'):
""" """
Set an already encoded password. Set an already encoded password.
......
...@@ -60,4 +60,5 @@ class ILoginAccountProvider(Interface): ...@@ -60,4 +60,5 @@ class ILoginAccountProvider(Interface):
Return if password has already been used. Return if password has already been used.
""" """
\ No newline at end of file
...@@ -63,6 +63,19 @@ class EncryptedPasswordMixin: ...@@ -63,6 +63,19 @@ class EncryptedPasswordMixin:
return pw_validate(self.getPassword(), value) return pw_validate(self.getPassword(), value)
return False 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): def checkUserCanChangePassword(self):
if not _checkPermission(Permissions.SetOwnPassword, self): if not _checkPermission(Permissions.SetOwnPassword, self):
raise AccessControl_Unauthorized('setPassword') raise AccessControl_Unauthorized('setPassword')
...@@ -87,6 +100,7 @@ class EncryptedPasswordMixin: ...@@ -87,6 +100,7 @@ class EncryptedPasswordMixin:
def _setPassword(self, value): def _setPassword(self, value):
self.checkUserCanChangePassword() self.checkUserCanChangePassword()
self.checkPasswordValueAcceptable(value)
self._forceSetPassword(value) self._forceSetPassword(value)
security.declarePublic('setPassword') security.declarePublic('setPassword')
......
...@@ -77,7 +77,7 @@ class LoginAccountProviderMixin: ...@@ -77,7 +77,7 @@ class LoginAccountProviderMixin:
Return if password has already been used. Return if password has already been used.
""" """
preferred_number_of_last_password_to_check = self.portal_preferences.getPreferredNumberOfLastPasswordToCheck() preferred_number_of_last_password_to_check = self.portal_preferences.getPreferredNumberOfLastPasswordToCheck()
password_list = self.getLastChangedPasswordValueList() + [self.getPassword()] password_list = self.getLastChangedPasswordValueList()
password_list.reverse() password_list.reverse()
for encoded_password in password_list[:preferred_number_of_last_password_to_check]: for encoded_password in password_list[:preferred_number_of_last_password_to_check]:
if pw_validate(encoded_password, password): if pw_validate(encoded_password, password):
......
...@@ -71,6 +71,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -71,6 +71,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
last_name = 'Last', last_name = 'Last',
**kw) **kw)
person.validate() person.validate()
assignment = person.newContent(portal_type = 'Assignment')
assignment.open()
# Setup auth policy # Setup auth policy
preference = portal.portal_preferences.newContent( preference = portal.portal_preferences.newContent(
...@@ -78,7 +80,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -78,7 +80,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
title = 'Authentication', title = 'Authentication',
preferred_max_authentication_failure = 3, preferred_max_authentication_failure = 3,
preferred_authentication_failure_check_duration = 600, 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() preference.enable()
self.stepTic() self.stepTic()
...@@ -97,7 +100,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -97,7 +100,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
Test that a recataloging works for Web Site documents Test that a recataloging works for Web Site documents
""" """
portal = self.getPortal() portal = self.getPortal()
self.assertTrue(portal.ERP5Site_isAuthenticationPolicyEnabled()) self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.portal_catalog.getResultValue(portal_type = 'Person', person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test') reference = 'test')
...@@ -116,7 +119,6 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -116,7 +119,6 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for i in range (0, 1000): for i in range (0, 1000):
self.assertEqual(3, len(person.notifyLoginFailure())) self.assertEqual(3, len(person.notifyLoginFailure()))
#import pdb; pdb.set_trace()
self.assertTrue(person.isLoginBlocked()) self.assertTrue(person.isLoginBlocked())
# set check back interval to actualy disable blocking # set check back interval to actualy disable blocking
...@@ -155,7 +157,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -155,7 +157,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
Test password history. Test password history.
""" """
portal = self.getPortal() portal = self.getPortal()
self.assertTrue(portal.ERP5Site_isAuthenticationPolicyEnabled()) self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.portal_catalog.getResultValue(portal_type = 'Person', person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test') reference = 'test')
...@@ -170,23 +172,58 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -170,23 +172,58 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self._clearCache() self._clearCache()
before = DateTime() before = DateTime()
old_password = person.getPassword()
person.setPassword('12345678') person.setPassword('12345678')
self.stepTic() self.stepTic()
# password change date should be saved as well hashed old password value # password change date should be saved as well hashed old password value
old_password = person.getPassword()
self.assertTrue(person.getLastPasswordModificationDate() > before) 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 # .. test one more time to check history of password is saved in a list
before = DateTime() before = DateTime()
old_password1 = person.getPassword()
person.setPassword('123456789') person.setPassword('123456789')
self.stepTic() self.stepTic()
old_password1 = person.getPassword()
# password change date should be saved as well hashed old password value # password change date should be saved as well hashed old password value
self.assertTrue(person.getLastPasswordModificationDate() > before) self.assertTrue(person.getLastPasswordModificationDate() > before)
self.assertEqual([old_password, old_password1], person.getLastChangedPasswordValueList()) 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): def test_03_PasswordValidity(self):
""" """
...@@ -201,7 +238,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -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', person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test') reference = 'test')
...@@ -228,12 +265,15 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -228,12 +265,15 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
preference.setPreferredNumberOfLastPasswordToCheck(3) preference.setPreferredNumberOfLastPasswordToCheck(3)
self.stepTic() self.stepTic()
self._clearCache() self._clearCache()
self.assertTrue(person.isPasswordValid('12345678'))
self.assertEqual(1, person.isPasswordValid('12345678'))
person.setPassword('12345678') person.setPassword('12345678')
self.stepTic() 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 preference.setPreferredMinPasswordLifetimeDuration(0) # remove restriction
self.stepTic() self.stepTic()
self._clearCache() self._clearCache()
...@@ -242,40 +282,47 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -242,40 +282,47 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
# password not used in previous X passwords # password not used in previous X passwords
preference.setPreferredMinPasswordLength(None) # disable for now preference.setPreferredMinPasswordLength(None) # disable for now
self._cleanUpPerson(person) self._cleanUpPerson(person)
self._clearCache() self._clearCache()
person.setPassword('12345678') self.stepTic()
person.setPassword('12345678-new')
self.stepTic() 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 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) person.setPassword(password)
self.stepTic() self.stepTic()
self.assertTrue(person.isPasswordValid('12345678')) self.assertEqual(1, person.isPasswordValid('12345678-new'))
self.assertTrue(person.isPasswordValid('a')) self.assertEqual(1, person.isPasswordValid('a'))
self.assertTrue(person.isPasswordValid('b')) self.assertEqual(1, person.isPasswordValid('b'))
# only last 3 (including current one are invalid) # 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('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 # if we remove restricted then all password are usable
preference.setPreferredNumberOfLastPasswordToCheck(None) preference.setPreferredNumberOfLastPasswordToCheck(None)
self._clearCache() self._clearCache()
self.stepTic() self.stepTic()
self.assertTrue(person.isPasswordValid('c')) self.assertEqual(1, person.isPasswordValid('d'))
self.assertTrue(person.isPasswordValid('d')) self.assertEqual(1, person.isPasswordValid('e'))
self.assertTrue(person.isPasswordValid('e')) self.assertEqual(1, person.isPasswordValid('f'))
# if we set only last password to check # if we set only last password to check
preference.setPreferredNumberOfLastPasswordToCheck(1) preference.setPreferredNumberOfLastPasswordToCheck(1)
self._clearCache() self._clearCache()
self.stepTic() self.stepTic()
self.assertTrue(person.isPasswordValid('c')) self.assertEqual(1, person.isPasswordValid('c'))
self.assertTrue(person.isPasswordValid('d')) self.assertEqual(1, person.isPasswordValid('d'))
self.assertEqual(-4, person.isPasswordValid('e')) self.assertEqual(1, person.isPasswordValid('e'))
self.assertEqual(-4, person.isPasswordValid('f'))
preference.setPreferredRegularExpressionGroupList(regular_expression_list) preference.setPreferredRegularExpressionGroupList(regular_expression_list)
preference.setPreferredMinPasswordLength(7) preference.setPreferredMinPasswordLength(7)
preference.setPreferredNumberOfLastPasswordToCheck(None) preference.setPreferredNumberOfLastPasswordToCheck(None)
...@@ -383,7 +430,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -383,7 +430,7 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
portal = self.getPortal() portal = self.getPortal()
request = self.app.REQUEST request = self.app.REQUEST
self.assertTrue(portal.ERP5Site_isAuthenticationPolicyEnabled()) self.assertTrue(portal.portal_preferences.isAuthenticationPolicyEnabled())
person = portal.portal_catalog.getResultValue(portal_type = 'Person', person = portal.portal_catalog.getResultValue(portal_type = 'Person',
reference = 'test') reference = 'test')
...@@ -426,7 +473,20 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -426,7 +473,20 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.assertFalse(person.isPasswordExpired()) self.assertFalse(person.isPasswordExpired())
#import pdb; pdb.set_trace() #import pdb; pdb.set_trace()
self.assertFalse(request['is_user_account_password_expired_warning_on']) 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(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
......
...@@ -271,5 +271,31 @@ class PreferenceTool(BaseTool): ...@@ -271,5 +271,31 @@ class PreferenceTool(BaseTool):
finally: finally:
setSecurityManager(security_manager) 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) InitializeClass(PreferenceTool)
...@@ -171,15 +171,12 @@ class ERP5UserManager(BasePlugin): ...@@ -171,15 +171,12 @@ class ERP5UserManager(BasePlugin):
except _AuthenticationFailure: except _AuthenticationFailure:
authentication_result = None authentication_result = None
method = getattr(self, 'ERP5Site_isAuthenticationPolicyEnabled', None) if not self.getPortalObject().portal_preferences.isAuthenticationPolicyEnabled():
if method is None or (method is not None and not method()):
# stop here, no authentication policy enabled # stop here, no authentication policy enabled
# so just return authentication check result # so just return authentication check result
# XXX: move to ERP5 Site API
return authentication_result return authentication_result
# authentication policy enabled, we need person object anyway # authentication policy enabled, we need person object anyway
# XXX: every request is a MySQL call
user_list = self.getUserByLogin(credentials.get('login')) user_list = self.getUserByLogin(credentials.get('login'))
if not user_list: if not user_list:
# not an ERP5 Person object # 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