diff --git a/atst/domain/authz/decorator.py b/atst/domain/authz/decorator.py index b22c8054..4cc61aa6 100644 --- a/atst/domain/authz/decorator.py +++ b/atst/domain/authz/decorator.py @@ -3,11 +3,6 @@ from functools import wraps from flask import g, current_app as app, request from . import user_can_access -from atst.domain.portfolios import Portfolios -from atst.domain.task_orders import TaskOrders -from atst.domain.applications import Applications -from atst.domain.environments import Environments -from atst.domain.invitations import PortfolioInvitations from atst.domain.exceptions import UnauthorizedError @@ -18,14 +13,6 @@ def check_access(permission, message, override, *args, **kwargs): "application": g.application, } - # TODO: We should change the `token` arg in routes to be either - # `portfolio_token` or `application_token` and have - # atst.utils.context_processors.assign_resources take care of - # this. - if "token" in kwargs: - invite = PortfolioInvitations._get(kwargs["token"]) - access_args["portfolio"] = invite.role.portfolio - if override is not None and override(g.current_user, **access_args, **kwargs): return True diff --git a/atst/routes/portfolios/invitations.py b/atst/routes/portfolios/invitations.py index 0e5901fe..4c3a53a8 100644 --- a/atst/routes/portfolios/invitations.py +++ b/atst/routes/portfolios/invitations.py @@ -19,9 +19,9 @@ def send_invite_email(owner_name, token, new_member_email): ) -@portfolios_bp.route("/portfolios/invitations/", methods=["GET"]) -def accept_invitation(token): - invite = PortfolioInvitations.accept(g.current_user, token) +@portfolios_bp.route("/portfolios/invitations/", methods=["GET"]) +def accept_invitation(portfolio_token): + invite = PortfolioInvitations.accept(g.current_user, portfolio_token) for task_order in invite.portfolio.task_orders: if g.current_user in task_order.officers: @@ -35,11 +35,11 @@ def accept_invitation(token): @portfolios_bp.route( - "/portfolios//invitations//revoke", methods=["POST"] + "/portfolios//invitations//revoke", methods=["POST"] ) @user_can(Permissions.EDIT_PORTFOLIO_USERS, message="revoke invitation") -def revoke_invitation(portfolio_id, token): - PortfolioInvitations.revoke(token) +def revoke_invitation(portfolio_id, portfolio_token): + PortfolioInvitations.revoke(portfolio_token) return redirect( url_for( @@ -52,11 +52,11 @@ def revoke_invitation(portfolio_id, token): @portfolios_bp.route( - "/portfolios//invitations//resend", methods=["POST"] + "/portfolios//invitations//resend", methods=["POST"] ) @user_can(Permissions.EDIT_PORTFOLIO_USERS, message="resend invitation") -def resend_invitation(portfolio_id, token): - invite = PortfolioInvitations.resend(g.current_user, token) +def resend_invitation(portfolio_id, portfolio_token): + invite = PortfolioInvitations.resend(g.current_user, portfolio_token) send_invite_email(g.current_user.full_name, invite.token, invite.email) flash("resend_portfolio_invitation", user_name=invite.user_name) return redirect( diff --git a/atst/utils/context_processors.py b/atst/utils/context_processors.py index bf6e9a3e..946e62a8 100644 --- a/atst/utils/context_processors.py +++ b/atst/utils/context_processors.py @@ -5,7 +5,14 @@ from sqlalchemy.orm.exc import NoResultFound from atst.database import db from atst.domain.authz import Authorization -from atst.models import Application, Environment, Portfolio, TaskOrder +from atst.models import ( + Application, + Environment, + Portfolio, + PortfolioInvitation, + PortfolioRole, + TaskOrder, +) from atst.models.permissions import Permissions from atst.domain.portfolios.scopes import ScopedPortfolio @@ -13,7 +20,18 @@ from atst.domain.portfolios.scopes import ScopedPortfolio def get_resources_from_context(view_args): query = None - if "portfolio_id" in view_args: + if "portfolio_token" in view_args: + query = ( + db.session.query(Portfolio) + .join(PortfolioRole, PortfolioRole.portfolio_id == Portfolio.id) + .join( + PortfolioInvitation, + PortfolioInvitation.portfolio_role_id == PortfolioRole.id, + ) + .filter(PortfolioInvitation.token == view_args["portfolio_token"]) + ) + + elif "portfolio_id" in view_args: query = db.session.query(Portfolio).filter( Portfolio.id == view_args["portfolio_id"] ) diff --git a/templates/emails/portfolio/invitation.txt b/templates/emails/portfolio/invitation.txt index dd0f12df..d68d8360 100644 --- a/templates/emails/portfolio/invitation.txt +++ b/templates/emails/portfolio/invitation.txt @@ -5,6 +5,6 @@ Join this JEDI Cloud Portfolio {{ owner }} has invited you to join a JEDI Cloud Portfolio. Login now to view or use your JEDI Cloud resources. -{{ url_for("portfolios.accept_invitation", token=token, _external=True) }} +{{ url_for("portfolios.accept_invitation", portfolio_token=token, _external=True) }} {% endblock %} diff --git a/tests/routes/portfolios/test_invitations.py b/tests/routes/portfolios/test_invitations.py index 1f07a3e2..5134957b 100644 --- a/tests/routes/portfolios/test_invitations.py +++ b/tests/routes/portfolios/test_invitations.py @@ -27,7 +27,9 @@ def test_existing_member_accepts_valid_invite(client, user_session): assert len(Portfolios.for_user(user)) == 0 user_session(user) - response = client.get(url_for("portfolios.accept_invitation", token=invite.token)) + response = client.get( + url_for("portfolios.accept_invitation", portfolio_token=invite.token) + ) # user is redirected to the portfolio view assert response.status_code == 302 @@ -68,7 +70,9 @@ def test_new_member_accepts_valid_invite(monkeypatch, client, user_session): "atst.domain.auth.should_redirect_to_user_profile", lambda *args: False ) user_session(user) - response = client.get(url_for("portfolios.accept_invitation", token=token)) + response = client.get( + url_for("portfolios.accept_invitation", portfolio_token=token) + ) # user is redirected to the portfolio view assert response.status_code == 302 @@ -90,7 +94,9 @@ def test_member_accepts_invalid_invite(client, user_session): user_id=user.id, role=ws_role, status=InvitationStatus.REJECTED_WRONG_USER ) user_session(user) - response = client.get(url_for("portfolios.accept_invitation", token=invite.token)) + response = client.get( + url_for("portfolios.accept_invitation", portfolio_token=invite.token) + ) assert response.status_code == 404 @@ -121,7 +127,9 @@ def test_user_accepts_invite_with_wrong_dod_id(client, user_session): ) invite = PortfolioInvitationFactory.create(user_id=user.id, role=ws_role) user_session(different_user) - response = client.get(url_for("portfolios.accept_invitation", token=invite.token)) + response = client.get( + url_for("portfolios.accept_invitation", portfolio_token=invite.token) + ) assert response.status_code == 404 @@ -139,7 +147,9 @@ def test_user_accepts_expired_invite(client, user_session): expiration_time=datetime.datetime.now() - datetime.timedelta(seconds=1), ) user_session(user) - response = client.get(url_for("portfolios.accept_invitation", token=invite.token)) + response = client.get( + url_for("portfolios.accept_invitation", portfolio_token=invite.token) + ) assert response.status_code == 404 @@ -161,7 +171,7 @@ def test_revoke_invitation(client, user_session): url_for( "portfolios.revoke_invitation", portfolio_id=portfolio.id, - token=invite.token, + portfolio_token=invite.token, ) ) @@ -187,7 +197,7 @@ def test_user_can_only_revoke_invites_in_their_portfolio(client, user_session): url_for( "portfolios.revoke_invitation", portfolio_id=portfolio.id, - token=invite.token, + portfolio_token=invite.token, ) ) @@ -213,7 +223,7 @@ def test_user_can_only_resend_invites_in_their_portfolio(client, user_session, q url_for( "portfolios.resend_invitation", portfolio_id=portfolio.id, - token=invite.token, + portfolio_token=invite.token, ) ) @@ -235,7 +245,7 @@ def test_resend_invitation_sends_email(client, user_session, queue): url_for( "portfolios.resend_invitation", portfolio_id=portfolio.id, - token=invite.token, + portfolio_token=invite.token, ) ) @@ -261,7 +271,7 @@ def test_existing_member_invite_resent_to_email_submitted_in_form( url_for( "portfolios.resend_invitation", portfolio_id=portfolio.id, - token=invite.token, + portfolio_token=invite.token, ) ) @@ -295,7 +305,9 @@ def test_contracting_officer_accepts_invite(monkeypatch, client, user_session): "atst.domain.auth.should_redirect_to_user_profile", lambda *args: False ) user_session(user) - response = client.get(url_for("portfolios.accept_invitation", token=token)) + response = client.get( + url_for("portfolios.accept_invitation", portfolio_token=token) + ) # user is redirected to the task order review page assert response.status_code == 302 @@ -329,7 +341,9 @@ def test_cor_accepts_invite(monkeypatch, client, user_session): "atst.domain.auth.should_redirect_to_user_profile", lambda *args: False ) user_session(user) - response = client.get(url_for("portfolios.accept_invitation", token=token)) + response = client.get( + url_for("portfolios.accept_invitation", portfolio_token=token) + ) # user is redirected to the task order review page assert response.status_code == 302 @@ -363,7 +377,9 @@ def test_so_accepts_invite(monkeypatch, client, user_session): "atst.domain.auth.should_redirect_to_user_profile", lambda *args: False ) user_session(user) - response = client.get(url_for("portfolios.accept_invitation", token=token)) + response = client.get( + url_for("portfolios.accept_invitation", portfolio_token=token) + ) # user is redirected to the task order review page assert response.status_code == 302 diff --git a/tests/test_access.py b/tests/test_access.py index 2a56fa8c..84f1d167 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -411,7 +411,9 @@ def test_portfolios_resend_invitation_access(post_url_assert_status): invite = PortfolioInvitationFactory.create(user=UserFactory.create(), role=prr) url = url_for( - "portfolios.resend_invitation", portfolio_id=portfolio.id, token=invite.token + "portfolios.resend_invitation", + portfolio_id=portfolio.id, + portfolio_token=invite.token, ) post_url_assert_status(ccpo, url, 302) post_url_assert_status(owner, url, 302) @@ -459,7 +461,7 @@ def test_portfolios_revoke_invitation_access(post_url_assert_status): url = url_for( "portfolios.revoke_invitation", portfolio_id=portfolio.id, - token=invite.token, + portfolio_token=invite.token, ) post_url_assert_status(user, url, status)