From 1ea51a5bb4ee8d045e051531c0ede2f56e418fb0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Thu, 17 Jan 2019 08:25:02 +0100
Subject: [PATCH] utils: add hook to decide if user can change/set password
 (#28848)

---
 src/authentic2/backends/ldap_backend.py |  4 ++--
 src/authentic2/custom_user/models.py    |  2 +-
 src/authentic2/utils.py                 | 10 ++++++++++
 src/authentic2/views.py                 |  2 +-
 tests/test_utils.py                     |  8 +++++++-
 5 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py
index b86ff7e1..91352c4c 100644
--- a/src/authentic2/backends/ldap_backend.py
+++ b/src/authentic2/backends/ldap_backend.py
@@ -38,7 +38,7 @@ from authentic2.user_login_failure import user_login_failure, user_login_success
 from django_rbac.utils import get_ou_model
 from authentic2.a2_rbac.utils import get_default_ou
 from authentic2.ldap_utils import FilterFormatter
-from authentic2.utils import utf8_encode
+from authentic2.utils import utf8_encode, user_can_change_password
 
 from authentic2.backends import is_user_authenticable
 
@@ -221,7 +221,7 @@ class LDAPUser(get_user_model()):
         return self.block['can_reset_password']
 
     def can_change_password(self):
-        return app_settings.A2_REGISTRATION_CAN_CHANGE_PASSWORD and self.block['user_can_change_password']
+        return self.block['user_can_change_password'] and user_can_change_password(user=self)
 
 
 class LDAPBackend(object):
diff --git a/src/authentic2/custom_user/models.py b/src/authentic2/custom_user/models.py
index 5c006c3a..f0711cae 100644
--- a/src/authentic2/custom_user/models.py
+++ b/src/authentic2/custom_user/models.py
@@ -274,4 +274,4 @@ class User(AbstractBaseUser, PermissionMixin):
         return rc
 
     def can_change_password(self):
-        return app_settings.A2_REGISTRATION_CAN_CHANGE_PASSWORD
+        return utils.user_can_change_password(user=self)
diff --git a/src/authentic2/utils.py b/src/authentic2/utils.py
index 1ff7e89d..d02e73f6 100644
--- a/src/authentic2/utils.py
+++ b/src/authentic2/utils.py
@@ -1073,3 +1073,13 @@ def get_user_flag(user, name, default=None):
         if ou_value is not None:
             return ou_value
     return default
+
+
+def user_can_change_password(user, request=None):
+    from . import hooks
+    if not app_settings.A2_REGISTRATION_CAN_CHANGE_PASSWORD:
+        return False
+    for can in hooks.call_hooks('user_can_change_password', user=user, request=request):
+        if can is False:
+            return can
+    return True
diff --git a/src/authentic2/views.py b/src/authentic2/views.py
index 478bdb02..0c75933e 100644
--- a/src/authentic2/views.py
+++ b/src/authentic2/views.py
@@ -582,7 +582,7 @@ def logout(request, next_url=None, default_next_url='auth_homepage',
 
 def login_password_profile(request, *args, **kwargs):
     context = kwargs.pop('context', {})
-    can_change_password = app_settings.A2_REGISTRATION_CAN_CHANGE_PASSWORD
+    can_change_password = utils.user_can_change_password(user=request.user)
     has_usable_password = request.user.has_usable_password()
     context.update(
         {'can_change_password': can_change_password,
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 7f161815..8146c850 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,4 +1,4 @@
-from authentic2.utils import good_next_url, same_origin, select_next_url
+from authentic2.utils import good_next_url, same_origin, select_next_url, user_can_change_password
 
 
 def test_good_next_url(rf, settings):
@@ -46,3 +46,9 @@ def test_select_next_url(rf, settings):
     assert select_next_url(request, '/') == '/'
     settings.A2_REDIRECT_WHITELIST = ['//example.com/']
     assert select_next_url(request, '/') == 'http://example.com/'
+
+
+def test_user_can_change_password(simple_user, settings):
+    assert user_can_change_password(user=simple_user) is True
+    settings.A2_REGISTRATION_CAN_CHANGE_PASSWORD = False
+    assert user_can_change_password(user=simple_user) is False
-- 
2.20.1

