From ba8e0bf35044416efeedded0c99615eae5f51b11 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20Dachary?= <ldachary@easter-eggs.com>
Date: Fri, 19 Feb 2021 13:49:22 +0100
Subject: [PATCH] ldap: move messages from hard coded to templates (#51294)

Fixes: #51294

Licenses: MIT
---
 src/authentic2/backends/ldap_backend.py | 59 ++++++++++++++-----------
 1 file changed, 34 insertions(+), 25 deletions(-)

diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py
index be6ed5c95..9a75d7dda 100644
--- a/src/authentic2/backends/ldap_backend.py
+++ b/src/authentic2/backends/ldap_backend.py
@@ -47,10 +47,11 @@ 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.template import loader
 from django.utils.encoding import force_bytes, force_text
 from django.utils import six
 from django.utils.six.moves.urllib import parse as urlparse
-from django.utils.translation import ugettext as _, ngettext
+from django.utils.translation import ugettext as _, gettext, ngettext
 
 from authentic2.a2_rbac.models import Role
 
@@ -237,35 +238,26 @@ def map_text(d):
     raise NotImplementedError
 
 
-def password_policy_control_messages(ctrl):
-    messages = []
+def password_policy_control_messages(ctrl, control_messages):
+    results = []
 
     if ctrl.error:
         error = ppolicy.PasswordPolicyError.namedValues[ctrl.error]
-        error2message = {
-            'passwordExpired': _('The password expired'),
-            'accountLocked': _('The account is locked.'),
-            '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.'),
-            'passwordTooYoung': _('It is too soon to change the password.'),
-            'passwordInHistory': _('This password was recently used and cannot be used again.'),
-        }
-        messages.append(error2message.get(error, _('Unexpected error {error}').format(error=error)))
-        return messages
+        if error not in control_messages:
+            results.append(_('Unexpected error {error}').format(error=error))
+        else:
+            results.append(control_messages[error])
+        return results
 
     if ctrl.timeBeforeExpiration:
-        timeBeforeExpiration = time.asctime(time.localtime(time.time() + ctrl.timeBeforeExpiration))
-        messages.append(_('The password will expire at {timeBeforeExpiration}.').format(
-            timeBeforeExpiration=timeBeforeExpiration))
+        expiration_date = time.asctime(time.localtime(time.time() + ctrl.timeBeforeExpiration))
+        results.append(control_messages['expiration_date'].format(expiration_date=expiration_date))
     if ctrl.graceAuthNsRemaining:
-        messages.append(ngettext(
+        results.append(ngettext(
             'This password expired: this is the last time it can be used.',
             'This password expired and can only be used {graceAuthNsRemaining} times, including this one.',
             ctrl.graceAuthNsRemaining).format(graceAuthNsRemaining=ctrl.graceAuthNsRemaining))
-    return messages
+    return results
 
 class LDAPUser(User):
     SESSION_LDAP_DATA_KEY = 'ldap-data'
@@ -548,6 +540,22 @@ class LDAPBackend(object):
         'user_attributes': [],
         # https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#ldap-controls
         'use_controls': True,
+        'ppolicy_messages': {
+            'passwordExpired': gettext('The password expired'),
+            'accountLocked': gettext('The account is locked.'),
+            'changeAfterReset': gettext('The password was reset and must be changed.'),
+            'passwordModNotAllowed': gettext('It is not possible to modify the password.'),
+            'mustSupplyOldPassword': gettext('The old password must be supplied.'),
+            'insufficientPasswordQuality': gettext('The password does not meet the quality requirements.'),
+            'passwordTooShort': gettext('The password is too short.'),
+            'passwordTooYoung': gettext('It is too soon to change the password.'),
+            'passwordInHistory': gettext('This password was recently used and cannot be used again.'),
+            'expiration_date': gettext('The password will expire at {expiration_date}.'),
+#            'graceAuthNsRemaining': (
+#                'This password expired: this is the last time it can be used.',
+#                'This password expired and can only be used {graceAuthNsRemaining} times, including this one.',
+#            ),
+        },
     }
     _REQUIRED = ('url', 'basedn')
     _TO_ITERABLE = ('url', 'groupsu', 'groupstaff', 'groupactive')
@@ -579,10 +587,10 @@ class LDAPBackend(object):
         return blocks
 
     @staticmethod
-    def process_controls(request, authz_id, ctrls):
+    def process_controls(request, authz_id, ctrls, block):
         for c in ctrls:
             if c.controlType == ppolicy.PasswordPolicyControl.controlType:
-                message = ' '.join(password_policy_control_messages(c))
+                message = ' '.join(password_policy_control_messages(c, block['ppolicy_messages']))
                 if request is not None:
                     messages.add_message(request, messages.WARNING, message)
             else:
@@ -706,7 +714,7 @@ class LDAPBackend(object):
                             else:
                                 serverctrls = []
                             results = conn.simple_bind_s(authz_id, password, serverctrls=serverctrls)
-                            self.process_controls(request, authz_id, results[3])
+                            self.process_controls(request, authz_id, results[3], block)
                             user_login_success(authz_id)
                             if not block['connect_with_user_credentials']:
                                 try:
@@ -717,7 +725,8 @@ class LDAPBackend(object):
                             break
                         except ldap.INVALID_CREDENTIALS as e:
                             if block.get('use_controls') and len(e.args) > 0 and 'ctrls' in e.args[0]:
-                                self.process_controls(request, authz_id, DecodeControlTuples(e.args[0]['ctrls']))
+                                self.process_controls(request, authz_id,
+                                                      DecodeControlTuples(e.args[0]['ctrls']), block)
                             user_login_failure(authz_id)
                             pass
                     else:
-- 
2.25.1

