45 |
45 |
from django.contrib.auth.models import Group
|
46 |
46 |
from django.core.cache import cache
|
47 |
47 |
from django.core.exceptions import ImproperlyConfigured
|
|
48 |
from django.template import loader
|
48 |
49 |
from django.utils.encoding import force_bytes, force_text
|
49 |
|
from django.utils.translation import ngettext
|
50 |
|
from django.utils.translation import ugettext as _
|
|
50 |
from django.utils.translation import ugettext as _, gettext, ngettext
|
51 |
51 |
|
52 |
52 |
from authentic2 import app_settings, crypto
|
53 |
53 |
from authentic2.a2_rbac.models import Role
|
... | ... | |
299 |
299 |
raise NotImplementedError
|
300 |
300 |
|
301 |
301 |
|
302 |
|
def password_policy_control_messages(ctrl):
|
303 |
|
messages = []
|
|
302 |
def password_policy_control_messages(ctrl, control_messages):
|
|
303 |
results = []
|
304 |
304 |
|
305 |
305 |
if ctrl.error:
|
306 |
306 |
error = ppolicy.PasswordPolicyError.namedValues[ctrl.error]
|
307 |
|
error2message = {
|
308 |
|
'passwordExpired': _('The password expired'),
|
309 |
|
'accountLocked': _('The account is locked.'),
|
310 |
|
'changeAfterReset': _('The password was reset and must be changed.'),
|
311 |
|
'passwordModNotAllowed': _('It is not possible to modify the password.'),
|
312 |
|
'mustSupplyOldPassword': _('The old password must be supplied.'),
|
313 |
|
'insufficientPasswordQuality': _('The password does not meet the quality requirements.'),
|
314 |
|
'passwordTooShort': _('The password is too short.'),
|
315 |
|
'passwordTooYoung': _('It is too soon to change the password.'),
|
316 |
|
'passwordInHistory': _('This password was recently used and cannot be used again.'),
|
317 |
|
}
|
318 |
|
messages.append(error2message.get(error, _('Unexpected error {error}').format(error=error)))
|
319 |
|
return messages
|
|
307 |
if error not in control_messages:
|
|
308 |
results.append(_('Unexpected error {error}').format(error=error))
|
|
309 |
else:
|
|
310 |
results.append(control_messages[error])
|
|
311 |
return results
|
320 |
312 |
|
321 |
313 |
if ctrl.timeBeforeExpiration:
|
322 |
314 |
expiration_date = time.asctime(time.localtime(time.time() + ctrl.timeBeforeExpiration))
|
323 |
|
messages.append(
|
324 |
|
_('The password will expire at {expiration_date}.').format(expiration_date=expiration_date)
|
325 |
|
)
|
|
315 |
results.append(control_messages['expiration_date'].format(expiration_date=expiration_date))
|
326 |
316 |
if ctrl.graceAuthNsRemaining:
|
327 |
|
messages.append(
|
328 |
|
ngettext(
|
329 |
|
'This password expired: this is the last time it can be used.',
|
330 |
|
'This password expired and can only be used {graceAuthNsRemaining} times, including this one.',
|
331 |
|
ctrl.graceAuthNsRemaining,
|
332 |
|
).format(graceAuthNsRemaining=ctrl.graceAuthNsRemaining)
|
333 |
|
)
|
334 |
|
return messages
|
|
317 |
results.append(control_messages['graceAuthNsRemaining'].format(graceAuthNsRemaining=ctrl.graceAuthNsRemaining))
|
|
318 |
return results
|
335 |
319 |
|
336 |
320 |
|
337 |
321 |
class LDAPUser(User):
|
... | ... | |
621 |
605 |
'user_attributes': [],
|
622 |
606 |
# https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#ldap-controls
|
623 |
607 |
'use_controls': True,
|
|
608 |
'ppolicy_messages': {
|
|
609 |
'passwordExpired': gettext('The password expired'),
|
|
610 |
'accountLocked': gettext('The account is locked.'),
|
|
611 |
'changeAfterReset': gettext('The password was reset and must be changed.'),
|
|
612 |
'passwordModNotAllowed': gettext('It is not possible to modify the password.'),
|
|
613 |
'mustSupplyOldPassword': gettext('The old password must be supplied.'),
|
|
614 |
'insufficientPasswordQuality': gettext('The password does not meet the quality requirements.'),
|
|
615 |
'passwordTooShort': gettext('The password is too short.'),
|
|
616 |
'passwordTooYoung': gettext('It is too soon to change the password.'),
|
|
617 |
'passwordInHistory': gettext('This password was recently used and cannot be used again.'),
|
|
618 |
'expiration_date': gettext('The password will expire at {expiration_date}.'),
|
|
619 |
'graceAuthNsRemaining': gettext(
|
|
620 |
'This password expired and can only be used {graceAuthNsRemaining} time(s), including this one.'
|
|
621 |
),
|
|
622 |
},
|
624 |
623 |
}
|
625 |
624 |
_REQUIRED = ('url', 'basedn')
|
626 |
625 |
_TO_ITERABLE = ('url', 'groupsu', 'groupstaff', 'groupactive')
|
... | ... | |
660 |
659 |
return blocks
|
661 |
660 |
|
662 |
661 |
@staticmethod
|
663 |
|
def process_controls(request, authz_id, ctrls):
|
|
662 |
def process_controls(request, authz_id, ctrls, block):
|
664 |
663 |
for c in ctrls:
|
665 |
664 |
if c.controlType == ppolicy.PasswordPolicyControl.controlType:
|
666 |
|
message = ' '.join(password_policy_control_messages(c))
|
|
665 |
message = ' '.join(password_policy_control_messages(c, block['ppolicy_messages']))
|
667 |
666 |
if request is not None:
|
668 |
667 |
messages.add_message(request, messages.WARNING, message)
|
669 |
668 |
if c.graceAuthNsRemaining or c.timeBeforeExpiration:
|
... | ... | |
791 |
790 |
else:
|
792 |
791 |
serverctrls = []
|
793 |
792 |
results = conn.simple_bind_s(authz_id, password, serverctrls=serverctrls)
|
794 |
|
self.process_controls(request, authz_id, results[3])
|
|
793 |
self.process_controls(request, authz_id, results[3], block)
|
795 |
794 |
user_login_success(authz_id)
|
796 |
795 |
if not block['connect_with_user_credentials']:
|
797 |
796 |
try:
|
... | ... | |
803 |
802 |
except ldap.INVALID_CREDENTIALS as e:
|
804 |
803 |
if block.get('use_controls') and len(e.args) > 0 and 'ctrls' in e.args[0]:
|
805 |
804 |
self.process_controls(
|
806 |
|
request, authz_id, DecodeControlTuples(e.args[0]['ctrls'])
|
|
805 |
request, authz_id, DecodeControlTuples(e.args[0]['ctrls']), block
|
807 |
806 |
)
|
808 |
807 |
attributes = self.get_ldap_attributes(block, conn, authz_id)
|
809 |
808 |
user = self.lookup_existing_user(authz_id, block, attributes)
|