ldap: provide user-friendlier password policy error messages (#54185)
gitea/authentic/pipeline/head Build started...
Details
gitea/authentic/pipeline/head Build started...
Details
This commit is contained in:
parent
7408e493a2
commit
246b299fef
|
@ -32,6 +32,7 @@ except ImportError:
|
|||
ldap = None
|
||||
PYTHON_LDAP3 = None
|
||||
import base64
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
@ -42,6 +43,7 @@ from django.conf import settings
|
|||
from django.contrib import messages
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.humanize.templatetags.humanize import NaturalTimeFormatter
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
|
@ -303,20 +305,83 @@ def password_policy_control_messages(ctrl, attributes):
|
|||
|
||||
if ctrl.error:
|
||||
error = ppolicy.PasswordPolicyError.namedValues[ctrl.error]
|
||||
|
||||
def add_ppolicy_user_friendly_message_values(attributes):
|
||||
def humanize_duration(seconds):
|
||||
if not seconds:
|
||||
return
|
||||
if isinstance(seconds, str):
|
||||
try:
|
||||
seconds = int(seconds)
|
||||
except ValueError:
|
||||
return
|
||||
minutes, dummy = divmod(seconds, 60)
|
||||
if not minutes:
|
||||
return ngettext("%(count)d second", "%(count)d seconds", seconds) % {'count': seconds}
|
||||
hours, dummy = divmod(minutes, 60)
|
||||
if not hours:
|
||||
return ngettext("%(count)d minute", "%(count)d minutes", minutes) % {'count': minutes}
|
||||
days, dummy = divmod(hours, 24)
|
||||
if not days:
|
||||
return ngettext("%(cound)d hour", "%(count)d hours", hours) % {'count': hours}
|
||||
months, dummy = divmod(hours, 30)
|
||||
if not months:
|
||||
return ngettext("%(count)d day", "%(count)d days", days) % {'count': days}
|
||||
return ngettext("%(count)d month", "%(count)d months", months) % {'count': months}
|
||||
|
||||
friendly_attributes = {}
|
||||
|
||||
pwdmaxage_friendly = attributes['pwdmaxage']
|
||||
if pwdmaxage_friendly and isinstance(pwdmaxage_friendly, list):
|
||||
pwdmaxage_friendly = pwdmaxage_friendly[0]
|
||||
friendly_attributes['pwdmaxage_friendly'] = (
|
||||
humanize_duration(pwdmaxage_friendly) or attributes['pwdmaxage']
|
||||
)
|
||||
|
||||
pwdminage_friendly = attributes['pwdminage']
|
||||
if pwdminage_friendly and isinstance(pwdminage_friendly, list):
|
||||
pwdminage_friendly = pwdminage_friendly[0]
|
||||
friendly_attributes['pwdminage_friendly'] = (
|
||||
humanize_duration(pwdminage_friendly) or attributes['pwdminage']
|
||||
)
|
||||
|
||||
pwdaccountlockedtime_friendly = attributes['pwdaccountlockedtime']
|
||||
if pwdaccountlockedtime_friendly and isinstance(pwdaccountlockedtime_friendly, list):
|
||||
pwdaccountlockedtime_friendly = pwdaccountlockedtime_friendly[0]
|
||||
try:
|
||||
pwdaccountlockedtime_friendly = int(pwdaccountlockedtime_friendly)
|
||||
except ValueError:
|
||||
friendly_attributes['pwdaccountlockedtime_friendly'] = attributes['pwdaccountlockedtime']
|
||||
else:
|
||||
friendly_attributes['pwdaccountlockedtime_friendly'] = NaturalTimeFormatter.string_for(
|
||||
datetime.datetime.fromtimestamp(pwdaccountlockedtime_friendly)
|
||||
)
|
||||
|
||||
return friendly_attributes
|
||||
|
||||
friendly_attributes = attributes.copy()
|
||||
friendly_attributes.update(add_ppolicy_user_friendly_message_values(attributes))
|
||||
|
||||
error2message = {
|
||||
'passwordExpired': _('The password expired after {pwdmaxage}').format(**attributes),
|
||||
'passwordExpired': _('The password expired after {pwdmaxage_friendly}.').format(
|
||||
**friendly_attributes
|
||||
),
|
||||
'accountLocked': _(
|
||||
'The account is locked since {pwdaccountlockedtime[0]} after {pwdmaxfailure} failures.'
|
||||
).format(**attributes),
|
||||
'The account has been locked {pwdaccountlockedtime_friendly} after {pwdmaxfailure} failures.'
|
||||
).format(**friendly_attributes),
|
||||
'changeAfterReset': _('The password was reset and must be changed.'),
|
||||
'passwordModNotAllowed': _('It is not possible to modify the password.'),
|
||||
'mustSupplyOldPassword': _('The old password must be supplied.'),
|
||||
'insufficientPasswordQuality': _('The password does not meet the quality requirements.'),
|
||||
'passwordTooShort': _('The password is too short {pwdminlength}.').format(**attributes),
|
||||
'passwordTooYoung': _('It is too soon to change the password {pwdminage}.').format(**attributes),
|
||||
'passwordTooShort': _(
|
||||
'The password is too short, it should be at least {pwdminlength} characters long.'
|
||||
).format(**friendly_attributes),
|
||||
'passwordTooYoung': _(
|
||||
'It is too soon to change the password, it should be {pwdminage_friendly} old.'
|
||||
).format(**friendly_attributes),
|
||||
'passwordInHistory': _(
|
||||
'This password is among the last {pwdhistory} password that were used and cannot be used again.'
|
||||
).format(**attributes),
|
||||
'This password is among the last {pwdhistory} passwords that were used and cannot be used again.'
|
||||
).format(**friendly_attributes),
|
||||
}
|
||||
messages.append(error2message.get(error, _('Unexpected error {error}').format(error=error)))
|
||||
return messages
|
||||
|
|
|
@ -1170,12 +1170,12 @@ pwdSafeModify: FALSE
|
|||
response.form.set('password', 'invalid')
|
||||
response = response.form.submit(name='login-password-submit')
|
||||
assert 'Incorrect Username or password' in str(response.pyquery('.errornotice'))
|
||||
assert 'account is locked' not in str(response.pyquery('.messages'))
|
||||
assert 'account has been locked' not in str(response.pyquery('.messages'))
|
||||
response = app.get('/login/')
|
||||
response.form.set('username', USERNAME)
|
||||
response.form.set('password', 'invalid')
|
||||
response = response.form.submit(name='login-password-submit')
|
||||
assert 'account is locked' in str(response.pyquery('.messages'))
|
||||
assert 'account has been locked' in str(response.pyquery('.messages'))
|
||||
|
||||
|
||||
def ppolicy_authenticate_exactly_pwdMaxFailure(slapd_ppolicy, caplog):
|
||||
|
@ -1225,9 +1225,9 @@ def test_authenticate_ppolicy_pwdMaxFailure(slapd_ppolicy, settings, db, caplog)
|
|||
]
|
||||
|
||||
ppolicy_authenticate_exactly_pwdMaxFailure(slapd_ppolicy, caplog)
|
||||
assert 'account is locked' not in caplog.text
|
||||
assert 'account has been locked' not in caplog.text
|
||||
assert authenticate(username=USERNAME, password='incorrect') is None
|
||||
assert 'account is locked since 20' in caplog.text
|
||||
assert 'account has been locked' in caplog.text
|
||||
|
||||
|
||||
def test_do_not_use_controls(slapd_ppolicy, settings, db, caplog):
|
||||
|
|
Loading…
Reference in New Issue