diff --git a/.secrets.baseline b/.secrets.baseline index 9bb34b58..9c221915 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -194,7 +194,7 @@ "hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207", "is_secret": false, "is_verified": false, - "line_number": 525, + "line_number": 543, "type": "Hex High Entropy String" } ] diff --git a/atst/domain/application_roles.py b/atst/domain/application_roles.py index 2478d351..76e81089 100644 --- a/atst/domain/application_roles.py +++ b/atst/domain/application_roles.py @@ -70,3 +70,16 @@ class ApplicationRoles(object): db.session.commit() return application_role + + @classmethod + def _update_status(cls, application_role, new_status): + application_role.status = new_status + db.session.add(application_role) + db.session.commit() + + return application_role + + @classmethod + def disable(cls, application_role): + application_role.deleted = True + return cls._update_status(application_role, ApplicationRoleStatus.DISABLED) diff --git a/atst/domain/invitations.py b/atst/domain/invitations.py index 6b68db92..16f50c87 100644 --- a/atst/domain/invitations.py +++ b/atst/domain/invitations.py @@ -143,3 +143,10 @@ class PortfolioInvitations(BaseInvitations): class ApplicationInvitations(BaseInvitations): model = ApplicationInvitation role_domain_class = ApplicationRoles + + @classmethod + def _update_status(cls, invite, new_status): + invite = super()._update_status(invite, new_status) + ApplicationRoles.disable(invite.role) + + return invite diff --git a/atst/routes/applications/settings.py b/atst/routes/applications/settings.py index 8bc002bc..33dc668e 100644 --- a/atst/routes/applications/settings.py +++ b/atst/routes/applications/settings.py @@ -8,6 +8,7 @@ from atst.domain.application_roles import ApplicationRoles from atst.domain.audit_log import AuditLog from atst.domain.common import Paginator from atst.domain.environment_roles import EnvironmentRoles +from atst.domain.invitations import ApplicationInvitations from atst.forms.application_member import NewForm as NewMemberForm, UpdateMemberForm from atst.forms.application import NameAndDescriptionForm, EditEnvironmentForm from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS @@ -379,3 +380,25 @@ def update_member(application_id, application_role_id): _anchor="application-members", ) ) + + +@applications_bp.route( + "/applications//members//revoke_invite", + methods=["POST"], +) +@user_can(Permissions.DELETE_APPLICATION_MEMBER, message="revoke appliction invitation") +def revoke_invite(application_id, application_role_id): + app_role = ApplicationRoles.get_by_id(application_role_id) + invite = app_role.latest_invitation + + if invite.is_revokable: + ApplicationInvitations.revoke(invite.token) + + return redirect( + url_for( + "applications.settings", + application_id=application_id, + fragment="application-members", + _anchor="application-members", + ) + ) diff --git a/tests/factories.py b/tests/factories.py index daf686e2..5fe45f55 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -259,6 +259,7 @@ class ApplicationInvitationFactory(Base): email = factory.Faker("email") status = InvitationStatus.PENDING expiration_time = PortfolioInvitations.current_expiration_time() + role = factory.SubFactory(ApplicationRoleFactory) class AttachmentFactory(Base): diff --git a/tests/routes/applications/test_settings.py b/tests/routes/applications/test_settings.py index 3d69404c..811bee36 100644 --- a/tests/routes/applications/test_settings.py +++ b/tests/routes/applications/test_settings.py @@ -14,6 +14,7 @@ from atst.domain.common import Paginator from atst.domain.permission_sets import PermissionSets from atst.domain.portfolios import Portfolios from atst.domain.exceptions import NotFoundError +from atst.models.application_role import Status as ApplicationRoleStatus from atst.models.environment_role import CSPRole from atst.models.permissions import Permissions from atst.models.portfolio_role import Status as PortfolioRoleStatus @@ -540,3 +541,21 @@ def test_update_member(client, user_session): # check that the user has roles in the correct envs assert environment_roles[0].environment in [env, env_2] assert environment_roles[1].environment in [env, env_2] + + +def test_revoke_invite(client, user_session): + invite = ApplicationInvitationFactory.create() + app_role = invite.role + application = app_role.application + + user_session(application.portfolio.owner) + response = client.post( + url_for( + "applications.revoke_invite", + application_id=application.id, + application_role_id=app_role.id, + ) + ) + + assert invite.is_revoked + assert app_role.status == ApplicationRoleStatus.DISABLED diff --git a/tests/test_access.py b/tests/test_access.py index f8ef43a2..e864ff4f 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -572,6 +572,24 @@ def test_applications_update_member(post_url_assert_status): post_url_assert_status(rando, url, 404) +# applications.revoke_invite +def test_applications_revoke_invite(post_url_assert_status): + ccpo = UserFactory.create_ccpo() + rando = UserFactory.create() + application = ApplicationFactory.create() + + for user, status in [(ccpo, 302), (application.portfolio.owner, 302), (rando, 404)]: + app_role = ApplicationRoleFactory.create() + invite = ApplicationInvitationFactory.create(role=app_role) + + url = url_for( + "applications.revoke_invite", + application_id=application.id, + application_role_id=app_role.id, + ) + post_url_assert_status(user, url, status) + + # task_orders.download_task_order_pdf def test_task_orders_download_task_order_pdf_access(get_url_assert_status, monkeypatch): monkeypatch.setattr(