diff --git a/atst/domain/application_roles.py b/atst/domain/application_roles.py index f1aa36f7..af000706 100644 --- a/atst/domain/application_roles.py +++ b/atst/domain/application_roles.py @@ -1,5 +1,5 @@ from atst.database import db -from atst.models.application_role import ApplicationRole +from atst.models import ApplicationRole, ApplicationRoleStatus from atst.domain.permission_sets import PermissionSets @@ -21,3 +21,10 @@ class ApplicationRoles(object): db.session.commit() return application_role + + @classmethod + def enable(cls, role): + role.status = ApplicationRoleStatus.ACTIVE + + db.session.add(role) + db.session.commit() diff --git a/atst/models/application_role.py b/atst/models/application_role.py index fd606216..fe56a37d 100644 --- a/atst/models/application_role.py +++ b/atst/models/application_role.py @@ -62,7 +62,13 @@ class ApplicationRole( @property def history(self): - return self.get_changes() + previous_state = self.get_changes() + change_set = {} + if "status" in previous_state: + from_status = previous_state["status"][0].value + to_status = self.status.value + change_set["status"] = [from_status, to_status] + return change_set def has_permission_set(self, perm_set_name): return first_or_none( diff --git a/atst/routes/applications/__init__.py b/atst/routes/applications/__init__.py index 60f7f495..aa2e2bb6 100644 --- a/atst/routes/applications/__init__.py +++ b/atst/routes/applications/__init__.py @@ -6,6 +6,7 @@ from . import index from . import new from . import settings from . import team +from . import invitations from atst.domain.environment_roles import EnvironmentRoles from atst.domain.exceptions import UnauthorizedError from atst.domain.authz.decorator import user_can_access_decorator as user_can diff --git a/atst/routes/applications/invitations.py b/atst/routes/applications/invitations.py new file mode 100644 index 00000000..a3893da8 --- /dev/null +++ b/atst/routes/applications/invitations.py @@ -0,0 +1,15 @@ +from flask import redirect, url_for, g + +from . import applications_bp +from atst.domain.invitations import ApplicationInvitations + + +@applications_bp.route("/applications/invitations/", methods=["GET"]) +def accept_invitation(token): + invite = ApplicationInvitations.accept(g.current_user, token) + + return redirect( + url_for( + "portfolios.show_portfolio", portfolio_id=invite.application.portfolio_id + ) + ) diff --git a/tests/domain/test_application_roles.py b/tests/domain/test_application_roles.py index bde4a1d5..f430c8cb 100644 --- a/tests/domain/test_application_roles.py +++ b/tests/domain/test_application_roles.py @@ -1,6 +1,8 @@ from atst.domain.application_roles import ApplicationRoles from atst.domain.permission_sets import PermissionSets -from tests.factories import UserFactory, ApplicationFactory +from atst.models import ApplicationRoleStatus + +from tests.factories import * def test_create_application_role(): @@ -18,3 +20,16 @@ def test_create_application_role(): ) assert application_role.application == application assert application_role.user == user + + +def test_enabled_application_role(): + application = ApplicationFactory.create() + user = UserFactory.create() + app_role = ApplicationRoleFactory.create( + application=application, user=user, status=ApplicationRoleStatus.DISABLED + ) + assert app_role.status == ApplicationRoleStatus.DISABLED + + ApplicationRoles.enable(app_role) + + assert app_role.status == ApplicationRoleStatus.ACTIVE diff --git a/tests/routes/applications/test_invitations.py b/tests/routes/applications/test_invitations.py new file mode 100644 index 00000000..718485dd --- /dev/null +++ b/tests/routes/applications/test_invitations.py @@ -0,0 +1,23 @@ +from flask import url_for + +from tests.factories import * + + +def test_accept_application_invitation(client, user_session): + user = UserFactory.create() + application = ApplicationFactory.create() + app_role = ApplicationRoleFactory.create(application=application, user=user) + invite = ApplicationInvitationFactory.create( + role=app_role, user=user, inviter=application.portfolio.owner + ) + + user_session(user) + response = client.get(url_for("applications.accept_invitation", token=invite.token)) + + assert response.status_code == 302 + expected_location = url_for( + "portfolios.show_portfolio", + portfolio_id=application.portfolio_id, + _external=True, + ) + assert response.location == expected_location diff --git a/tests/test_access.py b/tests/test_access.py index ded929e3..2767ef28 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -35,6 +35,7 @@ _NO_ACCESS_CHECK_REQUIRED = _NO_LOGIN_REQUIRED + [ "users.user", # available to all users "users.update_user", # available to all users "portfolios.accept_invitation", # available to all users; access control is built into invitation logic + "applications.accept_invitation", # available to all users; access control is built into invitation logic "atst.catch_all", # available to all users "portfolios.portfolios", # the portfolios list is scoped to the user separately ]