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.
If not valid return a negative code to indicate failure.
Returns the list of messages in case a password does not comply with the policy
"""
from Products.Formulator.Errors import ValidationError
from Products.ERP5Type.Message import translateString
from DateTime import DateTime
import re
MARKER = ['', None]
result_code_list = []
def addError(error_message):
result_code_list.append(translateString(error_message))
portal = context.getPortalObject()
request = context.REQUEST
is_temp_object = context.isTempObject()
result_code_list = []
min_password_length = portal.portal_preferences.getPreferredMinPasswordLength()
if password is None:
......@@ -22,7 +24,7 @@ if password is None:
# not long enough
if min_password_length is not None:
if len(password) < min_password_length:
result_code_list.append(-1)
addError('Too short.')
# password contain X out of following Y regular expression groups ?
regular_expression_list = portal.portal_preferences.getPreferredRegularExpressionGroupList()
......@@ -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))
if group_counter < min_regular_expression_group_number:
# not enough groups match
result_code_list.append(-2)
addError('Not complex enough.')
if not is_temp_object:
# not changed in last period ?
......@@ -57,13 +59,13 @@ if not is_temp_object:
min_password_lifetime_duration is not None and \
(last_password_modification_date + min_password_lifetime_duration*one_hour) > now:
# too early to change password
result_code_list.append(-3)
addError('You have changed your password too recently.')
# not already used before ?
preferred_number_of_last_password_to_check = portal.portal_preferences.getPreferredNumberOfLastPasswordToCheck()
if preferred_number_of_last_password_to_check not in [None, 0]:
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)
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 \
(last_name not in MARKER and last_name in lower_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
......@@ -6,19 +6,11 @@ from Products.Formulator.Errors import ValidationError
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):
# raise so Formulator shows proper message
result_code_list = login.analyzePassword(password)
if result_code_list!=[]:
translateString = context.Base_translateString
message = ' '.join([translateString(message_dict[x]) for x in result_code_list])
result_message_list = login.analyzePassword(password)
if result_message_list:
message = u' '.join([str(x) for x in result_message_list])
raise ValidationError('external_validator_failed', context, error_text=message)
return 1
......
......@@ -63,8 +63,11 @@ class ILoginAccountProvider(Interface):
def analyzePassword(password, **kw):
"""
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):
......
......@@ -89,8 +89,11 @@ class LoginAccountProviderMixin:
def analyzePassword(self, password, **kw):
"""
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')
return method(password, **kw)
......
......@@ -307,8 +307,8 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.tic()
self._clearCache()
self.assertEqual([-1], login.analyzePassword(''))
self.assertEqual([-1], login.analyzePassword('1234567'))
self.assertEqual(['Too short.'], [str(msg) for msg in login.analyzePassword('')])
self.assertEqual(['Too short.'], [str(msg) for msg in login.analyzePassword('1234567')])
self.assertTrue(login.isPasswordValid('12345678'))
# not changed in last x days
......@@ -323,8 +323,12 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.tic()
# if we try to change now we should fail with any password
self.assertSameSet([-3], login.analyzePassword('87654321'))
self.assertSameSet([-1, -3], login.analyzePassword('short')) # multiple failures
self.assertEqual(
['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.assertRaises(ValueError, login.setPassword, '87654321')
......@@ -342,7 +346,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
login.setPassword('12345678-new')
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.assertTrue(login.isPasswordValid('12345678_')) # it's OK with another one not used yet
for password in ['a','b','c','d', 'e', 'f']:
......@@ -361,9 +368,15 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.assertTrue(login.isPasswordValid('b'))
self.assertTrue(login.isPasswordValid('c'))
# only last 3 (including current one are invalid)
self.assertSameSet([-4], login.analyzePassword('d'))
self.assertSameSet([-4], login.analyzePassword('e'))
self.assertSameSet([-4], login.analyzePassword('f'))
self.assertEqual(
['You have already used this password.'],
[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
preference.setPreferredNumberOfLastPasswordToCheck(None)
......@@ -381,7 +394,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self.assertTrue(login.isPasswordValid('c'))
self.assertTrue(login.isPasswordValid('d'))
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.setPreferredMinPasswordLength(7)
......@@ -402,7 +417,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for password in four_group_password_list:
self.assertTrue(login.isPasswordValid(password))
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
preference.setPreferredMinRegularExpressionGroupNumber(3)
......@@ -412,7 +429,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for password in four_group_password_list + three_group_password_list:
self.assertTrue(login.isPasswordValid(password))
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
preference.setPreferredMinRegularExpressionGroupNumber(2)
......@@ -421,7 +440,9 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for password in four_group_password_list + three_group_password_list + two_group_password_list:
self.assertTrue(login.isPasswordValid(password))
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
preference.setPreferredMinRegularExpressionGroupNumber(1)
......@@ -434,8 +455,12 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
preference.setPrefferedForceUsernameCheckInPassword(1)
self._clearCache()
self.tic()
self.assertSameSet([-5], login.analyzePassword('abAB#12_%s' %person.getFirstName()))
self.assertSameSet([-5], login.analyzePassword('abAB#12_%s' %person.getLastName()))
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.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)
self._clearCache()
self.tic()
......@@ -455,8 +480,10 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
self._clearCache()
self.tic()
# 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.assertSameSet([], temp_person.Login_analyzePassword('longEnough1'))
self.assertEqual(
['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)
preference.setPreferredRegularExpressionGroupList(regular_expression_list)
......@@ -467,20 +494,26 @@ class TestAuthenticationPolicy(ERP5TypeTestCase):
for password in four_group_password_list + three_group_password_list:
self.assertSameSet([], temp_person.Login_analyzePassword(password))
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)
preference.setPrefferedForceUsernameCheckInPassword(1)
self._clearCache()
self.tic()
self.assertSameSet([-5], temp_person.Login_analyzePassword('abAB#12_%s' %first_name))
self.assertSameSet([-5], temp_person.Login_analyzePassword('abAB#12_%s' %last_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' %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)
self._clearCache()
self.tic()
self.assertSameSet([], 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' %first_name))
self.assertEqual([], temp_person.Login_analyzePassword('abAB#12_%s' %last_name))
# check Base_isPasswordValid is able to work in Anonymous User fashion
# 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