Commit 288bffbe authored by Jérome Perrin's avatar Jérome Perrin

login_account_provider: return directly messages in analyzePassword

This is a change in the API, previously only "messages id" were
returned. To make it easier to reuse analyzePassword in other contexts,
we now return the message directly.
parent cd6ff527
""" """
Returns if password is valid or not. Returns the list of messages in case a password does not comply with the policy
If not valid return a negative code to indicate failure.
""" """
from Products.Formulator.Errors import ValidationError from Products.ERP5Type.Message import translateString
from DateTime import DateTime from DateTime import DateTime
import re import re
MARKER = ['', None] MARKER = ['', None]
result_code_list = []
def addError(error_message):
result_code_list.append(translateString(error_message))
portal = context.getPortalObject() portal = context.getPortalObject()
request = context.REQUEST request = context.REQUEST
is_temp_object = context.isTempObject() is_temp_object = context.isTempObject()
result_code_list = []
min_password_length = portal.portal_preferences.getPreferredMinPasswordLength() min_password_length = portal.portal_preferences.getPreferredMinPasswordLength()
if password is None: if password is None:
...@@ -22,7 +24,7 @@ if password is None: ...@@ -22,7 +24,7 @@ if password is None:
# not long enough # not long enough
if min_password_length is not None: if min_password_length is not None:
if len(password) < min_password_length: if len(password) < min_password_length:
result_code_list.append(-1) addError('Too short.')
# password contain X out of following Y regular expression groups ? # password contain X out of following Y regular expression groups ?
regular_expression_list = portal.portal_preferences.getPreferredRegularExpressionGroupList() regular_expression_list = portal.portal_preferences.getPreferredRegularExpressionGroupList()
...@@ -36,7 +38,7 @@ if regular_expression_list: ...@@ -36,7 +38,7 @@ if regular_expression_list:
#context.log('%s %s %s %s' %(password, group_counter, min_regular_expression_group_number, regular_expression_list)) #context.log('%s %s %s %s' %(password, group_counter, min_regular_expression_group_number, regular_expression_list))
if group_counter < min_regular_expression_group_number: if group_counter < min_regular_expression_group_number:
# not enough groups match # not enough groups match
result_code_list.append(-2) addError('Not complex enough.')
if not is_temp_object: if not is_temp_object:
# not changed in last period ? # not changed in last period ?
...@@ -57,13 +59,13 @@ if not is_temp_object: ...@@ -57,13 +59,13 @@ if not is_temp_object:
min_password_lifetime_duration is not None and \ min_password_lifetime_duration is not None and \
(last_password_modification_date + min_password_lifetime_duration*one_hour) > now: (last_password_modification_date + min_password_lifetime_duration*one_hour) > now:
# too early to change password # too early to change password
result_code_list.append(-3) addError('You have changed your password too recently.')
# not already used before ? # not already used before ?
preferred_number_of_last_password_to_check = portal.portal_preferences.getPreferredNumberOfLastPasswordToCheck() preferred_number_of_last_password_to_check = portal.portal_preferences.getPreferredNumberOfLastPasswordToCheck()
if preferred_number_of_last_password_to_check not in [None, 0]: if preferred_number_of_last_password_to_check not in [None, 0]:
if context.isPasswordAlreadyUsed(password): if context.isPasswordAlreadyUsed(password):
result_code_list.append(-4) addError('You have already used this password.')
# not contain the full name of the user in password or any parts of it (i.e. last and / or first name) # not contain the full name of the user in password or any parts of it (i.e. last and / or first name)
if portal.portal_preferences.isPrefferedForceUsernameCheckInPassword(): if portal.portal_preferences.isPrefferedForceUsernameCheckInPassword():
...@@ -85,6 +87,6 @@ if portal.portal_preferences.isPrefferedForceUsernameCheckInPassword(): ...@@ -85,6 +87,6 @@ if portal.portal_preferences.isPrefferedForceUsernameCheckInPassword():
if (first_name not in MARKER and first_name in lower_password) or \ if (first_name not in MARKER and first_name in lower_password) or \
(last_name not in MARKER and last_name in lower_password): (last_name not in MARKER and last_name in lower_password):
# user's name must not be contained in password # user's name must not be contained in password
result_code_list.append(-5) addError('You can not use any parts of your first and last name in password.')
return result_code_list return result_code_list
...@@ -6,19 +6,11 @@ from Products.Formulator.Errors import ValidationError ...@@ -6,19 +6,11 @@ from Products.Formulator.Errors import ValidationError
portal = context.getPortalObject() portal = context.getPortalObject()
message_dict = { 0: 'Unknown error',
-1: 'Too short.',
-2: 'Not complex enough.',
-3: 'You have changed your password too recently.',
-4: 'You have already used this password.',
-5: 'You can not use any parts of your first and last name in password.'}
def doValidation(login, password): def doValidation(login, password):
# raise so Formulator shows proper message # raise so Formulator shows proper message
result_code_list = login.analyzePassword(password) result_message_list = login.analyzePassword(password)
if result_code_list!=[]: if result_message_list:
translateString = context.Base_translateString message = u' '.join([str(x) for x in result_message_list])
message = ' '.join([translateString(message_dict[x]) for x in result_code_list])
raise ValidationError('external_validator_failed', context, error_text=message) raise ValidationError('external_validator_failed', context, error_text=message)
return 1 return 1
......
...@@ -63,8 +63,11 @@ class ILoginAccountProvider(Interface): ...@@ -63,8 +63,11 @@ class ILoginAccountProvider(Interface):
def analyzePassword(password, **kw): def analyzePassword(password, **kw):
""" """
Analyze password validity. Analyze password validity.
Return status code indicating if password is acceptable and if not status code
for reason for not being a valid one (i.e. too short, not complex, etc ...) Returns a list of Products.ERP5Type.Message.Message instances describing
the reason for this password not to be valid (too short, not complex).
If password is valid, the returned list is empty.
""" """
def isPasswordAlreadyUsed(password): def isPasswordAlreadyUsed(password):
......
...@@ -89,8 +89,11 @@ class LoginAccountProviderMixin: ...@@ -89,8 +89,11 @@ class LoginAccountProviderMixin:
def analyzePassword(self, password, **kw): def analyzePassword(self, password, **kw):
""" """
Analyze password validity. Analyze password validity.
Return status code indicating if password is acceptable and if not status code
for reason for not being a valid one (i.e. too short, not complex, etc ...) Returns a list of Products.ERP5Type.Message.Message instances describing
the reason for this password not to be valid (too short, not complex).
If password is valid, the returned list is empty.
""" """
method = self._getTypeBasedMethod('analyzePassword') method = self._getTypeBasedMethod('analyzePassword')
return method(password, **kw) return method(password, **kw)
......
...@@ -307,8 +307,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -307,8 +307,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.tic() self.tic()
self._clearCache() self._clearCache()
self.assertEqual([-1], login.analyzePassword('')) self.assertEqual(['Too short.'], [str(msg) for msg in login.analyzePassword('')])
self.assertEqual([-1], login.analyzePassword('1234567')) self.assertEqual(['Too short.'], [str(msg) for msg in login.analyzePassword('1234567')])
self.assertTrue(login.isPasswordValid('12345678')) self.assertTrue(login.isPasswordValid('12345678'))
# not changed in last x days # not changed in last x days
...@@ -323,8 +323,12 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -323,8 +323,12 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.tic() self.tic()
# 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.assertSameSet([-3], login.analyzePassword('87654321')) self.assertEqual(
self.assertSameSet([-1, -3], login.analyzePassword('short')) # multiple failures ['You have changed your password too recently.'],
[str(msg) for msg in login.analyzePassword('87654321')])
self.assertSameSet(
['Too short.', 'You have changed your password too recently.'],
[str(msg) for msg in login.analyzePassword('short')]) # multiple failures
self.assertFalse(login.isPasswordValid('short')) # multiple failures self.assertFalse(login.isPasswordValid('short')) # multiple failures
self.assertRaises(ValueError, login.setPassword, '87654321') self.assertRaises(ValueError, login.setPassword, '87654321')
...@@ -342,7 +346,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -342,7 +346,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
login.setPassword('12345678-new') login.setPassword('12345678-new')
self.tic() self.tic()
self.assertSameSet([-4], login.analyzePassword('12345678-new')) # if we try to change now we should fail with this EXACT password # if we try to change now we should fail with this EXACT password
self.assertEqual(
['You have already used this password.'],
[str(msg) for msg in login.analyzePassword('12345678-new')])
self.assertRaises(ValueError, login.setPassword, '12345678-new') self.assertRaises(ValueError, login.setPassword, '12345678-new')
self.assertTrue(login.isPasswordValid('12345678_')) # it's OK with another one not used yet self.assertTrue(login.isPasswordValid('12345678_')) # it's OK with another one not used yet
for password in ['a','b','c','d', 'e', 'f']: for password in ['a','b','c','d', 'e', 'f']:
...@@ -361,9 +368,15 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -361,9 +368,15 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.assertTrue(login.isPasswordValid('b')) self.assertTrue(login.isPasswordValid('b'))
self.assertTrue(login.isPasswordValid('c')) self.assertTrue(login.isPasswordValid('c'))
# only last 3 (including current one are invalid) # only last 3 (including current one are invalid)
self.assertSameSet([-4], login.analyzePassword('d')) self.assertEqual(
self.assertSameSet([-4], login.analyzePassword('e')) ['You have already used this password.'],
self.assertSameSet([-4], login.analyzePassword('f')) [str(msg) for msg in login.analyzePassword('d')])
self.assertEqual(
['You have already used this password.'],
[str(msg) for msg in login.analyzePassword('e')])
self.assertEqual(
['You have already used this password.'],
[str(msg) for msg in login.analyzePassword('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)
...@@ -381,7 +394,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -381,7 +394,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.assertTrue(login.isPasswordValid('c')) self.assertTrue(login.isPasswordValid('c'))
self.assertTrue(login.isPasswordValid('d')) self.assertTrue(login.isPasswordValid('d'))
self.assertTrue(login.isPasswordValid('e')) self.assertTrue(login.isPasswordValid('e'))
self.assertSameSet([-4], login.analyzePassword('f')) self.assertEqual(
['You have already used this password.'],
[str(msg) for msg in login.analyzePassword('f')])
preference.setPreferredRegularExpressionGroupList(regular_expression_list) preference.setPreferredRegularExpressionGroupList(regular_expression_list)
preference.setPreferredMinPasswordLength(7) preference.setPreferredMinPasswordLength(7)
...@@ -402,7 +417,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -402,7 +417,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for password in four_group_password_list: for password in four_group_password_list:
self.assertTrue(login.isPasswordValid(password)) self.assertTrue(login.isPasswordValid(password))
for password in three_group_password_list+two_group_password_list + one_group_password_list: for password in three_group_password_list+two_group_password_list + one_group_password_list:
self.assertSameSet([-2], login.analyzePassword(password)) self.assertEqual(
['Not complex enough.'],
[str(msg) for msg in login.analyzePassword(password)])
# min 3 out of all groups # min 3 out of all groups
preference.setPreferredMinRegularExpressionGroupNumber(3) preference.setPreferredMinRegularExpressionGroupNumber(3)
...@@ -412,7 +429,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -412,7 +429,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for password in four_group_password_list + three_group_password_list: for password in four_group_password_list + three_group_password_list:
self.assertTrue(login.isPasswordValid(password)) self.assertTrue(login.isPasswordValid(password))
for password in two_group_password_list + one_group_password_list: for password in two_group_password_list + one_group_password_list:
self.assertSameSet([-2], login.analyzePassword(password)) self.assertEqual(
['Not complex enough.'],
[str(msg) for msg in login.analyzePassword(password)])
# min 2 out of all groups # min 2 out of all groups
preference.setPreferredMinRegularExpressionGroupNumber(2) preference.setPreferredMinRegularExpressionGroupNumber(2)
...@@ -421,7 +440,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -421,7 +440,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for password in four_group_password_list + three_group_password_list + two_group_password_list: for password in four_group_password_list + three_group_password_list + two_group_password_list:
self.assertTrue(login.isPasswordValid(password)) self.assertTrue(login.isPasswordValid(password))
for password in one_group_password_list: for password in one_group_password_list:
self.assertSameSet([-2], login.analyzePassword(password)) self.assertEqual(
['Not complex enough.'],
[str(msg) for msg in login.analyzePassword(password)])
# min 1 out of all groups # min 1 out of all groups
preference.setPreferredMinRegularExpressionGroupNumber(1) preference.setPreferredMinRegularExpressionGroupNumber(1)
...@@ -434,8 +455,12 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -434,8 +455,12 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
preference.setPrefferedForceUsernameCheckInPassword(1) preference.setPrefferedForceUsernameCheckInPassword(1)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertSameSet([-5], login.analyzePassword('abAB#12_%s' %person.getFirstName())) self.assertEqual(
self.assertSameSet([-5], login.analyzePassword('abAB#12_%s' %person.getLastName())) ['You can not use any parts of your first and last name in password.'],
[str(msg) for msg in person.Login_analyzePassword('abAB#12_%s' %person.getFirstName())])
self.assertEqual(
['You can not use any parts of your first and last name in password.'],
[str(msg) for msg in person.Login_analyzePassword('abAB#12_%s' %person.getLastName())])
preference.setPrefferedForceUsernameCheckInPassword(0) preference.setPrefferedForceUsernameCheckInPassword(0)
self._clearCache() self._clearCache()
self.tic() self.tic()
...@@ -455,8 +480,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -455,8 +480,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self._clearCache() self._clearCache()
self.tic() self.tic()
# in this case which is basically used in new account creation only length of password matters # in this case which is basically used in new account creation only length of password matters
self.assertSameSet([-1], temp_person.Login_analyzePassword('onlyNine1')) self.assertEqual(
self.assertSameSet([], temp_person.Login_analyzePassword('longEnough1')) ['Too short.'],
[str(msg) for msg in temp_person.Login_analyzePassword('onlyNine1')])
self.assertEqual([], temp_person.Login_analyzePassword('longEnough1'))
# make sure re check works on temp as well ( i.e. min 3 out of all groups) # make sure re check works on temp as well ( i.e. min 3 out of all groups)
preference.setPreferredRegularExpressionGroupList(regular_expression_list) preference.setPreferredRegularExpressionGroupList(regular_expression_list)
...@@ -467,20 +494,26 @@ class TestAuthenticationPolicy(ERP5TypeTestCase): ...@@ -467,20 +494,26 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for password in four_group_password_list + three_group_password_list: for password in four_group_password_list + three_group_password_list:
self.assertSameSet([], temp_person.Login_analyzePassword(password)) self.assertSameSet([], temp_person.Login_analyzePassword(password))
for password in two_group_password_list + one_group_password_list: for password in two_group_password_list + one_group_password_list:
self.assertSameSet([-2], temp_person.Login_analyzePassword(password)) self.assertEqual(
['Not complex enough.'],
[str(msg) for msg in temp_person.Login_analyzePassword(password)])
# make sure peron's check on username works on temp as well (i.e. not contain the full name of the user) # make sure peron's check on username works on temp as well (i.e. not contain the full name of the user)
preference.setPrefferedForceUsernameCheckInPassword(1) preference.setPrefferedForceUsernameCheckInPassword(1)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertSameSet([-5], temp_person.Login_analyzePassword('abAB#12_%s' %first_name)) self.assertEqual(
self.assertSameSet([-5], temp_person.Login_analyzePassword('abAB#12_%s' %last_name)) ['You can not use any parts of your first and last name in password.'],
[str(msg) for msg in temp_person.Login_analyzePassword('abAB#12_%s' %first_name)])
self.assertEqual(
['You can not use any parts of your first and last name in password.'],
[str(msg) for msg in temp_person.Login_analyzePassword('abAB#12_%s' %last_name)])
preference.setPrefferedForceUsernameCheckInPassword(0) preference.setPrefferedForceUsernameCheckInPassword(0)
self._clearCache() self._clearCache()
self.tic() self.tic()
self.assertSameSet([], temp_person.Login_analyzePassword('abAB#12_%s' %first_name)) self.assertEqual([], temp_person.Login_analyzePassword('abAB#12_%s' %first_name))
self.assertSameSet([], temp_person.Login_analyzePassword('abAB#12_%s' %last_name)) self.assertEqual([], temp_person.Login_analyzePassword('abAB#12_%s' %last_name))
# check Base_isPasswordValid is able to work in Anonymous User fashion # check Base_isPasswordValid is able to work in Anonymous User fashion
# but with already create Person object (i.e. recover password case) # but with already create Person object (i.e. recover password case)
......
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