From 9a84fa30a60c7ff654e634213affe97c3d333345 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 30 Jul 2018 15:45:01 +0200 Subject: [PATCH] allow overriding User.can_reset_password by hooks (fixes #25534) This commit introduce the concept of an user flag, this flag can be defined in many places: * globally trough a setting named A2_USER_ * on the user object itself if there is a property user. which is not None * by any hook returning a not None result and named a2_hook_user_ * for all users of an OU if the ou. is not None --- ...anizationalunit_user_can_reset_password.py | 19 +++++++++++++++ src/authentic2/a2_rbac/models.py | 3 +++ src/authentic2/app_settings.py | 10 +++++++- src/authentic2/backends/ldap_backend.py | 1 + src/authentic2/custom_user/models.py | 3 --- src/authentic2/profile_views.py | 5 +++- src/authentic2/utils.py | 23 +++++++++++++++++++ src/authentic2/views.py | 2 +- 8 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 src/authentic2/a2_rbac/migrations/0017_organizationalunit_user_can_reset_password.py diff --git a/src/authentic2/a2_rbac/migrations/0017_organizationalunit_user_can_reset_password.py b/src/authentic2/a2_rbac/migrations/0017_organizationalunit_user_can_reset_password.py new file mode 100644 index 00000000..e3abc5d4 --- /dev/null +++ b/src/authentic2/a2_rbac/migrations/0017_organizationalunit_user_can_reset_password.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('a2_rbac', '0016_auto_20171208_1429'), + ] + + operations = [ + migrations.AddField( + model_name='organizationalunit', + name='user_can_reset_password', + field=models.NullBooleanField(verbose_name='Users can reset password'), + ), + ] diff --git a/src/authentic2/a2_rbac/models.py b/src/authentic2/a2_rbac/models.py index dad83d1f..d6614b98 100644 --- a/src/authentic2/a2_rbac/models.py +++ b/src/authentic2/a2_rbac/models.py @@ -44,6 +44,9 @@ class OrganizationalUnit(OrganizationalUnitAbstractBase): content_type_field='target_ct', object_id_field='target_id') + user_can_reset_password = models.NullBooleanField( + verbose_name=_('Users can reset password')) + objects = managers.OrganizationalUnitManager() class Meta: diff --git a/src/authentic2/app_settings.py b/src/authentic2/app_settings.py index 6c1b3a3a..10bd4237 100644 --- a/src/authentic2/app_settings.py +++ b/src/authentic2/app_settings.py @@ -49,6 +49,14 @@ class AppSettings(object): add_realms(self.A2_REALMS) return realms.items() + @property + def A2_USER_CAN_RESET_PASSWORD(self): + if hasattr(self.settings, 'A2_USER_CAN_RESET_PASSWORD'): + return self.settings.A2_USER_CAN_RESET_PASSWORD + if hasattr(self.settings, 'A2_CAN_RESET_PASSWORD'): + return self.settings.A2_CAN_RESET_PASSWORD + return self.defaults['A2_USER_CAN_RESET_PASSWORD'].default + def __getattr__(self, key): if key not in self.defaults: raise AttributeError('unknown key %s' % key) @@ -107,7 +115,7 @@ default_settings = dict( definition='Include empty fields in profile view'), A2_HOMEPAGE_URL = Setting(default=None, definition='IdP has no homepage, ' 'redirect to this one.'), - A2_CAN_RESET_PASSWORD = Setting(default=True, definition='Allow online reset of passwords'), + A2_USER_CAN_RESET_PASSWORD = Setting(default=None, definition='Allow online reset of passwords'), A2_EMAIL_IS_UNIQUE = Setting(default=False, definition='Email of users must be unique'), A2_REGISTRATION_EMAIL_IS_UNIQUE = Setting(default=False, diff --git a/src/authentic2/backends/ldap_backend.py b/src/authentic2/backends/ldap_backend.py index ba4bb117..fb5ed1c7 100644 --- a/src/authentic2/backends/ldap_backend.py +++ b/src/authentic2/backends/ldap_backend.py @@ -216,6 +216,7 @@ class LDAPUser(get_user_model()): if hasattr(self, 'keep_pk'): self.pk = pk + @property def can_reset_password(self): return self.block['can_reset_password'] diff --git a/src/authentic2/custom_user/models.py b/src/authentic2/custom_user/models.py index 068712a4..582c1c51 100644 --- a/src/authentic2/custom_user/models.py +++ b/src/authentic2/custom_user/models.py @@ -250,8 +250,5 @@ class User(AbstractBaseUser, PermissionMixin): attribute.set_value(self, getattr(self, attr_name, None)) return rc - def can_reset_password(self): - return self.has_usable_password() - def can_change_password(self): return app_settings.A2_REGISTRATION_CAN_CHANGE_PASSWORD diff --git a/src/authentic2/profile_views.py b/src/authentic2/profile_views.py index 5f2b4ced..3c71490b 100644 --- a/src/authentic2/profile_views.py +++ b/src/authentic2/profile_views.py @@ -81,7 +81,10 @@ class PasswordResetConfirmView(cbv.RedirectToNextURLViewMixin, FormView): 'or has expired')) if not validlink: return utils.redirect(request, self.get_success_url()) - if not self.user.can_reset_password(): + can_reset_password = utils.get_user_flag(user=self.user, + name='can_reset_password', + default=self.user.has_usable_password()) + if not can_reset_password: messages.warning(request, _('It\'s not possible to reset your password. Please ' 'contact an administrator.')) return utils.redirect(request, self.get_success_url()) diff --git a/src/authentic2/utils.py b/src/authentic2/utils.py index 02e6e47b..2bcacb75 100644 --- a/src/authentic2/utils.py +++ b/src/authentic2/utils.py @@ -1054,3 +1054,26 @@ def send_email_change_email(user, email, request=None, context=None, template_na def update_model(obj, d): for attr, value in d.items(): setattr(obj, attr, value) + + +def get_user_flag(user, name, default=None): + '''Get a boolean flag settable at user, by a hook, globally or ou wide''' + from . import hooks + + setting_value = getattr(app_settings, 'A2_USER_' + name.upper(), None) + if setting_value is not None: + return bool(setting_value) + + user_value = getattr(user, name, None) + if user_value is not None: + return user_value + + hook_value = hooks.call_hooks_first_result('user_' + name, user=user) + if hook_value is not None: + return bool(hook_value) + + if user.ou and hasattr(user.ou, 'user_' + name): + ou_value = getattr(user.ou, 'user_' + name, None) + if ou_value is not None: + return ou_value + return default diff --git a/src/authentic2/views.py b/src/authentic2/views.py index e5f5e7bb..90e60e0b 100644 --- a/src/authentic2/views.py +++ b/src/authentic2/views.py @@ -292,7 +292,7 @@ def login(request, template_name='authentic2/login.html', context_instance = RequestContext(request, { 'cancel': nonce is not None, - 'can_reset_password': app_settings.A2_CAN_RESET_PASSWORD, + 'can_reset_password': app_settings.A2_USER_CAN_RESET_PASSWORD is not False, 'registration_authorized': getattr(settings, 'REGISTRATION_OPEN', True), 'registration_url': registration_url, }) -- 2.18.0