From 8ab3c58ead530d79a28c0a32bb68e92c8ae0d656 Mon Sep 17 00:00:00 2001 From: Oliver Stolpe Date: Wed, 11 Dec 2024 15:38:23 +0100 Subject: [PATCH] feat: add function to remove user from a group (#353) (#355) --- adminsec/email.py | 14 +- adminsec/tests/test_email.py | 4 +- adminsec/tests/test_views.py | 233 +++++++++ adminsec/views.py | 152 +++++- usersec/forms.py | 21 + usersec/models.py | 91 +++- usersec/rules.py | 53 ++ ...cprojectcreaterequest_retract_confirm.html | 9 - .../hpcuserchangerequest_retract_confirm.html | 9 - .../hpcusercreaterequest_retract_confirm.html | 9 - .../hpcuserdeleterequest_approve_confirm.html | 9 + .../hpcuserdeleterequest_delete_confirm.html | 9 + .../hpcuserdeleterequest_deny_confirm.html | 9 + .../usersec/hpcuserdeleterequest_detail.html | 35 ++ .../usersec/hpcuserdeleterequest_form.html | 78 +++ usersec/templates/usersec/modules/manage.html | 50 +- .../usersec/modules/overview_group.html | 6 +- usersec/tests/test_forms.py | 26 + usersec/tests/test_models.py | 280 ++++++++++- usersec/tests/test_rules.py | 458 ++++++++++++++++++ usersec/tests/test_views.py | 273 +++++++++++ usersec/urls.py | 4 +- usersec/views.py | 201 +++++++- utils/cli/hpc_access_cli/states.py | 13 +- 24 files changed, 1957 insertions(+), 89 deletions(-) delete mode 100644 usersec/templates/usersec/hpcprojectcreaterequest_retract_confirm.html delete mode 100644 usersec/templates/usersec/hpcuserchangerequest_retract_confirm.html delete mode 100644 usersec/templates/usersec/hpcusercreaterequest_retract_confirm.html create mode 100644 usersec/templates/usersec/hpcuserdeleterequest_approve_confirm.html create mode 100644 usersec/templates/usersec/hpcuserdeleterequest_delete_confirm.html create mode 100644 usersec/templates/usersec/hpcuserdeleterequest_deny_confirm.html create mode 100644 usersec/templates/usersec/hpcuserdeleterequest_detail.html create mode 100644 usersec/templates/usersec/hpcuserdeleterequest_form.html diff --git a/adminsec/email.py b/adminsec/email.py index ce771aa..17d4e4b 100644 --- a/adminsec/email.py +++ b/adminsec/email.py @@ -214,11 +214,9 @@ {footer} """.lstrip() -#: -SUBJECT_MANAGER_CHANGE_REQUEST_APPROVED = ( - SUBJECT_PREFIX + "Your {request_type} request has been approved" -) -NOTIFICATION_MANAGER_CHANGE_REQUEST_APPROVED = """ +#: Notification text for managers for a request approval +SUBJECT_MANAGER_REQUEST_APPROVED = SUBJECT_PREFIX + "Your {request_type} request has been approved" +NOTIFICATION_MANAGER_REQUEST_APPROVED = """ {greeting} your {request_type} request has been approved. Please see the active changes @@ -521,11 +519,11 @@ def send_notification_manager_project_created(request, project): return send_mail(subject, message, [request.requester.email]) -def send_notification_manager_change_request_approved(request): - subject = SUBJECT_MANAGER_CHANGE_REQUEST_APPROVED.format( +def send_notification_manager_request_approved(request): + subject = SUBJECT_MANAGER_REQUEST_APPROVED.format( request_type=request.get_request_type(), ) - message = NOTIFICATION_MANAGER_CHANGE_REQUEST_APPROVED.format( + message = NOTIFICATION_MANAGER_REQUEST_APPROVED.format( greeting=USER_GREETING.format(user=request.requester.name), request_type=request.get_request_type(), hpc_access_link=HPC_ACCESS_LINK + request.get_detail_path(), diff --git a/adminsec/tests/test_email.py b/adminsec/tests/test_email.py index 2a7af12..8abbe52 100644 --- a/adminsec/tests/test_email.py +++ b/adminsec/tests/test_email.py @@ -4,11 +4,11 @@ from adminsec.email import ( send_mail, send_notification_admin_request, - send_notification_manager_change_request_approved, send_notification_manager_group_created, send_notification_manager_group_request, send_notification_manager_project_created, send_notification_manager_project_request, + send_notification_manager_request_approved, send_notification_manager_request_denied, send_notification_manager_revision_required, send_notification_manager_user_decided_invitation, @@ -71,7 +71,7 @@ def test_send_notification_manager_project_created(self): self.assertEqual(len(mail.outbox), 1) def test_send_notification_manager_change_request_approved(self): - ret = send_notification_manager_change_request_approved(self.hpc_user_change_request) + ret = send_notification_manager_request_approved(self.hpc_user_change_request) self.assertEqual(ret, 1) self.assertEqual(len(mail.outbox), 1) diff --git a/adminsec/tests/test_views.py b/adminsec/tests/test_views.py index 850256b..998adb3 100644 --- a/adminsec/tests/test_views.py +++ b/adminsec/tests/test_views.py @@ -36,6 +36,7 @@ HpcProjectCreateRequestFactory, HpcUserChangeRequestFactory, HpcUserCreateRequestFactory, + HpcUserDeleteRequestFactory, HpcUserFactory, TermsAndConditionsFactory, ) @@ -1284,6 +1285,238 @@ def test_post(self): self.assertEqual(len(mail.outbox), 1) +class TestHpcUserDeleteRequestDetailView(TestViewBase): + """Tests for HpcUserDeleteRequestDetailView.""" + + def test_get(self): + request = HpcUserDeleteRequestFactory( + requester=self.user_owner, user=self.hpc_member, status=REQUEST_STATUS_ACTIVE + ) + + with self.login(self.user_hpcadmin): + response = self.client.get( + reverse( + "adminsec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": request.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertFalse(response.context["is_decided"]) + self.assertFalse(response.context["is_denied"]) + self.assertFalse(response.context["is_retracted"]) + self.assertFalse(response.context["is_approved"]) + self.assertTrue(response.context["is_active"]) + self.assertFalse(response.context["is_revision"]) + self.assertTrue(response.context["admin"]) + + def test_get_retracted(self): + request = HpcUserDeleteRequestFactory( + requester=self.user_owner, user=self.hpc_member, status=REQUEST_STATUS_RETRACTED + ) + + with self.login(self.user_hpcadmin): + response = self.client.get( + reverse( + "adminsec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": request.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertFalse(response.context["is_decided"]) + self.assertFalse(response.context["is_denied"]) + self.assertTrue(response.context["is_retracted"]) + self.assertFalse(response.context["is_approved"]) + self.assertFalse(response.context["is_active"]) + self.assertFalse(response.context["is_revision"]) + + def test_get_denied(self): + request = HpcUserDeleteRequestFactory( + requester=self.user_owner, user=self.hpc_member, status=REQUEST_STATUS_DENIED + ) + + with self.login(self.user_hpcadmin): + response = self.client.get( + reverse( + "adminsec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": request.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertTrue(response.context["is_decided"]) + self.assertTrue(response.context["is_denied"]) + self.assertFalse(response.context["is_retracted"]) + self.assertFalse(response.context["is_approved"]) + self.assertFalse(response.context["is_active"]) + self.assertFalse(response.context["is_revision"]) + + def test_get_approved(self): + request = HpcUserDeleteRequestFactory( + requester=self.user_owner, user=self.hpc_member, status=REQUEST_STATUS_APPROVED + ) + + with self.login(self.user_hpcadmin): + response = self.client.get( + reverse( + "adminsec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": request.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertTrue(response.context["is_decided"]) + self.assertFalse(response.context["is_denied"]) + self.assertFalse(response.context["is_retracted"]) + self.assertTrue(response.context["is_approved"]) + self.assertFalse(response.context["is_active"]) + self.assertFalse(response.context["is_revision"]) + + +class TestHpcUserDeleteRequestApproveView(TestViewBase): + """Tests for HpcUserDeleteRequestApproveView.""" + + def setUp(self): + super().setUp() + self.obj = HpcUserDeleteRequestFactory( + requester=self.user_owner, user=self.hpc_member, status=REQUEST_STATUS_ACTIVE + ) + + def test_get(self): + with self.login(self.user_hpcadmin): + response = self.client.get( + reverse( + "adminsec:hpcuserdeleterequest-approve", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ) + ) + self.assertEqual(response.status_code, 200) + self.assertIsNotNone(response.context["form"]) + + def test_post(self): + with self.login(self.user_hpcadmin): + response = self.client.post( + reverse( + "adminsec:hpcuserdeleterequest-approve", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ), + ) + + self.assertRedirects( + response, + reverse("adminsec:overview"), + ) + + messages = list(get_messages(response.wsgi_request)) + self.assertEqual(len(messages), 1) + self.assertEqual(str(messages[0]), "Successfully approved request for user deletion.") + + self.assertEqual(self.obj.status, REQUEST_STATUS_ACTIVE) + self.obj.refresh_from_db() + self.assertEqual(self.obj.status, REQUEST_STATUS_APPROVED) + + self.assertEqual(len(mail.outbox), 1) + + +class TestHpcUserDeleteRequestRevisionView(TestViewBase): + """Tests for HpcUserDeleteRequestRevisionView.""" + + def setUp(self): + super().setUp() + self.obj = HpcUserDeleteRequestFactory( + requester=self.user_owner, user=self.hpc_member, status=REQUEST_STATUS_ACTIVE + ) + + def test_get(self): + with self.login(self.user_hpcadmin): + response = self.client.get( + reverse( + "adminsec:hpcuserdeleterequest-revision", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["form"]["comment"].value(), "") + self.assertTrue(response.context["update"]) + + def test_post(self): + update = { + "comment": "I made a comment!", + } + + with self.login(self.user_hpcadmin): + response = self.client.post( + reverse( + "adminsec:hpcuserdeleterequest-revision", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ), + update, + ) + self.assertRedirects( + response, + reverse("adminsec:overview"), + ) + + self.obj.refresh_from_db() + + self.assertEqual(self.obj.comment, update["comment"]) + + messages = list(get_messages(response.wsgi_request)) + self.assertEqual(len(messages), 1) + self.assertEqual(str(messages[0]), "Successfully requested revision for user deletion.") + + self.assertEqual(len(mail.outbox), 1) + + +class TestHpcUserDeleteRequestDenyView(TestViewBase): + """Tests for HpcUserDeleteRequestDenyView.""" + + def setUp(self): + super().setUp() + self.obj = HpcUserDeleteRequestFactory( + requester=self.user_owner, user=self.hpc_member, status=REQUEST_STATUS_ACTIVE + ) + + def test_get(self): + with self.login(self.user_hpcadmin): + response = self.client.get( + reverse( + "adminsec:hpcuserdeleterequest-deny", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ) + ) + self.assertEqual(response.status_code, 200) + self.assertIsNotNone(response.context["form"]) + + def test_post(self): + with self.login(self.user_hpcadmin): + response = self.client.post( + reverse( + "adminsec:hpcuserdeleterequest-deny", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ), + data={"comment": "Denied"}, + ) + + self.assertRedirects( + response, + reverse("adminsec:overview"), + ) + + messages = list(get_messages(response.wsgi_request)) + self.assertEqual(len(messages), 1) + self.assertEqual(str(messages[0]), "Successfully denied request for user deletion.") + + self.assertEqual(self.obj.status, REQUEST_STATUS_ACTIVE) + self.obj.refresh_from_db() + self.assertEqual(self.obj.comment, "Denied") + self.assertEqual(self.obj.status, REQUEST_STATUS_DENIED) + + self.assertEqual(len(mail.outbox), 1) + + class TestHpcProjectCreateRequestDetailView(TestViewBase): """Tests for HpcProjectCreateRequestDetailView.""" diff --git a/adminsec/views.py b/adminsec/views.py index a50d926..5530678 100644 --- a/adminsec/views.py +++ b/adminsec/views.py @@ -37,9 +37,9 @@ LDAP_USERNAME_SEPARATOR, ) from adminsec.email import ( - send_notification_manager_change_request_approved, send_notification_manager_group_created, send_notification_manager_project_created, + send_notification_manager_request_approved, send_notification_manager_request_denied, send_notification_manager_revision_required, send_notification_user_consent, @@ -55,6 +55,7 @@ HpcProjectCreateRequestForm, HpcUserChangeRequestForm, HpcUserCreateRequestForm, + HpcUserDeleteRequestForm, ) from usersec.models import ( OBJECT_STATUS_ACTIVE, @@ -70,6 +71,7 @@ HpcUser, HpcUserChangeRequest, HpcUserCreateRequest, + HpcUserDeleteRequest, TermsAndConditions, ) from usersec.views import ( @@ -80,6 +82,7 @@ MSG_PART_SUBMIT, MSG_PART_UPDATE, MSG_PART_USER_CREATION, + MSG_PART_USER_DELETION, MSG_PART_USER_UPDATE, MSG_REQUEST_FAILURE, HpcPermissionMixin, @@ -183,6 +186,7 @@ def get_context_data(self, **kwargs): HpcProjectChangeRequest.objects.active(), HpcUserCreateRequest.objects.active(), HpcUserChangeRequest.objects.active(), + HpcUserDeleteRequest.objects.active(), ) ) @@ -726,7 +730,7 @@ def post(self, request, *args, **kwargs): ) if settings.SEND_EMAIL: - send_notification_manager_change_request_approved(obj) + send_notification_manager_request_approved(obj) with transaction.atomic(): obj.comment = COMMENT_APPROVED @@ -1036,20 +1040,144 @@ def post(self, request, *args, **kwargs): return HttpResponseRedirect(reverse("adminsec:overview")) -class HpcUserDeleteRequestDetailView(View): - pass +class HpcUserDeleteRequestDetailView(HpcPermissionMixin, DetailView): + """HPC user delete request detail view.""" + template_name = "usersec/hpcuserdeleterequest_detail.html" + model = HpcUserDeleteRequest + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "adminsec.is_hpcadmin" -class HpcUserDeleteRequestRevisionView(View): - pass + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + obj = self.get_object() + context["is_decided"] = obj.is_decided() + context["is_denied"] = obj.is_denied() + context["is_retracted"] = obj.is_retracted() + context["is_approved"] = obj.is_approved() + context["is_active"] = obj.is_active() + context["is_revision"] = obj.is_revision() + context["is_archived"] = obj.is_archived() + context["admin"] = True + return context -class HpcUserDeleteRequestApproveView(View): - pass +class HpcUserDeleteRequestRevisionView(HpcPermissionMixin, UpdateView): + """HPC user delete request revision view.""" + template_name = "usersec/hpcuserdeleterequest_form.html" + model = HpcUserDeleteRequest + form_class = HpcUserDeleteRequestForm + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "adminsec.is_hpcadmin" + success_url = reverse_lazy("adminsec:overview") -class HpcUserDeleteRequestDenyView(View): - pass + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["update"] = True + context["admin"] = True + return context + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs.update({"user": self.request.user}) + return kwargs + + def get_initial(self): + initial = super().get_initial() + initial["comment"] = "" + return initial + + def form_valid(self, form): + obj = form.save(commit=False) + obj.editor = self.request.user + obj = obj.revision_with_version() + + if not obj: + messages.error( + self.request, MSG_REQUEST_REVISION_FAILURE.format(MSG_PART_USER_DELETION) + ) + return HttpResponseRedirect(reverse("adminsec:overview")) + + if settings.SEND_EMAIL: + send_notification_manager_revision_required(obj) + + messages.success(self.request, MSG_REQUEST_REVISION_SUCCESS.format(MSG_PART_USER_DELETION)) + return HttpResponseRedirect(self.get_success_url()) + + +class HpcUserDeleteRequestApproveView(HpcPermissionMixin, DeleteView): + """HpcUserDeleteRequest approve view.""" + + template_name_suffix = "_approve_confirm" + model = HpcUserDeleteRequest + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "adminsec.is_hpcadmin" + success_url = reverse_lazy("adminsec:overview") + + def post(self, request, *args, **kwargs): + obj = self.get_object() + + try: + with transaction.atomic(): + obj.user.primary_group = None + obj.user.delete_with_version() + + except Exception as e: + messages.error(self.request, "Could not delete user: {}".format(e)) + return HttpResponseRedirect( + reverse( + "adminsec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": obj.uuid}, + ) + ) + + with transaction.atomic(): + obj.comment = COMMENT_APPROVED + obj.editor = self.request.user + obj.approve_with_version() + + if settings.SEND_EMAIL: + send_notification_manager_request_approved(obj) + + messages.success(self.request, MSG_REQUEST_APPROVED_SUCCESS.format(MSG_PART_USER_DELETION)) + return HttpResponseRedirect(reverse("adminsec:overview")) + + +class HpcUserDeleteRequestDenyView(HpcPermissionMixin, DeleteView): + """HpcUserDeleteRequest deny view.""" + + template_name_suffix = "_deny_confirm" + model = HpcUserDeleteRequest + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "adminsec.is_hpcadmin" + + def get_context_data(self, **kwargs): + context = super().get_context_data() + context["form"] = HpcUserDeleteRequestForm( + user=self.request.user, + instance=context["object"], + initial={ + "comment": "", + }, + ) + return context + + def post(self, request, *args, **kwargs): + obj = self.get_object() + obj.comment = self.request.POST.get("comment") + obj.editor = self.request.user + obj.deny_with_version() + + if settings.SEND_EMAIL: + send_notification_manager_request_denied(obj) + + messages.success(self.request, MSG_REQUEST_DENIED_SUCCESS.format(MSG_PART_USER_DELETION)) + return HttpResponseRedirect(reverse("adminsec:overview")) class HpcUserChangeRequestDetailView(HpcPermissionMixin, DetailView): @@ -1150,7 +1278,7 @@ def post(self, request, *args, **kwargs): obj.approve_with_version() if settings.SEND_EMAIL: - send_notification_manager_change_request_approved(obj) + send_notification_manager_request_approved(obj) messages.success(self.request, MSG_REQUEST_APPROVED_SUCCESS.format(MSG_PART_USER_UPDATE)) return HttpResponseRedirect(reverse("adminsec:overview")) @@ -1333,7 +1461,7 @@ def post(self, request, *args, **kwargs): obj.approve_with_version() if settings.SEND_EMAIL: - send_notification_manager_change_request_approved(obj) + send_notification_manager_request_approved(obj) messages.success(self.request, MSG_REQUEST_APPROVED_SUCCESS.format(MSG_PART_PROJECT_UPDATE)) return HttpResponseRedirect(reverse("adminsec:overview")) diff --git a/usersec/forms.py b/usersec/forms.py index 5b94de8..f9cb3f1 100644 --- a/usersec/forms.py +++ b/usersec/forms.py @@ -22,6 +22,7 @@ HpcUser, HpcUserChangeRequest, HpcUserCreateRequest, + HpcUserDeleteRequest, ) @@ -296,6 +297,26 @@ def __init__(self, *args, user, **kwargs): self.fields["comment"].widget.attrs["rows"] = 3 +class HpcUserDeleteRequestForm(forms.ModelForm): + """Form for HpcUserDeleteRequest.""" + + class Meta: + model = HpcUserDeleteRequest + fields = [ + "comment", + ] + + def __init__(self, *args, user, **kwargs): + super().__init__(*args, **kwargs) + + if user.is_hpcadmin: + self.fields["comment"].required = True + + # Some cosmetics + self.fields["comment"].widget.attrs["class"] = "form-control" + self.fields["comment"].widget.attrs["rows"] = 3 + + class HpcProjectCreateRequestForm(forms.ModelForm): """Form for HpcProjectCreateRequest.""" diff --git a/usersec/models.py b/usersec/models.py index 3263bcc..eea173a 100644 --- a/usersec/models.py +++ b/usersec/models.py @@ -153,7 +153,15 @@ class VersionRequestManager(VersionManager): def active(self, **kwargs): kwargs.update({"status": REQUEST_STATUS_ACTIVE}) - return super().get_queryset().filter(**kwargs) + return self.get_queryset().filter(**kwargs) + + def in_process(self, **kwargs): + kwargs.update({"status__in": [REQUEST_STATUS_ACTIVE, REQUEST_STATUS_REVISION]}) + return self.get_queryset().filter(**kwargs) + + def retracted(self, **kwargs): + kwargs.update({"status": REQUEST_STATUS_RETRACTED}) + return self.get_queryset().filter(**kwargs) class VersionManagerMixin: @@ -591,7 +599,48 @@ class Meta: ) -class HpcUser(ContactMixin, VersionManagerMixin, CheckQuotaMixin, HpcUserAbstract): +ROLE_ALUMNI = "Alumni" +ROLE_DELEGATE = "Delegate" +ROLE_PI = "PI" +ROLE_MEMBER = "Member" + + +class HpcObjectPendingRequestMixin: + """Mixin for objects with pending requests.""" + + def has_pending_delete_request(self): + return ( + getattr(self, f"{self.__class__.__name__.lower()}deleterequest").in_process().exists() + ) + + def has_pending_change_request(self): + return ( + getattr(self, f"{self.__class__.__name__.lower()}changerequest").in_process().exists() + ) + + def has_pending_requests(self): + return self.has_pending_delete_request() or self.has_pending_change_request() + + def has_retracted_delete_request(self): + return getattr(self, f"{self.__class__.__name__.lower()}deleterequest").retracted().exists() + + def has_retracted_change_request(self): + return getattr(self, f"{self.__class__.__name__.lower()}changerequest").retracted().exists() + + def retracted_delete_request(self): + return getattr(self, f"{self.__class__.__name__.lower()}deleterequest").retracted().first() + + def retracted_change_request(self): + return getattr(self, f"{self.__class__.__name__.lower()}changerequest").retracted().first() + + +class HpcUser( + ContactMixin, + VersionManagerMixin, + CheckQuotaMixin, + HpcObjectPendingRequestMixin, + HpcUserAbstract, +): """HpcUser model""" #: Set custom manager @@ -624,10 +673,32 @@ def __str__(self): def get_pending_invitations(self): return self.hpcprojectinvitations.filter(status=INVITATION_STATUS_PENDING) + @property + def role(self): + if self.is_alumni: + return ROLE_ALUMNI + elif self.is_pi: + return ROLE_PI + elif self.is_delegate: + return ROLE_DELEGATE + return ROLE_MEMBER + @property def is_pi(self): return self.primary_group.owner == self + @property + def is_delegate(self): + return self.primary_group.delegate == self + + @property + def is_alumni(self): + return self.primary_group is None + + @property + def is_member(self): + return not (self.is_alumni or self.is_pi or self.is_delegate) + class HpcUserVersion(HpcUserAbstract): """HpcUserVersion model""" @@ -739,7 +810,13 @@ class Meta: expiration = models.DateTimeField(help_text="Expiration date of the group") -class HpcGroup(ContactMixin, VersionManagerMixin, CheckQuotaMixin, HpcGroupAbstract): +class HpcGroup( + ContactMixin, + VersionManagerMixin, + CheckQuotaMixin, + # HpcObjectPendingRequestMixin, + HpcGroupAbstract, +): """HpcGroup model""" #: Set custom manager @@ -893,7 +970,13 @@ class Meta: expiration = models.DateTimeField(help_text="Expiration date of the project") -class HpcProject(ContactMixin, VersionManagerMixin, CheckQuotaMixin, HpcProjectAbstract): +class HpcProject( + ContactMixin, + VersionManagerMixin, + CheckQuotaMixin, + # HpcObjectPendingRequestMixin, + HpcProjectAbstract, +): """HpcProject model""" #: Set custom manager diff --git a/usersec/rules.py b/usersec/rules.py index d3cd49d..5e22b89 100644 --- a/usersec/rules.py +++ b/usersec/rules.py @@ -94,6 +94,7 @@ def _is_delegate_of_hpcuser(user, hpcuser): can_view_hpcuser = is_hpcuser | is_pi_of_hpcuser | is_delegate_of_hpcuser can_create_hpcuserchangerequest = (is_pi_of_hpcuser | is_delegate_of_hpcuser) & ~view_mode_enabled +can_create_hpcuserdeleterequest = (is_pi_of_hpcuser | is_delegate_of_hpcuser) & ~view_mode_enabled # HpcGroupCreateRequest based @@ -359,6 +360,51 @@ def _is_group_delegate_by_hpcuserchangerequest(user, hpcuserchangerequest): ) & ~view_mode_enabled +# HpcUserDeleteRequest based +# ------------------------------------------------------------------------------ + + +@rules.predicate +def _is_group_owner_by_hpcuserdeleterequest(user, hpcuserdeleterequest): + if hpcuserdeleterequest is None: + raise ValueError("HpcUserDeleteRequest is None") + + owner = hpcuserdeleterequest.user.primary_group.owner + + if owner: + return owner.user == user + + return False + + +@rules.predicate +def _is_group_delegate_by_hpcuserdeleterequest(user, hpcuserdeleterequest): + if hpcuserdeleterequest is None: + raise ValueError("HpcUserDeleteRequest is None") + + delegate = hpcuserdeleterequest.user.primary_group.delegate + + if delegate: + return delegate.user == user + + return False + + +is_group_owner_by_hpcuserdeleterequest = ( + ~is_hpcadmin & is_cluster_user & _is_group_owner_by_hpcuserdeleterequest +) +is_group_delegate_by_hpcuserdeleterequest = ( + ~is_hpcadmin & is_cluster_user & _is_group_delegate_by_hpcuserdeleterequest +) + +can_view_hpcuserdeleterequest = ( + is_group_owner_by_hpcuserdeleterequest | is_group_delegate_by_hpcuserdeleterequest +) & ~view_mode_enabled +can_manage_hpcuserdeleterequest = ( + is_group_owner_by_hpcuserdeleterequest | is_group_delegate_by_hpcuserdeleterequest +) & ~view_mode_enabled + + # HpcGroup based # ------------------------------------------------------------------------------ @@ -521,6 +567,13 @@ def _is_project_invited_user(user, hpcprojectinvitation): rules.add_perm("usersec.create_hpcuserchangerequest", can_create_hpcuserchangerequest) rules.add_perm("usersec.manage_hpcuserchangerequest", can_manage_hpcuserchangerequest) +# HpcUserDeleteRequest related +# ------------------------------------------------------------------------------ +rules.add_perm("usersec.view_hpcuserdeleterequest", can_view_hpcuserdeleterequest) +rules.add_perm("usersec.create_hpcuserdeleterequest", can_create_hpcuserdeleterequest) +rules.add_perm("usersec.manage_hpcuserdeleterequest", can_manage_hpcuserdeleterequest) + + # HpcGroup related # ------------------------------------------------------------------------------ diff --git a/usersec/templates/usersec/hpcprojectcreaterequest_retract_confirm.html b/usersec/templates/usersec/hpcprojectcreaterequest_retract_confirm.html deleted file mode 100644 index 12ef508..0000000 --- a/usersec/templates/usersec/hpcprojectcreaterequest_retract_confirm.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'base.html' %} - -{% load common %} - -{% block content %} - - {% include "usersec/modules/confirm.html" with action="Retract" object_type="request to create a project" %} - -{% endblock content %} diff --git a/usersec/templates/usersec/hpcuserchangerequest_retract_confirm.html b/usersec/templates/usersec/hpcuserchangerequest_retract_confirm.html deleted file mode 100644 index 5ba2966..0000000 --- a/usersec/templates/usersec/hpcuserchangerequest_retract_confirm.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'base.html' %} - -{% load common %} - -{% block content %} - - {% include "usersec/modules/confirm.html" with action="Retract" object_type="request to change a user" %} - -{% endblock content %} diff --git a/usersec/templates/usersec/hpcusercreaterequest_retract_confirm.html b/usersec/templates/usersec/hpcusercreaterequest_retract_confirm.html deleted file mode 100644 index d26f014..0000000 --- a/usersec/templates/usersec/hpcusercreaterequest_retract_confirm.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends 'base.html' %} - -{% load common %} - -{% block content %} - - {% include "usersec/modules/confirm.html" with action="Retract" object_type="request to invite a user" %} - -{% endblock content %} diff --git a/usersec/templates/usersec/hpcuserdeleterequest_approve_confirm.html b/usersec/templates/usersec/hpcuserdeleterequest_approve_confirm.html new file mode 100644 index 0000000..985f133 --- /dev/null +++ b/usersec/templates/usersec/hpcuserdeleterequest_approve_confirm.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% load common %} + +{% block content %} + + {% include "usersec/modules/confirm.html" with action="Approve" object_type="request to remove a user" %} + +{% endblock content %} diff --git a/usersec/templates/usersec/hpcuserdeleterequest_delete_confirm.html b/usersec/templates/usersec/hpcuserdeleterequest_delete_confirm.html new file mode 100644 index 0000000..1fedb18 --- /dev/null +++ b/usersec/templates/usersec/hpcuserdeleterequest_delete_confirm.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% load common %} + +{% block content %} + + {% include "usersec/modules/confirm.html" with action="Delete" object_type="request to remove a user" %} + +{% endblock content %} diff --git a/usersec/templates/usersec/hpcuserdeleterequest_deny_confirm.html b/usersec/templates/usersec/hpcuserdeleterequest_deny_confirm.html new file mode 100644 index 0000000..74d8bb1 --- /dev/null +++ b/usersec/templates/usersec/hpcuserdeleterequest_deny_confirm.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% load common %} + +{% block content %} + + {% include "usersec/modules/confirm.html" with action="Deny" object_type="request to remove a user" %} + +{% endblock content %} diff --git a/usersec/templates/usersec/hpcuserdeleterequest_detail.html b/usersec/templates/usersec/hpcuserdeleterequest_detail.html new file mode 100644 index 0000000..4065b12 --- /dev/null +++ b/usersec/templates/usersec/hpcuserdeleterequest_detail.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} + +{% load common %} + +{% block content %} + +

+ Remove User From Group + + {% if admin %} + {% include "usersec/modules/request_buttons_admin.html" %} + {% else %} + {% include "usersec/modules/request_buttons_user.html" %} + {% endif %} + +

+ +
+
+
+ {% if admin %} +
Requester
+
{{ object.requester }}
+ {% endif %} + +
User
+
{{ object.user }}
+
+
+
+ {% include "usersec/modules/comments.html" %} +
+
+ +{% endblock content %} diff --git a/usersec/templates/usersec/hpcuserdeleterequest_form.html b/usersec/templates/usersec/hpcuserdeleterequest_form.html new file mode 100644 index 0000000..92e8354 --- /dev/null +++ b/usersec/templates/usersec/hpcuserdeleterequest_form.html @@ -0,0 +1,78 @@ +{% extends 'base.html' %} + +{% load crispy_forms_tags %} + +{% block content %} + +

+ {% if update %} + {% if admin %} + Request Revision + {% else %} + Update User Remove Request + {% endif %} + {% else %} + Remove User From Group + {% endif %} + — + {{ user }} +

+ +{% include "usersec/modules/comments.html" %} + +
+ {% csrf_token %} + {% if admin %} +
+
Requester
+
{{ object.requester.username }}
+ +
Expiration
+
{{ object.expiration|date:"Y-m-d" }}
+
+ {% endif %} + + {% for field in form.hidden_fields %} + {{ field }} + {% endfor %} + + {% for field in form.visible_fields %} +
+ {% if field.errors %} + + {% endif %} +
+ + {% if field.field.required %} + {{ field.label }}* + {% else %} + {{ field.label }} + {% endif %} + + {{ field }} +
+ {% if field.help_text %} +

{{ field.help_text|safe }}

+ {% endif %} +
+ {% endfor %} + + + Cancel + + +
+ +{% endblock content %} diff --git a/usersec/templates/usersec/modules/manage.html b/usersec/templates/usersec/modules/manage.html index 3829f86..391e1f7 100644 --- a/usersec/templates/usersec/modules/manage.html +++ b/usersec/templates/usersec/modules/manage.html @@ -95,6 +95,7 @@

Group Members

Name Username + Role Joined Expires @@ -105,20 +106,49 @@

Group Members

{{ member.user.name }} {{ member.username }} + {{ member.role }} {{ member.date_created|date:"Y-m-d H:i" }} {{ member.expiration|date:"Y-m-d H:i" }}
- - - - - - + {% if member.has_pending_requests %} + + + + {% elif member.has_retracted_change_request %} + + + + {% else %} + + + + {% endif %} + {% if not member.is_pi %} + {% if members.has_pending_requests %} + + + + {% elif member.has_retracted_delete_request %} + + + + {% else %} + + + + {% endif %} + {% endif %}
diff --git a/usersec/templates/usersec/modules/overview_group.html b/usersec/templates/usersec/modules/overview_group.html index 1450dce..7c193ea 100644 --- a/usersec/templates/usersec/modules/overview_group.html +++ b/usersec/templates/usersec/modules/overview_group.html @@ -4,9 +4,9 @@

AG {{ object.primary_group.name.capitalize }} - {% if object.hpcgroup_owner.exists %} + {% if object.is_pi %} Owner - {% elif object.hpcgroup_delegate.exists %} + {% elif object.is_delegate %} Delegate {% else %} Member @@ -31,7 +31,7 @@

Delegate
- {% if object.primary_group.delegate %} + {% if object.is_delegate %} {{ object.primary_group.delegate.user.name }} ({{ object.primary_group.delegate.username }}) {% else %} diff --git a/usersec/tests/test_forms.py b/usersec/tests/test_forms.py index 58f8cda..b8de19c 100644 --- a/usersec/tests/test_forms.py +++ b/usersec/tests/test_forms.py @@ -10,6 +10,7 @@ HpcProjectCreateRequestForm, HpcUserChangeRequestForm, HpcUserCreateRequestForm, + HpcUserDeleteRequestForm, ProjectSelectForm, UserSelectForm, ) @@ -227,6 +228,31 @@ def test_form_invalid_hpcadmin_comment_missing(self): self.assertEqual(form.errors["comment"], ["This field is required."]) +class TestHpcUserDeleteRequestForm(TestCase): + """Tests for HpcUserDeleteRequest form.""" + + def setUp(self): + super().setUp() + self.user = self.make_user("user") + self.user_owner = self.make_user("owner") + self.user_hpcadmin = self.make_user("hpcadmin") + self.user_hpcadmin.is_hpcadmin = True + self.user_hpcadmin.save() + + def test_form_valid(self): + form = HpcUserDeleteRequestForm(user=self.user_owner, data={}) + self.assertTrue(form.is_valid()) + + def test_form_valid_hpcadmin(self): + form = HpcUserDeleteRequestForm(user=self.user_hpcadmin, data={"comment": "comment"}) + self.assertTrue(form.is_valid()) + + def test_form_invalid_hpcadmin_comment_missing(self): + form = HpcUserDeleteRequestForm(user=self.user_hpcadmin, data={}) + self.assertFalse(form.is_valid()) + self.assertEqual(form.errors["comment"], ["This field is required."]) + + class TestHpcProjectCreateRequestForm(TestCase): """Tests for HpcProjectCreateRequest form.""" diff --git a/usersec/tests/test_models.py b/usersec/tests/test_models.py index 8da5e81..581033f 100644 --- a/usersec/tests/test_models.py +++ b/usersec/tests/test_models.py @@ -163,6 +163,22 @@ def _test_active(self): self.factory(requester=self.user, status=REQUEST_STATUS_APPROVED) self.assertEqual(list(self.model.objects.active()), [obj]) + def _test_retracted(self): + obj = self.factory(requester=self.user, status=REQUEST_STATUS_RETRACTED) + self.factory(requester=self.user, status=REQUEST_STATUS_ACTIVE) + self.factory(requester=self.user, status=REQUEST_STATUS_REVISION) + self.factory(requester=self.user, status=REQUEST_STATUS_DENIED) + self.factory(requester=self.user, status=REQUEST_STATUS_APPROVED) + self.assertEqual(list(self.model.objects.retracted()), [obj]) + + def _test_in_process(self): + obj1 = self.factory(requester=self.user, status=REQUEST_STATUS_ACTIVE) + obj2 = self.factory(requester=self.user, status=REQUEST_STATUS_REVISION) + self.factory(requester=self.user, status=REQUEST_STATUS_RETRACTED) + self.factory(requester=self.user, status=REQUEST_STATUS_DENIED) + self.factory(requester=self.user, status=REQUEST_STATUS_APPROVED) + self.assertEqual(list(self.model.objects.in_process()), [obj1, obj2]) + def _test_get_revision_url(self): obj = self.factory(requester=self.user) name = self.model.__name__.lower() @@ -376,13 +392,115 @@ def _test_get_detail_url_admin(self): self.assertEqual(obj.get_detail_url(self.hpcadmin), expected) -class TestHpcUser(VersionTesterMixin, TestCase): +class PendingRequestTesterMixin: + change_request_factory = None + delete_request_factory = None + obj_type = None + + def _test_has_pending_delete_request(self): + obj = self.factory() + data = {self.obj_type: obj} + request = self.delete_request_factory(**data) + + for status in (REQUEST_STATUS_ACTIVE, REQUEST_STATUS_REVISION): + request.status = status + request.save() + self.assertTrue(obj.has_pending_delete_request()) + + for status in (REQUEST_STATUS_APPROVED, REQUEST_STATUS_DENIED, REQUEST_STATUS_RETRACTED): + request.status = status + request.save() + self.assertFalse(obj.has_pending_delete_request()) + + def _test_has_pending_change_request(self): + obj = self.factory() + data = {self.obj_type: obj} + request = self.change_request_factory(**data) + + for status in (REQUEST_STATUS_ACTIVE, REQUEST_STATUS_REVISION): + request.status = status + request.save() + self.assertTrue(obj.has_pending_change_request()) + + for status in (REQUEST_STATUS_APPROVED, REQUEST_STATUS_DENIED, REQUEST_STATUS_RETRACTED): + request.status = status + request.save() + self.assertFalse(obj.has_pending_change_request()) + + def _test_has_retracted_change_request(self): + obj = self.factory() + request = self.change_request_factory( + **{self.obj_type: obj, "status": REQUEST_STATUS_RETRACTED} + ) + self.assertTrue(obj.has_retracted_change_request()) + + for status in ( + REQUEST_STATUS_ACTIVE, + REQUEST_STATUS_REVISION, + REQUEST_STATUS_APPROVED, + REQUEST_STATUS_DENIED, + ): + request.status = status + request.save() + self.assertFalse(obj.has_retracted_change_request()) + + def _test_has_retracted_delete_request(self): + obj = self.factory() + request = self.delete_request_factory( + **{self.obj_type: obj, "status": REQUEST_STATUS_RETRACTED} + ) + self.assertTrue(obj.has_retracted_delete_request()) + + for status in ( + REQUEST_STATUS_ACTIVE, + REQUEST_STATUS_REVISION, + REQUEST_STATUS_APPROVED, + REQUEST_STATUS_DENIED, + ): + request.status = status + request.save() + self.assertFalse(obj.has_retracted_delete_request()) + + def _test_retracted_delete_request(self): + obj = self.factory() + request = self.delete_request_factory( + **{self.obj_type: obj, "status": REQUEST_STATUS_RETRACTED} + ) + self.assertEqual(obj.retracted_delete_request(), request) + + def _test_retracted_change_request(self): + obj = self.factory() + request = self.change_request_factory( + **{self.obj_type: obj, "status": REQUEST_STATUS_RETRACTED} + ) + self.assertEqual(obj.retracted_change_request(), request) + + def _test_has_pending_requests(self): + obj = self.factory() + self.change_request_factory(**{self.obj_type: obj, "status": REQUEST_STATUS_ACTIVE}) + self.delete_request_factory(**{self.obj_type: obj, "status": REQUEST_STATUS_RETRACTED}) + self.assertTrue(obj.has_pending_requests()) + + def _test_has_pending_requests_false(self): + obj = self.factory() + self.change_request_factory(**{self.obj_type: obj, "status": REQUEST_STATUS_RETRACTED}) + self.delete_request_factory(**{self.obj_type: obj, "status": REQUEST_STATUS_RETRACTED}) + self.assertFalse(obj.has_pending_requests()) + + +class TestHpcUser(VersionTesterMixin, PendingRequestTesterMixin, TestCase): """Tests for HpcUser model""" + # Version Tester Mixin model = HpcUser version_model = HpcUserVersion factory = HpcUserFactory + # Pending Request Tester Mixin + obj_type = "user" + change_request_factory = HpcUserChangeRequestFactory + delete_request_factory = HpcUserDeleteRequestFactory + def test_create_with_version(self): self._test_create_with_version() @@ -430,6 +548,60 @@ def test_get_pending_invitations(self): list(user.get_pending_invitations()), ) + def test_is_pi_false(self): + user = self.factory() + self.assertFalse(user.is_pi) + + def test_is_pi_true(self): + user = self.factory(primary_group=None) + user.primary_group = HpcGroupFactory(owner=user) + user.save() + self.assertTrue(user.is_pi) + + def test_role_pi(self): + user = self.factory(primary_group=None) + user.primary_group = HpcGroupFactory(owner=user) + user.save() + self.assertEqual(user.role, "PI") + + def test_role_delegate(self): + user = self.factory(primary_group=None) + user.primary_group = HpcGroupFactory(delegate=user) + user.save() + self.assertEqual(user.role, "Delegate") + + def test_role_member(self): + user = self.factory() + self.assertEqual(user.role, "Member") + + def test_role_alumni(self): + user = self.factory(primary_group=None) + self.assertEqual(user.role, "Alumni") + + def test_has_pending_delete_request(self): + self._test_has_pending_delete_request() + + def test_has_pending_change_request(self): + self._test_has_pending_change_request() + + def test_has_retracted_change_request(self): + self._test_has_retracted_change_request() + + def test_has_retracted_delete_request(self): + self._test_has_retracted_delete_request() + + def test_retracted_delete_request(self): + self._test_retracted_delete_request() + + def test_retracted_change_request(self): + self._test_retracted_change_request() + + def test_has_pending_requests(self): + self._test_has_pending_requests() + + def test_has_pending_requests_false(self): + self._test_has_pending_requests_false() + def test_generate_quota_report_green(self): user = self.factory( resources_requested={TIER_USER_HOME: 20}, @@ -578,7 +750,7 @@ def test_get_manager_contact(self): self.factory().get_manager_contact() -class TestHpcGroup(VersionTesterMixin, TestCase): +class TestHpcGroup(VersionTesterMixin, PendingRequestTesterMixin, TestCase): """Tests for HpcGroup model""" model = HpcGroup @@ -842,8 +1014,32 @@ def test_get_user_active(self): ): obj.get_user_active() + # def test_has_pending_delete_request(self): + # self._test_has_pending_delete_request() + + # def test_has_pending_change_request(self): + # self._test_has_pending_change_request() + + # def test_has_retracted_change_request(self): + # self._test_has_retracted_change_request() -class TestHpcProject(VersionTesterMixin, TestCase): + # def test_has_retracted_delete_request(self): + # self._test_has_retracted_delete_request() + + # def test_retracted_delete_request(self): + # self._test_retracted_delete_request() + + # def test_retracted_change_request(self): + # self._test_retracted_change_request() + + # def test_has_pending_requests(self): + # self._test_has_pending_requests() + + # def test_has_pending_requests_false(self): + # self._test_has_pending_requests_false() + + +class TestHpcProject(VersionTesterMixin, PendingRequestTesterMixin, TestCase): """Tests for HpcProject model""" model = HpcProject @@ -1125,6 +1321,30 @@ def test_get_user_active(self): ): obj.get_user_active() + # def test_has_pending_delete_request(self): + # self._test_has_pending_delete_request() + + # def test_has_pending_change_request(self): + # self._test_has_pending_change_request() + + # def test_has_retracted_change_request(self): + # self._test_has_retracted_change_request() + + # def test_has_retracted_delete_request(self): + # self._test_has_retracted_delete_request() + + # def test_retracted_delete_request(self): + # self._test_retracted_delete_request() + + # def test_retracted_change_request(self): + # self._test_retracted_change_request() + + # def test_has_pending_requests(self): + # self._test_has_pending_requests() + + # def test_has_pending_requests_false(self): + # self._test_has_pending_requests_false() + class TestHpcGroupChangeRequest(RequestTesterMixin, VersionTesterMixin, TestCase): """Tests for HpcGroupChangeRequest model""" @@ -1200,6 +1420,12 @@ def test_is_revision(self): def test_active(self): self._test_active() + def test_in_process(self): + self._test_in_process() + + def test_retracted(self): + self._test_retracted() + def test_get_revision_url(self): self._test_get_revision_url() @@ -1296,6 +1522,12 @@ def test_is_revision(self): def test_active(self): self._test_active() + def test_in_process(self): + self._test_in_process() + + def test_retracted(self): + self._test_retracted() + def test_get_revision_url(self): self._test_get_revision_url() @@ -1392,6 +1624,12 @@ def test_is_revision(self): def test_active(self): self._test_active() + def test_in_process(self): + self._test_in_process() + + def test_retracted(self): + self._test_retracted() + def test_get_revision_url(self): self._test_get_revision_url() @@ -1488,6 +1726,12 @@ def test_is_revision(self): def test_active(self): self._test_active() + def test_in_process(self): + self._test_in_process() + + def test_retracted(self): + self._test_retracted() + def test_get_revision_url(self): self._test_get_revision_url() @@ -1584,6 +1828,12 @@ def test_is_revision(self): def test_active(self): self._test_active() + def test_in_process(self): + self._test_in_process() + + def test_retracted(self): + self._test_retracted() + def test_get_revision_url(self): self._test_get_revision_url() @@ -1680,6 +1930,12 @@ def test_is_revision(self): def test_active(self): self._test_active() + def test_in_process(self): + self._test_in_process() + + def test_retracted(self): + self._test_retracted() + def test_get_revision_url(self): self._test_get_revision_url() @@ -1776,6 +2032,12 @@ def test_is_revision(self): def test_active(self): self._test_active() + def test_in_process(self): + self._test_in_process() + + def test_retracted(self): + self._test_retracted() + def test_get_revision_url(self): self._test_get_revision_url() @@ -1876,6 +2138,12 @@ def test_is_revision(self): def test_active(self): self._test_active() + def test_in_process(self): + self._test_in_process() + + def test_retracted(self): + self._test_retracted() + def test_get_revision_url(self): self._test_get_revision_url() @@ -1972,6 +2240,12 @@ def test_is_revision(self): def test_active(self): self._test_active() + def test_in_process(self): + self._test_in_process() + + def test_retracted(self): + self._test_retracted() + def test_get_revision_url(self): self._test_get_revision_url() diff --git a/usersec/tests/test_rules.py b/usersec/tests/test_rules.py index d094712..72ce015 100644 --- a/usersec/tests/test_rules.py +++ b/usersec/tests/test_rules.py @@ -26,6 +26,7 @@ HpcProjectInvitationFactory, HpcUserChangeRequestFactory, HpcUserCreateRequestFactory, + HpcUserDeleteRequestFactory, HpcUserFactory, ) @@ -132,6 +133,11 @@ def setUp(self): requester=self.user_owner, user=self.hpc_member ) + # Create HPC user delete request + self.hpc_user_delete_request = HpcUserDeleteRequestFactory( + requester=self.user_owner, user=self.hpc_member + ) + # Create HPC project create request self.hpc_project_create_request = HpcProjectCreateRequestFactory( requester=self.user_owner, group=self.hpc_group @@ -903,6 +909,63 @@ def test_manage_hpcprojectinvitation(self): self.assert_permissions_granted(perm, self.hpc_project_invitation, good_users) self.assert_permissions_denied(perm, self.hpc_project_invitation, bad_users) + def test_view_hpcuserdeleterequest(self): + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_pending, + self.user_hpcadmin, + self.user_member, + self.user_member2, + self.user_member_other_group, + self.user, + self.user_invited, + ] + perm = "usersec.view_hpcuserdeleterequest" + self.assert_permissions_granted(perm, self.hpc_user_delete_request, good_users) + self.assert_permissions_denied(perm, self.hpc_user_delete_request, bad_users) + + def test_create_hpcuserdeleterequest(self): + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_pending, + self.user_hpcadmin, + self.user_member, + self.user_member2, + self.user_member_other_group, + self.user, + self.user_invited, + ] + perm = "usersec.create_hpcuserdeleterequest" + self.assert_permissions_granted(perm, self.hpc_member, good_users) + self.assert_permissions_denied(perm, self.hpc_member, bad_users) + + def test_manage_hpcuserdeleterequest(self): + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_pending, + self.user_hpcadmin, + self.user_member, + self.user_member2, + self.user_member_other_group, + self.user, + self.user_invited, + ] + perm = "usersec.manage_hpcuserdeleterequest" + self.assert_permissions_granted(perm, self.hpc_user_delete_request, good_users) + self.assert_permissions_denied(perm, self.hpc_user_delete_request, bad_users) + class TestPermissionsInViews(TestRulesBase): """Tests for permissions in views.""" @@ -2731,6 +2794,401 @@ def test_hpc_user_change_request_archive_view_view_mode(self): ) self._test_view_mode_denied(url) + def test_hpc_user_delete_request_create_view_get(self): + url = reverse( + "usersec:hpcuserdeleterequest-create", + kwargs={"hpcuser": self.hpc_member.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + + self.assert_permissions_on_url(good_users, url, "GET", 200) + self.assert_permissions_on_url( + bad_users, url, "GET", 302, redirect_url=reverse("home"), not_authorized=True + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_create_view_get_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-create", + kwargs={"hpcuser": self.hpc_member.uuid}, + ) + self._test_view_mode_denied(url) + + def test_hpc_user_delete_request_create_view_post(self): + url = reverse( + "usersec:hpcuserdeleterequest-create", + kwargs={"hpcuser": self.hpc_member.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + data = {} + + self.assert_permissions_on_url( + good_users, + url, + "POST", + 302, + req_kwargs=data, + redirect_url=reverse("home"), + ) + self.assert_permissions_on_url( + bad_users, + url, + "POST", + 302, + req_kwargs=data, + redirect_url=reverse("home"), + not_authorized=True, + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_create_view_post_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-create", + kwargs={"hpcuser": self.hpc_member.uuid}, + ) + self._test_view_mode_denied(url, "POST") + + def test_hpc_user_delete_request_detail_view(self): + url = reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + + self.assert_permissions_on_url(good_users, url, "GET", 200) + self.assert_permissions_on_url( + bad_users, url, "GET", 302, redirect_url=reverse("home"), not_authorized=True + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_detail_view_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + self._test_view_mode_denied(url) + + def test_hpc_user_delete_request_update_view_get(self): + url = reverse( + "usersec:hpcuserdeleterequest-update", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + + self.assert_permissions_on_url(good_users, url, "GET", 200) + self.assert_permissions_on_url( + bad_users, url, "GET", 302, redirect_url=reverse("home"), not_authorized=True + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_update_view_get_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-update", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + self._test_view_mode_denied(url) + + def test_hpc_user_delete_request_update_view_post(self): + url = reverse( + "usersec:hpcuserdeleterequest-update", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + data = {} + + self.assert_permissions_on_url( + good_users, + url, + "POST", + 302, + req_kwargs=data, + redirect_url=reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ), + ) + self.assert_permissions_on_url( + bad_users, + url, + "POST", + 302, + req_kwargs=data, + redirect_url=reverse("home"), + not_authorized=True, + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_update_view_post_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-update", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + self._test_view_mode_denied(url, "POST") + + def test_hpc_user_delete_request_retract_view(self): + url = reverse( + "usersec:hpcuserdeleterequest-retract", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + + self.assert_permissions_on_url( + good_users, + url, + "GET", + 302, + redirect_url=reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ), + ) + self.assert_permissions_on_url( + bad_users, + url, + "GET", + 302, + redirect_url=reverse("home"), + not_authorized=True, + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_retract_view_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-retract", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + self._test_view_mode_denied(url) + + def test_hpc_user_delete_request_reactivate_view(self): + url = reverse( + "usersec:hpcuserdeleterequest-reactivate", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + + self.assert_permissions_on_url(good_users, url, "GET", 302, redirect_url=reverse("home")) + self.assert_permissions_on_url( + bad_users, url, "GET", 302, redirect_url=reverse("home"), not_authorized=True + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_reactivate_view_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-reactivate", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + self._test_view_mode_denied(url) + + def test_hpc_user_delete_request_delete_view_get(self): + url = reverse( + "usersec:hpcuserdeleterequest-delete", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + + self.assert_permissions_on_url( + good_users, + url, + "GET", + 200, + ) + self.assert_permissions_on_url( + bad_users, + url, + "GET", + 302, + redirect_url=reverse("home"), + not_authorized=True, + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_delete_view_get_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-delete", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + self._test_view_mode_denied(url) + + def test_hpc_user_delete_request_delete_view_post(self): + url = reverse( + "usersec:hpcuserdeleterequest-delete", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + + def rollback_callback(): + self.hpc_user_delete_request.save() + + self.assert_permissions_on_url( + good_users, + url, + "POST", + 302, + redirect_url=reverse("home"), + rollback_callback=rollback_callback, + ) + self.assert_permissions_on_url( + bad_users, + url, + "POST", + 302, + redirect_url=reverse("home"), + not_authorized=True, + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_delete_view_post_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-delete", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + self._test_view_mode_denied(url, "POST") + + def test_hpc_user_delete_request_archive_view(self): + url = reverse( + "usersec:hpcuserdeleterequest-archive", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + good_users = [ + self.superuser, + self.user_owner, + self.user_delegate, + ] + bad_users = [ + self.user_member, + self.user_member2, + self.user_pending, + self.user_hpcadmin, + self.user_member_other_group, + self.user, + ] + + self.assert_permissions_on_url(good_users, url, "GET", 302, redirect_url=reverse("home")) + self.assert_permissions_on_url( + bad_users, + url, + "GET", + 302, + redirect_url=reverse("home"), + not_authorized=True, + ) + + @override_settings(VIEW_MODE=True) + def test_hpc_user_delete_request_archive_view_view_mode(self): + url = reverse( + "usersec:hpcuserdeleterequest-archive", + kwargs={"hpcuserdeleterequest": self.hpc_user_delete_request.uuid}, + ) + self._test_view_mode_denied(url) + def test_hpc_project_create_request_create_view_get(self): url = reverse( "usersec:hpcprojectcreaterequest-create", diff --git a/usersec/tests/test_views.py b/usersec/tests/test_views.py index bef2d05..7e0e5ac 100644 --- a/usersec/tests/test_views.py +++ b/usersec/tests/test_views.py @@ -26,6 +26,7 @@ HpcUser, HpcUserChangeRequest, HpcUserCreateRequest, + HpcUserDeleteRequest, ) from usersec.tests.factories import ( HPCGROUPCREATEREQUEST_FORM_DATA_VALID, @@ -43,6 +44,7 @@ HpcProjectInvitationFactory, HpcUserChangeRequestFactory, HpcUserCreateRequestFactory, + HpcUserDeleteRequestFactory, HpcUserFactory, TermsAndConditionsFactory, ) @@ -1605,6 +1607,277 @@ def test_get(self): self.assertEqual(self.obj.status, REQUEST_STATUS_ARCHIVED) +class TestHpcUserDeleteRequestCreateView(TestViewBase): + """Tests for HpcUserDeleteRequestCreateView.""" + + def test_get(self): + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-create", kwargs={"hpcuser": self.hpc_member.uuid} + ) + ) + self.assertEqual(response.status_code, 200) + + def test_post_form_valid(self): + with self.login(self.user_owner): + data = {} + response = self.client.post( + reverse( + "usersec:hpcuserdeleterequest-create", kwargs={"hpcuser": self.hpc_member.uuid} + ), + data=data, + ) + self.assertEqual(HpcUserDeleteRequest.objects.count(), 1) + self.assertRedirects(response, reverse("home")) + self.assertNoMessages(response) + self.assertEqual(len(mail.outbox), 1) + + +class TestHpcUserDeleteRequestDetailView(TestViewBase): + """Tests for HpcUserDeleteRequestDetailView.""" + + def test_get(self): + request = HpcUserDeleteRequestFactory( + requester=self.user_owner, status=REQUEST_STATUS_ACTIVE, user=self.hpc_member + ) + + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": request.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertFalse(response.context["is_decided"]) + self.assertFalse(response.context["is_denied"]) + self.assertFalse(response.context["is_retracted"]) + self.assertFalse(response.context["is_approved"]) + self.assertTrue(response.context["is_active"]) + self.assertFalse(response.context["is_revision"]) + + def test_get_retracted(self): + request = HpcUserDeleteRequestFactory( + requester=self.user_owner, status=REQUEST_STATUS_RETRACTED, user=self.hpc_member + ) + + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": request.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertFalse(response.context["is_decided"]) + self.assertFalse(response.context["is_denied"]) + self.assertTrue(response.context["is_retracted"]) + self.assertFalse(response.context["is_approved"]) + self.assertFalse(response.context["is_active"]) + self.assertFalse(response.context["is_revision"]) + + def test_get_denied(self): + request = HpcUserDeleteRequestFactory( + requester=self.user_owner, status=REQUEST_STATUS_DENIED, user=self.hpc_member + ) + + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": request.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertTrue(response.context["is_decided"]) + self.assertTrue(response.context["is_denied"]) + self.assertFalse(response.context["is_retracted"]) + self.assertFalse(response.context["is_approved"]) + self.assertFalse(response.context["is_active"]) + self.assertFalse(response.context["is_revision"]) + + def test_get_approved(self): + request = HpcUserDeleteRequestFactory( + requester=self.user_owner, status=REQUEST_STATUS_APPROVED, user=self.hpc_member + ) + + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": request.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertTrue(response.context["is_decided"]) + self.assertFalse(response.context["is_denied"]) + self.assertFalse(response.context["is_retracted"]) + self.assertTrue(response.context["is_approved"]) + self.assertFalse(response.context["is_active"]) + self.assertFalse(response.context["is_revision"]) + + +class TestHpcUserDeleteRequestUpdateView(TestViewBase): + """Tests for HpcUserDeleteRequestUpdateView.""" + + def setUp(self): + super().setUp() + self.obj = HpcUserDeleteRequestFactory(requester=self.user_owner, user=self.hpc_member) + + def test_get(self): + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-update", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ) + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["form"]["comment"].value(), "") + self.assertTrue(response.context["update"]) + + def test_post(self): + update = { + "comment": "I made a comment!", + } + + with self.login(self.user_owner): + response = self.client.post( + reverse( + "usersec:hpcuserdeleterequest-update", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ), + update, + ) + self.assertRedirects( + response, + reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ), + ) + + self.obj.refresh_from_db() + + self.assertEqual(self.obj.comment, update["comment"]) + self.assertEqual(self.obj.editor, self.user_owner) + self.assertEqual(self.obj.requester, self.user_owner) + + self.assertEqual(len(mail.outbox), 0) + + +class TestHpcUserDeleteRequestRetractView(TestViewBase): + """Tests for HpcUserDeleteRequestRetractView.""" + + def setUp(self): + super().setUp() + self.obj = HpcUserDeleteRequestFactory(requester=self.user_owner, user=self.hpc_member) + + def test_get(self): + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-retract", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ) + ) + self.assertRedirects( + response, + reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ), + ) + + self.assertEqual(self.obj.status, REQUEST_STATUS_INITIAL) + self.obj.refresh_from_db() + self.assertEqual(self.obj.status, REQUEST_STATUS_RETRACTED) + + self.assertEqual(len(mail.outbox), 0) + + +class TestHpcUserDeleteRequestReactivateView(TestViewBase): + """Tests for HpcUserDeleteRequestReactivateView.""" + + def setUp(self): + super().setUp() + self.obj = HpcUserDeleteRequestFactory( + requester=self.user_owner, user=self.hpc_member, status=REQUEST_STATUS_RETRACTED + ) + + def test_get(self): + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-reactivate", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ) + ) + + self.assertRedirects(response, reverse("home")) + self.assertNoMessages(response) + self.assertEqual(self.obj.status, REQUEST_STATUS_RETRACTED) + self.obj.refresh_from_db() + self.assertEqual(self.obj.status, REQUEST_STATUS_ACTIVE) + + +class TestHpcUserDeleteRequestDeleteView(TestViewBase): + """Tests for HpcUserDeleteRequestDeleteView.""" + + def setUp(self): + super().setUp() + self.obj = HpcUserDeleteRequestFactory(requester=self.user_owner, user=self.hpc_member) + + def test_get(self): + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-delete", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ) + ) + self.assertEqual(response.status_code, 200) + + def test_post(self): + with self.login(self.user_owner): + response = self.client.post( + reverse( + "usersec:hpcuserdeleterequest-delete", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ) + ) + self.assertRedirects(response, reverse("home")) + self.assertNoMessages(response) + self.assertEqual(HpcUserDeleteRequest.objects.count(), 0) + + +class TestHpcUserDeleteRequestArchiveView(TestViewBase): + """Tests for HpcUserDeleteRequestArchiveView.""" + + def setUp(self): + super().setUp() + self.obj = HpcUserDeleteRequestFactory(requester=self.user_owner, user=self.hpc_member) + + def test_get(self): + with self.login(self.user_owner): + response = self.client.get( + reverse( + "usersec:hpcuserdeleterequest-archive", + kwargs={"hpcuserdeleterequest": self.obj.uuid}, + ) + ) + self.assertRedirects(response, reverse("home")) + self.assertNoMessages(response) + self.obj.refresh_from_db() + self.assertEqual(self.obj.status, REQUEST_STATUS_ARCHIVED) + + class TestHpcProjectCreateRequestCreateView(TestViewBase): """Tests for HpcProjectCreateRequestCreateView.""" diff --git a/usersec/urls.py b/usersec/urls.py index 3326ead..8aa4b6a 100644 --- a/usersec/urls.py +++ b/usersec/urls.py @@ -224,7 +224,7 @@ # HpcUserDeleteRequest related # ------------------------------------------------------------------------------ path( - "hpcgroup//hpcuserdeleterequest/", + "hpcuser//hpcuserdeleterequest/", view=views.HpcUserDeleteRequestCreateView.as_view(), name="hpcuserdeleterequest-create", ), @@ -346,7 +346,7 @@ # HpcProjectDeleteRequest related # ------------------------------------------------------------------------------ path( - "hpcgroup//hpcprojectdeleterequest/", + "hpcproject//hpcprojectdeleterequest/", view=views.HpcProjectDeleteRequestCreateView.as_view(), name="hpcprojectdeleterequest-create", ), diff --git a/usersec/views.py b/usersec/views.py index 9184f06..2e848e2 100644 --- a/usersec/views.py +++ b/usersec/views.py @@ -36,6 +36,7 @@ HpcProjectCreateRequestForm, HpcUserChangeRequestForm, HpcUserCreateRequestForm, + HpcUserDeleteRequestForm, ProjectSelectForm, UserSelectForm, ) @@ -63,6 +64,7 @@ HpcUser, HpcUserChangeRequest, HpcUserCreateRequest, + HpcUserDeleteRequest, TermsAndConditions, ) @@ -395,9 +397,10 @@ def get_requests_by_status(status): return list( chain( HpcUserCreateRequest.objects.filter(group=group, status=status), - HpcProjectCreateRequest.objects.filter(group=group, status=status), HpcUserChangeRequest.objects.filter(user__primary_group=group, status=status), + HpcUserDeleteRequest.objects.filter(user__primary_group=group, status=status), HpcGroupChangeRequest.objects.filter(group=group, status=status), + HpcProjectCreateRequest.objects.filter(group=group, status=status), HpcProjectChangeRequest.objects.filter( Q(project__group=group) | Q(project__delegate=context["object"]), status=status, @@ -926,32 +929,200 @@ def get(self, request, *args, **kwargs): return HttpResponseRedirect(reverse("home")) -class HpcUserDeleteRequestCreateView(View): - pass +class HpcUserDeleteRequestCreateView(HpcPermissionMixin, CreateView): + """HPC user delete request create view.""" + """HPC project create request create view. -class HpcUserDeleteRequestDetailView(View): - pass + Using HpcProject object for permission checking, + it is not the object to be created. + """ + # Required for permission checks, usually the CreateView doesn't have the current object + # available + model = HpcUser + template_name = "usersec/hpcuserdeleterequest_form.html" + slug_field = "uuid" + slug_url_kwarg = "hpcuser" + # Check permission based on HpcProject object + permission_required = "usersec.create_hpcuserdeleterequest" + # Pass the form to the actual object we want to create + form_class = HpcUserDeleteRequestForm -class HpcUserDeleteRequestUpdateView(View): - pass + def get_permission_object(self): + """Override to return the HpcUser object. + Parent returns None in case of CreateView. + """ + return self.get_object() -class HpcUserDeleteRequestRetractView(View): - pass + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs.update({"user": self.request.user}) + return kwargs + def form_valid(self, form): + obj = form.save(commit=False) + obj.requester = self.request.user + obj.editor = self.request.user + obj.status = REQUEST_STATUS_ACTIVE + obj.user = self.get_object() + obj = obj.save_with_version() -class HpcUserDeleteRequestReactivateView(View): - pass + if not obj: + messages.error( + self.request, MSG_REQUEST_FAILURE.format(MSG_PART_SUBMIT, MSG_PART_PROJECT_UPDATE) + ) + return HttpResponseRedirect( + reverse("usersec:hpcuserdeleterequest-create", kwargs={"hpcuser": obj.uuid}) + ) + + if settings.SEND_EMAIL: + send_notification_admin_request(obj) + return HttpResponseRedirect(reverse("home")) -class HpcUserDeleteRequestDeleteView(View): - pass +class HpcUserDeleteRequestDetailView(HpcPermissionMixin, DetailView): + """HPC user delete request detail view.""" -class HpcUserDeleteRequestArchiveView(View): - pass + model = HpcUserDeleteRequest + template_name = "usersec/hpcuserdeleterequest_detail.html" + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "usersec.view_hpcuserdeleterequest" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + obj = self.get_object() + context["is_decided"] = obj.is_decided() + context["is_denied"] = obj.is_denied() + context["is_retracted"] = obj.is_retracted() + context["is_approved"] = obj.is_approved() + context["is_active"] = obj.is_active() + context["is_revision"] = obj.is_revision() + context["is_archived"] = obj.is_archived() + return context + + +class HpcUserDeleteRequestUpdateView(HpcPermissionMixin, UpdateView): + """HPC user delete request update view.""" + + template_name = "usersec/hpcuserdeleterequest_form.html" + form_class = HpcUserDeleteRequestForm + model = HpcUserDeleteRequest + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "usersec.manage_hpcuserdeleterequest" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["update"] = True + return context + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs.update({"user": self.request.user}) + return kwargs + + def get_success_url(self): + return reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": self.get_object().uuid}, + ) + + def get_initial(self): + initial = super().get_initial() + initial["comment"] = "" + return initial + + def form_valid(self, form): + obj = form.save(commit=False) + obj.editor = self.request.user + if obj.status == REQUEST_STATUS_REVISION: + obj.status = REQUEST_STATUS_ACTIVE + obj = obj.save_with_version() + + if not obj: + messages.error( + self.request, MSG_REQUEST_FAILURE.format(MSG_PART_UPDATE, MSG_PART_USER_UPDATE) + ) + return HttpResponseRedirect(reverse("home")) + + return HttpResponseRedirect(self.get_success_url()) + + +class HpcUserDeleteRequestRetractView(HpcPermissionMixin, SingleObjectMixin, View): + """HPC user delete request retract view.""" + + model = HpcUserDeleteRequest + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "usersec.manage_hpcuserdeleterequest" + + def get(self, request, *args, **kwargs): + obj = self.get_object() + obj.editor = self.request.user + obj.comment = "" + obj.retract_with_version() + + return HttpResponseRedirect( + reverse( + "usersec:hpcuserdeleterequest-detail", + kwargs={"hpcuserdeleterequest": obj.uuid}, + ) + ) + + +class HpcUserDeleteRequestReactivateView(HpcPermissionMixin, SingleObjectMixin, View): + """HPC user delete request reactivate view.""" + + model = HpcUserDeleteRequest + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "usersec.manage_hpcuserdeleterequest" + + def get(self, request, *args, **kwargs): + obj = self.get_object() + obj.status = REQUEST_STATUS_ACTIVE + obj.editor = self.request.user + obj.comment = "" + obj.save_with_version() + + if settings.SEND_EMAIL: + send_notification_admin_request(obj) + + return HttpResponseRedirect(reverse("home")) + + +class HpcUserDeleteRequestDeleteView(HpcPermissionMixin, DeleteView): + """HPC user delete request delete view.""" + + template_name_suffix = "_delete_confirm" + model = HpcUserDeleteRequest + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "usersec.manage_hpcuserdeleterequest" + + def get_success_url(self): + return reverse("home") + + +class HpcUserDeleteRequestArchiveView(HpcPermissionMixin, SingleObjectMixin, View): + """HPC user delete request archive view.""" + + model = HpcUserDeleteRequest + slug_field = "uuid" + slug_url_kwarg = "hpcuserdeleterequest" + permission_required = "usersec.manage_hpcuserdeleterequest" + + def get(self, request, *args, **kwargs): + obj = self.get_object() + obj.status = REQUEST_STATUS_ARCHIVED + obj.editor = self.request.user + obj.save_with_version() + + return HttpResponseRedirect(reverse("home")) class HpcUserChangeRequestCreateView(HpcPermissionMixin, CreateView): diff --git a/utils/cli/hpc_access_cli/states.py b/utils/cli/hpc_access_cli/states.py index 2fcbc73..fc96a4c 100644 --- a/utils/cli/hpc_access_cli/states.py +++ b/utils/cli/hpc_access_cli/states.py @@ -497,11 +497,18 @@ def build_hpcuser(u: LdapUser, quotas: Dict[str, str]) -> HpcUser: status = Status.EXPIRED expiration = datetime.datetime.now() if u.gid_number and u.gid_number in group_by_gid_number: - primary_group = group_uuids.get(group_by_gid_number[u.gid_number].cn) + _primary_group = group_by_gid_number[u.gid_number].cn + primary_group = group_uuids.get(_primary_group) + if ( + _primary_group + and not _primary_group.startswith(POSIX_AG_PREFIX) + and not _primary_group == "hpc-alumnis" + ): + console_err.log( + f"User belongs to group that is not a group ({_primary_group}, {u.uid})" + ) else: primary_group = None - if not primary_group.startswith(POSIX_AG_PREFIX) and not primary_group == "hpc-alumnis": - console_err.log(f"User belongs to group that is not a group ({primary_group}, {u.uid})") return HpcUser( uuid=user_uuids[u.uid], primary_group=primary_group,