From f69fcbcc829891fb80d5cede281e24d19ad4b8c0 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Tue, 7 May 2019 03:13:14 -0400 Subject: [PATCH] Move password set views to the authentication app Signed-off-by: Roberto Rosario --- HISTORY.rst | 1 + docs/releases/3.2.rst | 1 + mayan/apps/authentication/apps.py | 19 +++- mayan/apps/authentication/links.py | 20 ++++- mayan/apps/authentication/tests/mixins.py | 22 +++++ mayan/apps/authentication/tests/test_views.py | 59 +++++++++++++ mayan/apps/authentication/urls.py | 15 +++- mayan/apps/authentication/views.py | 75 +++++++++++++++- mayan/apps/user_management/apps.py | 11 +-- mayan/apps/user_management/icons.py | 1 - mayan/apps/user_management/links.py | 11 --- mayan/apps/user_management/tests/mixins.py | 19 ---- .../apps/user_management/tests/test_views.py | 54 ------------ mayan/apps/user_management/urls.py | 10 +-- mayan/apps/user_management/views.py | 86 ++----------------- 15 files changed, 214 insertions(+), 190 deletions(-) create mode 100644 mayan/apps/authentication/tests/mixins.py diff --git a/HISTORY.rst b/HISTORY.rst index 6405eebfea..037f18d59c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -244,6 +244,7 @@ * Add the preparestatic command. * Remove the related attribute of check_access. * Remove filter_by_access. Replaced by restrict_queryset. +* Move the user set password views to the authentication app. 3.1.11 (2019-04-XX) =================== diff --git a/docs/releases/3.2.rst b/docs/releases/3.2.rst index 1adfde2a0b..cb13de0231 100644 --- a/docs/releases/3.2.rst +++ b/docs/releases/3.2.rst @@ -285,6 +285,7 @@ Other changes - 'relationship between two models. The registered relationship ' - 'will be automatically used by check_access().', - InterfaceWarning +* Move the user set password views to the authentication app. diff --git a/mayan/apps/authentication/apps.py b/mayan/apps/authentication/apps.py index a2125dea1d..4b0225d564 100644 --- a/mayan/apps/authentication/apps.py +++ b/mayan/apps/authentication/apps.py @@ -2,13 +2,17 @@ from __future__ import absolute_import, unicode_literals import logging +from django.contrib.auth import get_user_model from django.utils.translation import ugettext_lazy as _ from mayan.apps.common.apps import MayanAppConfig -from mayan.apps.common.menus import menu_user +from mayan.apps.common.menus import menu_multi_item, menu_object, menu_user from mayan.apps.navigation.classes import Separator -from .links import link_logout, link_password_change +from .links import ( + link_logout, link_password_change, link_user_multiple_set_password, + link_user_set_password +) logger = logging.getLogger(__name__) @@ -23,6 +27,17 @@ class AuthenticationApp(MayanAppConfig): def ready(self): super(AuthenticationApp, self).ready() + User = get_user_model() + + menu_multi_item.bind_links( + links=(link_user_multiple_set_password,), + sources=('user_management:user_list',) + ) + + menu_object.bind_links( + links=(link_user_set_password,), sources=(User,) + ) + menu_user.bind_links( links=( Separator(), link_password_change, link_logout diff --git a/mayan/apps/authentication/links.py b/mayan/apps/authentication/links.py index 7908cfe707..6f9eecdce7 100644 --- a/mayan/apps/authentication/links.py +++ b/mayan/apps/authentication/links.py @@ -3,8 +3,7 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ from mayan.apps.navigation.classes import Link - -from .icons import icon_logout, icon_password_change +from mayan.apps.user_management.permissions import permission_user_edit def has_usable_password_and_can_change_password(context): @@ -18,11 +17,24 @@ def has_usable_password_and_can_change_password(context): link_logout = Link( - html_extra_classes='non-ajax', icon_class=icon_logout, + html_extra_classes='non-ajax', + icon_class_path='mayan.apps.authentication.icons.icon_logout', text=_('Logout'), view='authentication:logout_view' ) link_password_change = Link( condition=has_usable_password_and_can_change_password, - icon_class=icon_password_change, text=_('Change password'), + icon_class_path='mayan.apps.authentication.icons.icon_password_change', + text=_('Change password'), view='authentication:password_change_view' ) +link_user_multiple_set_password = Link( + icon_class_path='mayan.apps.authentication.icons.icon_password_change', + permissions=(permission_user_edit,), text=_('Set password'), + view='authentication:user_multiple_set_password' +) +link_user_set_password = Link( + args='object.id', + icon_class_path='mayan.apps.authentication.icons.icon_password_change', + permissions=(permission_user_edit,), + text=_('Set password'), view='authentication:user_set_password', +) diff --git a/mayan/apps/authentication/tests/mixins.py b/mayan/apps/authentication/tests/mixins.py new file mode 100644 index 0000000000..74167ba324 --- /dev/null +++ b/mayan/apps/authentication/tests/mixins.py @@ -0,0 +1,22 @@ +from __future__ import unicode_literals + + +class UserPasswordViewTestMixin(object): + def _request_test_user_password_set_view(self, password): + return self.post( + viewname='authentication:user_set_password', + kwargs={'pk': self.test_user.pk}, + data={ + 'new_password1': password, 'new_password2': password + } + ) + + def _request_test_user_password_set_multiple_view(self, password): + return self.post( + viewname='authentication:user_multiple_set_password', + data={ + 'id_list': self.test_user.pk, + 'new_password1': password, + 'new_password2': password + } + ) diff --git a/mayan/apps/authentication/tests/test_views.py b/mayan/apps/authentication/tests/test_views.py index 70e0a1c788..8e61c9849e 100644 --- a/mayan/apps/authentication/tests/test_views.py +++ b/mayan/apps/authentication/tests/test_views.py @@ -13,10 +13,14 @@ from django.utils.http import urlunquote_plus from mayan.apps.common.tests import GenericViewTestCase from mayan.apps.smart_settings.classes import Namespace +from mayan.apps.user_management.permissions import permission_user_edit +from mayan.apps.user_management.tests.mixins import UserTestMixin +from mayan.apps.user_management.tests.literals import TEST_USER_PASSWORD_EDITED from ..settings import setting_maximum_session_length from .literals import TEST_EMAIL_AUTHENTICATION_BACKEND +from .mixins import UserPasswordViewTestMixin class CurrentUserViewTestCase(GenericViewTestCase): @@ -256,3 +260,58 @@ class UserLoginTestCase(GenericViewTestCase): ) self.assertEqual(response.redirect_chain, [(TEST_REDIRECT_URL, 302)]) + + +class UserViewTestCase(UserTestMixin, UserPasswordViewTestMixin, GenericViewTestCase): + def test_user_set_password_view_no_access(self): + self._create_test_user() + + password_hash = self.test_user.password + + response = self._request_test_user_password_set_view( + password=TEST_USER_PASSWORD_EDITED + ) + self.assertEqual(response.status_code, 404) + + self.test_user.refresh_from_db() + self.assertEqual(self.test_user.password, password_hash) + + def test_user_set_password_view_with_access(self): + self._create_test_user() + self.grant_access(obj=self.test_user, permission=permission_user_edit) + + password_hash = self.test_user.password + + response = self._request_test_user_password_set_view( + password=TEST_USER_PASSWORD_EDITED + ) + self.assertEqual(response.status_code, 302) + + self.test_user.refresh_from_db() + self.assertNotEqual(self.test_user.password, password_hash) + + def test_user_multiple_set_password_view_no_access(self): + self._create_test_user() + password_hash = self.test_user.password + + response = self._request_test_user_password_set_multiple_view( + password=TEST_USER_PASSWORD_EDITED + ) + self.assertEqual(response.status_code, 404) + + self.test_user.refresh_from_db() + self.assertEqual(self.test_user.password, password_hash) + + def test_user_multiple_set_password_view_with_access(self): + self._create_test_user() + self.grant_access(obj=self.test_user, permission=permission_user_edit) + + password_hash = self.test_user.password + + response = self._request_test_user_password_set_multiple_view( + password=TEST_USER_PASSWORD_EDITED + ) + self.assertEqual(response.status_code, 302) + + self.test_user.refresh_from_db() + self.assertNotEqual(self.test_user.password, password_hash) diff --git a/mayan/apps/authentication/urls.py b/mayan/apps/authentication/urls.py index d48ac1ffb7..ba075e549b 100644 --- a/mayan/apps/authentication/urls.py +++ b/mayan/apps/authentication/urls.py @@ -6,7 +6,7 @@ from .views import ( MayanLoginView, MayanLogoutView, MayanPasswordChangeDoneView, MayanPasswordChangeView, MayanPasswordResetCompleteView, MayanPasswordResetConfirmView, MayanPasswordResetDoneView, - MayanPasswordResetView + MayanPasswordResetView, UserSetPasswordView ) @@ -34,11 +34,20 @@ urlpatterns = [ name='password_reset_confirm_view' ), url( - regex=r'^password/reset/done/$', view=MayanPasswordResetDoneView.as_view(), + regex=r'^password/reset/done/$', + view=MayanPasswordResetDoneView.as_view(), name='password_reset_done_view' ), url( regex=r'^password/reset/$', view=MayanPasswordResetView.as_view(), name='password_reset_view' - ) + ), + url( + regex=r'^users/(?P\d+)/set_password/$', + view=UserSetPasswordView.as_view(), name='user_set_password' + ), + url( + regex=r'^users/multiple/set_password/$', + view=UserSetPasswordView.as_view(), name='user_multiple_set_password' + ), ] diff --git a/mayan/apps/authentication/views.py b/mayan/apps/authentication/views.py index f0d1823778..eb3a9b3afb 100644 --- a/mayan/apps/authentication/views.py +++ b/mayan/apps/authentication/views.py @@ -1,22 +1,27 @@ from __future__ import absolute_import, unicode_literals from django.contrib import messages +from django.contrib.auth import get_user_model +from django.contrib.auth.forms import SetPasswordForm from django.contrib.auth.views import ( LoginView, LogoutView, PasswordChangeDoneView, PasswordChangeView, PasswordResetCompleteView, PasswordResetConfirmView, PasswordResetDoneView, PasswordResetView ) +from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect from django.shortcuts import redirect from django.urls import reverse, reverse_lazy -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext, ugettext_lazy as _ from stronghold.views import StrongholdPublicMixin import mayan +from mayan.apps.common.generics import MultipleObjectFormActionView from mayan.apps.common.settings import ( setting_home_view, setting_project_title, setting_project_url ) +from mayan.apps.user_management.permissions import permission_user_edit from .forms import EmailAuthenticationForm, UsernameAuthenticationForm from .settings import setting_login_method, setting_maximum_session_length @@ -131,3 +136,71 @@ class MayanPasswordResetView(StrongholdPublicMixin, PasswordResetView): viewname='authentication:password_reset_done_view' ) template_name = 'authentication/password_reset_form.html' + + +class UserSetPasswordView(MultipleObjectFormActionView): + form_class = SetPasswordForm + model = get_user_model() + object_permission = permission_user_edit + success_message = _('Password change request performed on %(count)d user') + success_message_plural = _( + 'Password change request performed on %(count)d users' + ) + + def get_extra_context(self): + queryset = self.get_object_list() + + result = { + 'submit_label': _('Submit'), + 'title': ungettext( + singular='Change user password', + plural='Change users passwords', + number=queryset.count() + ) + } + + if queryset.count() == 1: + result.update( + { + 'object': queryset.first(), + 'title': _('Change password for user: %s') % queryset.first() + } + ) + + return result + + def get_form_extra_kwargs(self): + queryset = self.get_object_list() + result = {} + if queryset: + result['user'] = queryset.first() + return result + else: + raise PermissionDenied + + def object_action(self, form, instance): + try: + if instance.is_superuser or instance.is_staff: + messages.error( + message=_( + 'Super user and staff user password ' + 'reseting is not allowed, use the admin ' + 'interface for these cases.' + ), request=self.request + ) + else: + instance.set_password(form.cleaned_data['new_password1']) + instance.save() + messages.success( + message=_( + 'Successful password reset for user: %s.' + ) % instance, request=self.request + ) + except Exception as exception: + messages.error( + message=_( + 'Error reseting password for user "%(user)s": %(error)s' + ) % { + 'user': instance, 'error': exception + }, request=self.request + ) diff --git a/mayan/apps/user_management/apps.py b/mayan/apps/user_management/apps.py index 421e86a123..9415f0e44b 100644 --- a/mayan/apps/user_management/apps.py +++ b/mayan/apps/user_management/apps.py @@ -38,8 +38,7 @@ from .links import ( link_group_delete, link_group_edit, link_group_list, link_group_user_list, link_group_setup, link_user_create, link_user_delete, link_user_edit, link_user_group_list, link_user_list, link_user_multiple_delete, - link_user_multiple_set_password, link_user_set_options, - link_user_set_password, link_user_setup, separator_user_label, + link_user_set_options, link_user_setup, separator_user_label, text_user_label ) from .methods import ( @@ -222,7 +221,7 @@ class UserManagementApp(MayanAppConfig): ), sources=(User,) ) menu_multi_item.bind_links( - links=(link_user_multiple_set_password, link_user_multiple_delete), + links=(link_user_multiple_delete,), sources=('user_management:user_list',) ) menu_object.bind_links( @@ -234,9 +233,7 @@ class UserManagementApp(MayanAppConfig): sources=(Group,) ) menu_object.bind_links( - links=( - link_user_delete, link_user_edit, link_user_set_password - ), sources=(User,) + links=(link_user_delete, link_user_edit,), sources=(User,) ) menu_secondary.bind_links( links=(link_group_list, link_group_create), sources=( @@ -247,7 +244,7 @@ class UserManagementApp(MayanAppConfig): ) menu_secondary.bind_links( links=(link_user_list, link_user_create), sources=( - User, 'user_management:user_multiple_set_password', + User, 'authentication:user_multiple_set_password', 'user_management:user_multiple_delete', 'user_management:user_list', 'user_management:user_create' ) diff --git a/mayan/apps/user_management/icons.py b/mayan/apps/user_management/icons.py index 0ad7fddb47..0ef467700c 100644 --- a/mayan/apps/user_management/icons.py +++ b/mayan/apps/user_management/icons.py @@ -19,5 +19,4 @@ icon_user_list = Icon(driver_name='fontawesome', symbol='user') icon_user_multiple_delete = icon_user_delete icon_user_multiple_set_password = Icon(driver_name='fontawesome', symbol='key') icon_user_set_options = Icon(driver_name='fontawesome', symbol='cog') -icon_user_set_password = Icon(driver_name='fontawesome', symbol='key') icon_user_setup = Icon(driver_name='fontawesome', symbol='user') diff --git a/mayan/apps/user_management/links.py b/mayan/apps/user_management/links.py index 503133165f..9f6153d4bc 100644 --- a/mayan/apps/user_management/links.py +++ b/mayan/apps/user_management/links.py @@ -85,23 +85,12 @@ link_user_multiple_delete = Link( permissions=(permission_user_delete,), tags='dangerous', text=_('Delete'), view='user_management:user_multiple_delete' ) -link_user_multiple_set_password = Link( - icon_class_path='mayan.apps.user_management.icons.icon_user_set_password', - permissions=(permission_user_edit,), text=_('Set password'), - view='user_management:user_multiple_set_password' -) link_user_set_options = Link( args='object.id', icon_class_path='mayan.apps.user_management.icons.icon_user_set_options', permissions=(permission_user_edit,), text=_('User options'), view='user_management:user_options', ) -link_user_set_password = Link( - args='object.id', - icon_class_path='mayan.apps.user_management.icons.icon_user_set_password', - permissions=(permission_user_edit,), - text=_('Set password'), view='user_management:user_set_password', -) link_user_setup = Link( icon_class_path='mayan.apps.user_management.icons.icon_user_setup', permissions=(permission_user_view,), text=_('Users'), diff --git a/mayan/apps/user_management/tests/mixins.py b/mayan/apps/user_management/tests/mixins.py index 5701a0e7b3..e7076133f7 100644 --- a/mayan/apps/user_management/tests/mixins.py +++ b/mayan/apps/user_management/tests/mixins.py @@ -312,22 +312,3 @@ class UserViewTestMixin(object): viewname='user_management:user_groups', kwargs={'pk': self.test_user.pk} ) - - def _request_test_user_password_set_view(self, password): - return self.post( - viewname='user_management:user_set_password', - kwargs={'pk': self.test_user.pk}, - data={ - 'new_password1': password, 'new_password2': password - } - ) - - def _request_test_user_password_set_multiple_view(self, password): - return self.post( - viewname='user_management:user_multiple_set_password', - data={ - 'id_list': self.test_user.pk, - 'new_password1': password, - 'new_password2': password - } - ) diff --git a/mayan/apps/user_management/tests/test_views.py b/mayan/apps/user_management/tests/test_views.py index 901b7f0805..e5a53cdfe2 100644 --- a/mayan/apps/user_management/tests/test_views.py +++ b/mayan/apps/user_management/tests/test_views.py @@ -17,7 +17,6 @@ from ..permissions import ( permission_user_edit, permission_user_view ) -from .literals import TEST_USER_PASSWORD_EDITED from .mixins import ( GroupTestMixin, GroupViewTestMixin, UserTestMixin, UserViewTestMixin ) @@ -266,59 +265,6 @@ class UserViewTestCase(UserTestMixin, UserViewTestMixin, GenericViewTestCase): self.assertEqual(get_user_model().objects.count(), user_count - 1) - def test_user_set_password_view_no_access(self): - self._create_test_user() - - password_hash = self.test_user.password - - response = self._request_test_user_password_set_view( - password=TEST_USER_PASSWORD_EDITED - ) - self.assertEqual(response.status_code, 404) - - self.test_user.refresh_from_db() - self.assertEqual(self.test_user.password, password_hash) - - def test_user_set_password_view_with_access(self): - self._create_test_user() - self.grant_access(obj=self.test_user, permission=permission_user_edit) - - password_hash = self.test_user.password - - response = self._request_test_user_password_set_view( - password=TEST_USER_PASSWORD_EDITED - ) - self.assertEqual(response.status_code, 302) - - self.test_user.refresh_from_db() - self.assertNotEqual(self.test_user.password, password_hash) - - def test_user_multiple_set_password_view_no_access(self): - self._create_test_user() - password_hash = self.test_user.password - - response = self._request_test_user_password_set_multiple_view( - password=TEST_USER_PASSWORD_EDITED - ) - self.assertEqual(response.status_code, 404) - - self.test_user.refresh_from_db() - self.assertEqual(self.test_user.password, password_hash) - - def test_user_multiple_set_password_view_with_access(self): - self._create_test_user() - self.grant_access(obj=self.test_user, permission=permission_user_edit) - - password_hash = self.test_user.password - - response = self._request_test_user_password_set_multiple_view( - password=TEST_USER_PASSWORD_EDITED - ) - self.assertEqual(response.status_code, 302) - - self.test_user.refresh_from_db() - self.assertNotEqual(self.test_user.password, password_hash) - class UserGroupViewTestCase(GroupTestMixin, UserTestMixin, UserViewTestMixin, GenericViewTestCase): def test_user_groups_view_no_permission(self): diff --git a/mayan/apps/user_management/urls.py b/mayan/apps/user_management/urls.py index d3e229aa6f..aa22e65206 100644 --- a/mayan/apps/user_management/urls.py +++ b/mayan/apps/user_management/urls.py @@ -10,7 +10,7 @@ from .views import ( CurrentUserDetailsView, CurrentUserEditView, GroupCreateView, GroupDeleteView, GroupEditView, GroupListView, GroupUsersView, UserCreateView, UserDeleteView, UserDetailsView, UserEditView, - UserGroupsView, UserListView, UserOptionsEditView, UserSetPasswordView + UserGroupsView, UserListView, UserOptionsEditView ) urlpatterns_current_user = [ @@ -72,14 +72,6 @@ urlpatterns_users = [ regex=r'^users/(?P\d+)/groups/$', view=UserGroupsView.as_view(), name='user_groups' ), - url( - regex=r'^users/(?P\d+)/set_password/$', - view=UserSetPasswordView.as_view(), name='user_set_password' - ), - url( - regex=r'^users/multiple/set_password/$', - view=UserSetPasswordView.as_view(), name='user_multiple_set_password' - ), url( regex=r'^users/(?P\d+)/options/$', view=UserOptionsEditView.as_view(), name='user_options' diff --git a/mayan/apps/user_management/views.py b/mayan/apps/user_management/views.py index d5964e20cd..a44f9d8b1f 100644 --- a/mayan/apps/user_management/views.py +++ b/mayan/apps/user_management/views.py @@ -1,10 +1,7 @@ from __future__ import absolute_import, unicode_literals from django.contrib import messages -from django.contrib.auth import get_user_model -from django.contrib.auth.forms import SetPasswordForm from django.contrib.auth.models import Group -from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.template import RequestContext @@ -13,9 +10,8 @@ from django.utils.translation import ungettext, ugettext_lazy as _ from mayan.apps.common.generics import ( AddRemoveView, MultipleObjectConfirmActionView, - MultipleObjectFormActionView, SingleObjectCreateView, - SingleObjectDeleteView, SingleObjectDetailView, SingleObjectEditView, - SingleObjectListView + SingleObjectCreateView, SingleObjectDeleteView, SingleObjectDetailView, + SingleObjectEditView, SingleObjectListView ) from .forms import UserForm @@ -162,7 +158,7 @@ class UserCreateView(SingleObjectCreateView): super(UserCreateView, self).form_valid(form=form) return HttpResponseRedirect( reverse( - viewname='user_management:user_set_password', + viewname='authentication:user_set_password', kwargs={'pk': self.object.pk} ) ) @@ -180,13 +176,13 @@ class UserDeleteView(MultipleObjectConfirmActionView): ) def get_extra_context(self): - queryset = self.get_queryset() + queryset = self.get_object_list() result = { 'title': ungettext( - 'Delete user', - 'Delete users', - queryset.count() + singular='Delete user', + plural='Delete users', + number=queryset.count() ) } @@ -332,71 +328,3 @@ class UserOptionsEditView(SingleObjectEditView): return get_object_or_404( klass=get_user_queryset(), pk=self.kwargs['pk'] ) - - -class UserSetPasswordView(MultipleObjectFormActionView): - form_class = SetPasswordForm - model = get_user_model() - object_permission = permission_user_edit - success_message = _('Password change request performed on %(count)d user') - success_message_plural = _( - 'Password change request performed on %(count)d users' - ) - - def get_extra_context(self): - queryset = self.get_queryset() - - result = { - 'submit_label': _('Submit'), - 'title': ungettext( - 'Change user password', - 'Change users passwords', - queryset.count() - ) - } - - if queryset.count() == 1: - result.update( - { - 'object': queryset.first(), - 'title': _('Change password for user: %s') % queryset.first() - } - ) - - return result - - def get_form_extra_kwargs(self): - queryset = self.get_queryset() - result = {} - if queryset: - result['user'] = queryset.first() - return result - else: - raise PermissionDenied - - def object_action(self, form, instance): - try: - if instance.is_superuser or instance.is_staff: - messages.error( - message=_( - 'Super user and staff user password ' - 'reseting is not allowed, use the admin ' - 'interface for these cases.' - ), request=self.request - ) - else: - instance.set_password(form.cleaned_data['new_password1']) - instance.save() - messages.success( - message=_( - 'Successful password reset for user: %s.' - ) % instance, request=self.request - ) - except Exception as exception: - messages.error( - message=_( - 'Error reseting password for user "%(user)s": %(error)s' - ) % { - 'user': instance, 'error': exception - }, request=self.request - )