diff --git a/atst/routes/portfolios/applications.py b/atst/routes/portfolios/applications.py index c4665078..adee204c 100644 --- a/atst/routes/portfolios/applications.py +++ b/atst/routes/portfolios/applications.py @@ -96,15 +96,20 @@ def update_application(portfolio_id, application_id): ) +def wrap_environment_role_lookup( + user, _perm, portfolio_id=None, environment_id=None, **kwargs +): + env_role = EnvironmentRoles.get(user.id, environment_id) + if not env_role: + raise UnauthorizedError(user, "access environment {}".format(environment_id)) + + return True + + @portfolios_bp.route("/portfolios//environments//access") -# TODO: we probably need a different permission for this -@user_can(Permissions.VIEW_ENVIRONMENT) +@user_can(None, exceptions=[wrap_environment_role_lookup]) def access_environment(portfolio_id, environment_id): env_role = EnvironmentRoles.get(g.current_user.id, environment_id) - if not env_role: - raise UnauthorizedError( - g.current_user, "access environment {}".format(environment_id) - ) - else: - token = app.csp.cloud.get_access_token(env_role) - return redirect(url_for("atst.csp_environment_access", token=token)) + token = app.csp.cloud.get_access_token(env_role) + + return redirect(url_for("atst.csp_environment_access", token=token)) diff --git a/atst/routes/portfolios/index.py b/atst/routes/portfolios/index.py index f6d04ef7..da43015c 100644 --- a/atst/routes/portfolios/index.py +++ b/atst/routes/portfolios/index.py @@ -37,11 +37,7 @@ def serialize_member(member): } -@portfolios_bp.route("/portfolios//admin") -@user_can(Permissions.VIEW_PORTFOLIO_ADMIN) -def portfolio_admin(portfolio_id): - portfolio = Portfolios.get_for_update(portfolio_id) - form = PortfolioForm(data={"name": portfolio.name}) +def render_admin_page(portfolio, form): pagination_opts = Paginator.get_pagination_opts(http_request) audit_events = AuditLog.get_portfolio_events(portfolio, pagination_opts) members_data = [serialize_member(member) for member in portfolio.members] @@ -55,6 +51,14 @@ def portfolio_admin(portfolio_id): ) +@portfolios_bp.route("/portfolios//admin") +@user_can(Permissions.VIEW_PORTFOLIO_ADMIN) +def portfolio_admin(portfolio_id): + portfolio = Portfolios.get_for_update(portfolio_id) + form = PortfolioForm(data={"name": portfolio.name}) + return render_admin_page(portfolio, form) + + @portfolios_bp.route("/portfolios//edit", methods=["POST"]) @user_can(Permissions.EDIT_PORTFOLIO_NAME) def edit_portfolio(portfolio_id): @@ -66,7 +70,8 @@ def edit_portfolio(portfolio_id): url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id) ) else: - return render_template("portfolios/edit.html", form=form, portfolio=portfolio) + # rerender portfolio admin page + return render_admin_page(portfolio, form) @portfolios_bp.route("/portfolios/") diff --git a/atst/routes/portfolios/members.py b/atst/routes/portfolios/members.py index e9d854e8..234ca550 100644 --- a/atst/routes/portfolios/members.py +++ b/atst/routes/portfolios/members.py @@ -129,6 +129,8 @@ def view_member(portfolio_id, member_id): ) +# TODO: check if member_id is consistent with other routes here; +# user ID vs portfolio role ID @portfolios_bp.route( "/portfolios//members//member_edit", methods=["POST"] ) diff --git a/tests/domain/test_audit_log.py b/tests/domain/test_audit_log.py index 110d385e..af4f76e0 100644 --- a/tests/domain/test_audit_log.py +++ b/tests/domain/test_audit_log.py @@ -22,18 +22,6 @@ def developer(): return UserFactory.create() -@pytest.mark.skip(reason="redo as a route access test") -def test_non_admin_cannot_view_audit_log(developer): - with pytest.raises(UnauthorizedError): - AuditLog.get_all_events(developer) - - -@pytest.mark.skip(reason="redo as a route access test") -def test_ccpo_can_view_audit_log(): - events = AuditLog.get_all_events() - assert len(events) > 0 - - def test_paginate_audit_log(): user = UserFactory.create() for _ in range(100): @@ -43,39 +31,6 @@ def test_paginate_audit_log(): assert len(events) == 25 -@pytest.mark.skip(reason="redo as a route access test") -def test_ccpo_can_view_ws_audit_log(): - portfolio = PortfolioFactory.create() - events = AuditLog.get_portfolio_events(portfolio) - assert len(events) > 0 - - -@pytest.mark.skip(reason="redo as a route access test") -def test_ws_admin_can_view_ws_audit_log(): - portfolio = PortfolioFactory.create() - admin = UserFactory.create() - PortfolioRoleFactory.create( - portfolio=portfolio, user=admin, status=PortfolioRoleStatus.ACTIVE - ) - events = AuditLog.get_portfolio_events(portfolio) - assert len(events) > 0 - - -@pytest.mark.skip(reason="redo as a route access test") -def test_ws_owner_can_view_ws_audit_log(): - portfolio = PortfolioFactory.create() - events = AuditLog.get_portfolio_events(portfolio) - assert len(events) > 0 - - -@pytest.mark.skip(reason="redo as a route access test") -def test_other_users_cannot_view_portfolio_audit_log(): - with pytest.raises(UnauthorizedError): - portfolio = PortfolioFactory.create() - dev = UserFactory.create() - AuditLog.get_portfolio_events(dev, portfolio) - - def test_paginate_ws_audit_log(): portfolio = PortfolioFactory.create() application = ApplicationFactory.create(portfolio=portfolio) diff --git a/tests/domain/test_portfolios.py b/tests/domain/test_portfolios.py index ef594f44..6819b36b 100644 --- a/tests/domain/test_portfolios.py +++ b/tests/domain/test_portfolios.py @@ -46,26 +46,6 @@ def test_portfolio_has_timestamps(portfolio): assert portfolio.time_created == portfolio.time_updated -@pytest.mark.skip(reason="redo as a route access test") -def test_portfolios_get_ensures_user_is_in_portfolio(portfolio, portfolio_owner): - outside_user = UserFactory.create() - with pytest.raises(UnauthorizedError): - Portfolios.get(outside_user, portfolio.id) - - -def test_get_for_update_applications_allows_owner(portfolio, portfolio_owner): - Portfolios.get_for_update(portfolio.id) - - -@pytest.mark.skip(reason="redo as a route access test") -def test_get_for_update_applications_blocks_developer(portfolio): - developer = UserFactory.create() - PortfolioRoles.add(developer, portfolio.id) - - with pytest.raises(UnauthorizedError): - Portfolios.get_for_update(portfolio.id) - - def test_can_create_portfolio_role(portfolio, portfolio_owner): user_data = { "first_name": "New", @@ -96,22 +76,6 @@ def test_can_add_existing_user_to_portfolio(portfolio, portfolio_owner): assert not new_member.user.provisional -@pytest.mark.skip(reason="redo as a route access test") -def test_need_permission_to_create_portfolio_role(portfolio, portfolio_owner): - random_user = UserFactory.create() - - user_data = { - "first_name": "New", - "last_name": "User", - "email": "new.user@mail.com", - "portfolio_role": "developer", - "dod_id": "1234567890", - } - - with pytest.raises(UnauthorizedError): - Portfolios.create_member(portfolio, user_data) - - def test_update_portfolio_role_role(portfolio, portfolio_owner): user_data = { "first_name": "New", @@ -128,42 +92,6 @@ def test_update_portfolio_role_role(portfolio, portfolio_owner): assert updated_member.portfolio == portfolio -@pytest.mark.skip(reason="redo as a route access test") -def test_need_permission_to_update_portfolio_role_role(portfolio, portfolio_owner): - random_user = UserFactory.create() - user_data = { - "first_name": "New", - "last_name": "User", - "email": "new.user@mail.com", - "portfolio_role": "developer", - "dod_id": "1234567890", - } - member = Portfolios.create_member(portfolio, user_data) - role_name = "developer" - - with pytest.raises(UnauthorizedError): - Portfolios.update_member(member, role_name) - - -def test_owner_can_view_portfolio_members(portfolio, portfolio_owner): - portfolio = Portfolios.get_for_update(portfolio.id) - - assert portfolio - - -def test_ccpo_can_view_portfolio_members(portfolio, portfolio_owner): - ccpo = UserFactory.create_ccpo() - assert Portfolios.get_for_update(portfolio.id) - - -@pytest.mark.skip(reason="redo as a route access test") -def test_random_user_cannot_view_portfolio_members(portfolio): - developer = UserFactory.create() - - with pytest.raises(UnauthorizedError): - portfolio = Portfolios.get_for_update(portfolio.id) - - def test_scoped_portfolio_for_admin_missing_view_apps_perms(portfolio_owner, portfolio): Applications.create( portfolio, "My Application 2", "My application 2", ["dev", "staging", "prod"] @@ -265,28 +193,6 @@ def test_for_user_returns_all_portfolios_for_ccpo(portfolio, portfolio_owner): assert len(sams_portfolios) == 2 -@pytest.mark.skip(reason="redo as a route access test") -def test_get_for_update_information(portfolio, portfolio_owner): - owner_ws = Portfolios.get_for_update(portfolio.id) - assert portfolio == owner_ws - - admin = UserFactory.create() - perm_sets = get_all_portfolio_permission_sets() - PortfolioRoleFactory.create( - user=admin, portfolio=portfolio, permission_sets=perm_sets - ) - admin_ws = Portfolios.get_for_update(portfolio.id) - assert portfolio == admin_ws - - # TODO: implement ccpo roles - # ccpo = UserFactory.create_ccpo() - # assert Portfolios.get_for_update(portfolio.id) - - developer = UserFactory.create() - with pytest.raises(UnauthorizedError): - Portfolios.get_for_update(portfolio.id) - - def test_can_create_portfolios_with_matching_names(): portfolio_name = "Great Portfolio" PortfolioFactory.create(name=portfolio_name) diff --git a/tests/domain/test_task_orders.py b/tests/domain/test_task_orders.py index f7f94236..fad18ade 100644 --- a/tests/domain/test_task_orders.py +++ b/tests/domain/test_task_orders.py @@ -93,46 +93,6 @@ def test_add_officer_who_is_already_portfolio_member(): assert member.user == owner -@pytest.mark.skip(reason="redo as route access test") -def test_task_order_access(): - creator = UserFactory.create() - member = UserFactory.create() - rando = UserFactory.create() - officer = UserFactory.create() - - def check_access(can, cannot, method_name, method_args): - method = getattr(TaskOrders, method_name) - - for user in can: - assert method(user, *method_args) - - for user in cannot: - with pytest.raises(UnauthorizedError): - method(user, *method_args) - - portfolio = PortfolioFactory.create(owner=creator) - task_order = TaskOrderFactory.create(creator=creator, portfolio=portfolio) - PortfolioRoleFactory.create( - user=member, - portfolio=task_order.portfolio, - permission_sets=[ - PermissionSets.get(prms) - for prms in PortfolioRoles.DEFAULT_PORTFOLIO_PERMISSION_SETS - ], - ) - TaskOrders.add_officer(task_order, "contracting_officer", officer.to_dictionary()) - - check_access([creator, officer, member], [rando], "get", [task_order.id]) - check_access([creator, officer], [member, rando], "create", [portfolio]) - check_access([creator, officer], [member, rando], "update", [task_order]) - check_access( - [creator, officer], - [member, rando], - "add_officer", - [task_order, "contracting_officer", UserFactory.dictionary()], - ) - - def test_dd254_complete(): finished = DD254Factory.create() unfinished = DD254Factory.create(certifying_official=None) diff --git a/tests/routes/portfolios/test_applications.py b/tests/routes/portfolios/test_applications.py index 941d198e..b6245aaa 100644 --- a/tests/routes/portfolios/test_applications.py +++ b/tests/routes/portfolios/test_applications.py @@ -39,54 +39,6 @@ def test_user_without_permission_has_no_budget_report_link(client, user_session) ) -@pytest.mark.skip(reason="Temporarily no add activity log link") -def test_user_with_permission_has_activity_log_link(client, user_session): - portfolio = PortfolioFactory.create() - ccpo = UserFactory.create_ccpo() - admin = UserFactory.create() - PortfolioRoleFactory.create( - portfolio=portfolio, user=admin, status=PortfolioRoleStatus.ACTIVE - ) - - user_session(portfolio.owner) - response = client.get("/portfolios/{}/applications".format(portfolio.id)) - assert ( - 'href="/portfolios/{}/activity"'.format(portfolio.id).encode() in response.data - ) - - # logs out previous user before creating a new session - user_session(admin) - response = client.get("/portfolios/{}/applications".format(portfolio.id)) - assert ( - 'href="/portfolios/{}/activity"'.format(portfolio.id).encode() in response.data - ) - - user_session(ccpo) - response = client.get("/portfolios/{}/applications".format(portfolio.id)) - assert ( - 'href="/portfolios/{}/activity"'.format(portfolio.id).encode() in response.data - ) - - -@pytest.mark.skip(reason="Temporarily no add activity log link") -def test_user_without_permission_has_no_activity_log_link(client, user_session): - portfolio = PortfolioFactory.create() - developer = UserFactory.create() - PortfolioRoleFactory.create( - portfolio=portfolio, - user=developer, - role=Roles.get("developer"), - status=PortfolioRoleStatus.ACTIVE, - ) - - user_session(developer) - response = client.get("/portfolios/{}/applications".format(portfolio.id)) - assert ( - 'href="/portfolios/{}/activity"'.format(portfolio.id).encode() - not in response.data - ) - - def test_user_with_permission_has_add_application_link(client, user_session): portfolio = PortfolioFactory.create() user_session(portfolio.owner) diff --git a/tests/routes/portfolios/test_task_orders.py b/tests/routes/portfolios/test_task_orders.py index ab6e18d0..3948eaec 100644 --- a/tests/routes/portfolios/test_task_orders.py +++ b/tests/routes/portfolios/test_task_orders.py @@ -599,48 +599,6 @@ def test_resend_invite_when_officer_type_missing( assert len(queue.get_queue()) == queue_length -@pytest.mark.skip(reason="KO should not be able to resend invites") -def test_resend_invite_when_ko(app, client, user_session, portfolio, user): - queue_length = len(queue.get_queue()) - - task_order = TaskOrderFactory.create( - portfolio=portfolio, contracting_officer=user, ko_invite=True - ) - - portfolio_role = PortfolioRoleFactory.create( - portfolio=portfolio, user=user, status=PortfolioStatus.ACTIVE - ) - - original_invitation = Invitations.create( - inviter=user, portfolio_role=portfolio_role, email=user.email - ) - - user_session(user) - - response = client.post( - url_for( - "portfolios.resend_invite", - portfolio_id=portfolio.id, - task_order_id=task_order.id, - invite_type="ko_invite", - _external=True, - ) - ) - - assert original_invitation.status == InvitationStatus.REVOKED - assert response.status_code == 302 - assert ( - url_for( - "portfolios.task_order_invitations", - portfolio_id=portfolio.id, - task_order_id=task_order.id, - _external=True, - ) - == response.headers["Location"] - ) - assert len(queue.get_queue()) == queue_length + 1 - - def test_resend_invite_when_not_pending(app, client, user_session, portfolio, user): queue_length = len(queue.get_queue()) diff --git a/tests/test_access.py b/tests/test_access.py index f8a6e82d..3244795f 100644 --- a/tests/test_access.py +++ b/tests/test_access.py @@ -1,11 +1,22 @@ import pytest +from flask import url_for, Response + import atst from atst.app import make_app, make_config from atst.domain.auth import UNPROTECTED_ROUTES as _NO_LOGIN_REQUIRED import atst.domain.authz as authz +from atst.domain.permission_sets import PermissionSets +from atst.models.portfolio_role import Status as PortfolioRoleStatus -from tests.factories import UserFactory +from tests.factories import ( + AttachmentFactory, + InvitationFactory, + PortfolioFactory, + PortfolioRoleFactory, + TaskOrderFactory, + UserFactory, +) _NO_ACCESS_CHECK_REQUIRED = _NO_LOGIN_REQUIRED + [ "task_orders.get_started", # all users can start a new TO @@ -58,6 +69,7 @@ class Null: return self +@pytest.mark.access_check @pytest.mark.parametrize("rule,route", _PROTECTED_ROUTES) def test_all_protected_routes_have_access_control( rule, route, mocker, client, user_session, monkeypatch @@ -87,3 +99,693 @@ def test_all_protected_routes_have_access_control( atst.domain.authz.decorator.user_can_access.call_count == 1 or atst.domain.authz.decorator.evaluate_exceptions.call_count == 1 ), "no access control for {}".format(rule.endpoint) + + +def user_with(*perm_sets_names): + return UserFactory.create(permission_sets=PermissionSets.get_many(perm_sets_names)) + + +@pytest.fixture +def get_url_assert_status(client, user_session): + def _get_url_assert_status(user, url, status): + user_session(user) + resp = client.get(url) + assert resp.status_code == status + + return _get_url_assert_status + + +@pytest.fixture +def post_url_assert_status(client, user_session): + def _get_url_assert_status(user, url, status): + user_session(user) + resp = client.post(url) + assert resp.status_code == status + + return _get_url_assert_status + + +# atst.activity_history +def test_atst_activity_history_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_AUDIT_LOG) + rando = user_with() + + url = url_for("atst.activity_history") + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.access_environment +def test_portfolios_access_environment_access(get_url_assert_status): + dev = UserFactory.create() + rando = UserFactory.create() + ccpo = UserFactory.create_ccpo() + + portfolio = PortfolioFactory.create( + owner=dev, + applications=[ + { + "name": "Mos Eisley", + "description": "Where Han shot first", + "environments": [ + { + "name": "thebar", + "members": [{"user": dev, "role_name": "devops"}], + } + ], + } + ], + ) + env = portfolio.applications[0].environments[0] + + url = url_for( + "portfolios.access_environment", + portfolio_id=portfolio.id, + environment_id=env.id, + ) + get_url_assert_status(dev, url, 302) + get_url_assert_status(rando, url, 404) + get_url_assert_status(ccpo, url, 404) + + +# portfolios.application_members +def test_portfolios_application_members_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create( + owner=owner, + applications=[{"name": "Mos Eisley", "description": "Where Han shot first"}], + ) + app = portfolio.applications[0] + + url = url_for( + "portfolios.application_members", + portfolio_id=portfolio.id, + application_id=app.id, + ) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.create_application +def test_portfolios_create_application_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.create_application", portfolio_id=portfolio.id) + post_url_assert_status(ccpo, url, 200) + post_url_assert_status(owner, url, 200) + post_url_assert_status(rando, url, 404) + + +# portfolios.create_member +def test_portfolios_create_member_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.create_member", portfolio_id=portfolio.id) + post_url_assert_status(ccpo, url, 200) + post_url_assert_status(owner, url, 200) + post_url_assert_status(rando, url, 404) + + +# portfolios.edit_application +def test_portfolios_edit_application_access(get_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create( + owner=owner, + applications=[{"name": "Mos Eisley", "description": "Where Han shot first"}], + ) + app = portfolio.applications[0] + + url = url_for( + "portfolios.edit_application", portfolio_id=portfolio.id, application_id=app.id + ) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.edit_portfolio +def test_portfolios_edit_portfolio_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.edit_portfolio", portfolio_id=portfolio.id) + post_url_assert_status(ccpo, url, 200) + post_url_assert_status(owner, url, 200) + post_url_assert_status(rando, url, 404) + + +# portfolios.edit_task_order_invitations +def test_portfolios_edit_task_order_invitations_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) + + url = url_for( + "portfolios.edit_task_order_invitations", + portfolio_id=portfolio.id, + task_order_id=task_order.id, + ) + post_url_assert_status(ccpo, url, 302) + post_url_assert_status(owner, url, 302) + post_url_assert_status(rando, url, 404) + + +# portfolios.ko_review +def test_portfolios_ko_review_access(get_url_assert_status): + ccpo = UserFactory.create_ccpo() + owner = user_with() + cor = user_with() + ko = user_with() + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create( + portfolio=portfolio, + contracting_officer=ko, + contracting_officer_representative=cor, + ) + + url = url_for( + "portfolios.ko_review", portfolio_id=portfolio.id, task_order_id=task_order.id + ) + get_url_assert_status(ccpo, url, 404) + get_url_assert_status(owner, url, 404) + get_url_assert_status(ko, url, 200) + get_url_assert_status(cor, url, 200) + + +# portfolios.new_application +def test_portfolios_new_application_access(get_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.new_application", portfolio_id=portfolio.id) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.new_member +def test_portfolios_new_member_access(get_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.new_member", portfolio_id=portfolio.id) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.portfolio_admin +def test_portfolios_portfolio_admin_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.portfolio_admin", portfolio_id=portfolio.id) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.portfolio_applications +def test_portfolios_portfolio_applications_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.portfolio_applications", portfolio_id=portfolio.id) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.portfolio_funding +def test_portfolios_portfolio_funding_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.portfolio_funding", portfolio_id=portfolio.id) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.portfolio_members +def test_portfolios_portfolio_members_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.portfolio_members", portfolio_id=portfolio.id) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.portfolio_reports +def test_portfolios_portfolio_reports_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_REPORTS) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.portfolio_reports", portfolio_id=portfolio.id) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.resend_invitation +def test_portfolios_resend_invitation_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + invitee = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + prr = PortfolioRoleFactory.create(user=invitee, portfolio=portfolio) + invite = InvitationFactory.create(user=UserFactory.create(), portfolio_role=prr) + + url = url_for( + "portfolios.resend_invitation", portfolio_id=portfolio.id, token=invite.token + ) + post_url_assert_status(ccpo, url, 302) + post_url_assert_status(owner, url, 302) + post_url_assert_status(invitee, url, 404) + post_url_assert_status(rando, url, 404) + + +# portfolios.resend_invite +def test_portfolios_resend_invite_access(post_url_assert_status): + ccpo = UserFactory.create_ccpo() + owner = user_with() + rando = user_with() + ko = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko) + prr = PortfolioRoleFactory.create(user=ko, portfolio=portfolio) + invite = InvitationFactory.create(user=UserFactory.create(), portfolio_role=prr) + + url = url_for( + "portfolios.resend_invite", + portfolio_id=portfolio.id, + task_order_id=task_order.id, + invite_type="ko_invite", + ) + post_url_assert_status(ccpo, url, 302) + post_url_assert_status(owner, url, 302) + post_url_assert_status(ko, url, 404) + post_url_assert_status(rando, url, 404) + + +# portfolios.revoke_access +def test_portfolios_revoke_access_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + + for user, status in [(ccpo, 302), (owner, 302), (rando, 404)]: + prt_member = user_with() + prr = PortfolioRoleFactory.create( + user=prt_member, portfolio=portfolio, status=PortfolioRoleStatus.ACTIVE + ) + url = url_for( + "portfolios.revoke_access", portfolio_id=portfolio.id, member_id=prr.id + ) + post_url_assert_status(user, url, status) + + +# portfolios.revoke_invitation +def test_portfolios_revoke_invitation_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + + for user, status in [(ccpo, 302), (owner, 302), (rando, 404)]: + prt_member = user_with() + prr = PortfolioRoleFactory.create( + user=prt_member, portfolio=portfolio, status=PortfolioRoleStatus.ACTIVE + ) + invite = InvitationFactory.create(user=prt_member, portfolio_role=prr) + url = url_for( + "portfolios.revoke_invitation", + portfolio_id=portfolio.id, + token=invite.token, + ) + post_url_assert_status(user, url, status) + + +# portfolios.show_portfolio +def test_portfolios_show_portfolio_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + + url = url_for("portfolios.show_portfolio", portfolio_id=portfolio.id) + get_url_assert_status(ccpo, url, 302) + get_url_assert_status(owner, url, 302) + get_url_assert_status(rando, url, 404) + + +# portfolios.so_review +def test_portfolios_so_review_access(get_url_assert_status): + ccpo = UserFactory.create_ccpo() + owner = user_with() + rando = user_with() + so = user_with() + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio, security_officer=so) + + url = url_for( + "portfolios.so_review", portfolio_id=portfolio.id, task_order_id=task_order.id + ) + get_url_assert_status(so, url, 200) + get_url_assert_status(ccpo, url, 404) + get_url_assert_status(owner, url, 404) + get_url_assert_status(rando, url, 404) + + +# portfolios.submit_ko_review +def test_portfolios_submit_ko_review_access(post_url_assert_status): + ccpo = UserFactory.create_ccpo() + owner = user_with() + cor = user_with() + ko = user_with() + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create( + portfolio=portfolio, + contracting_officer=ko, + contracting_officer_representative=cor, + ) + + url = url_for( + "portfolios.submit_ko_review", + portfolio_id=portfolio.id, + task_order_id=task_order.id, + ) + post_url_assert_status(ccpo, url, 404) + post_url_assert_status(owner, url, 404) + post_url_assert_status(ko, url, 200) + post_url_assert_status(cor, url, 200) + + +# portfolios.submit_so_review +def test_portfolios_submit_so_review_access(post_url_assert_status): + ccpo = UserFactory.create_ccpo() + owner = user_with() + rando = user_with() + so = user_with() + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio, security_officer=so) + + url = url_for( + "portfolios.submit_so_review", + portfolio_id=portfolio.id, + task_order_id=task_order.id, + ) + post_url_assert_status(so, url, 200) + post_url_assert_status(ccpo, url, 404) + post_url_assert_status(owner, url, 404) + post_url_assert_status(rando, url, 404) + + +# portfolios.task_order_invitations +def test_portfolios_task_order_invitations_access(get_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) + + url = url_for( + "portfolios.task_order_invitations", + portfolio_id=portfolio.id, + task_order_id=task_order.id, + ) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(owner, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.update_application +def test_portfolios_update_application_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT) + dev = UserFactory.create() + rando = UserFactory.create() + + portfolio = PortfolioFactory.create( + owner=dev, + applications=[{"name": "Mos Eisley", "description": "Where Han shot first"}], + ) + app = portfolio.applications[0] + + url = url_for( + "portfolios.update_application", + portfolio_id=portfolio.id, + application_id=app.id, + ) + post_url_assert_status(dev, url, 200) + post_url_assert_status(ccpo, url, 200) + post_url_assert_status(rando, url, 404) + + +# portfolios.update_member +def test_portfolios_update_member_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + prt_member = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + prr = PortfolioRoleFactory.create(user=prt_member, portfolio=portfolio) + + url = url_for( + "portfolios.update_member", portfolio_id=portfolio.id, member_id=prt_member.id + ) + post_url_assert_status(owner, url, 200) + post_url_assert_status(ccpo, url, 200) + post_url_assert_status(rando, url, 404) + + +# portfolios.view_member +def test_portfolios_view_member_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_ADMIN) + owner = user_with() + rando = user_with() + prt_member = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + prr = PortfolioRoleFactory.create(user=prt_member, portfolio=portfolio) + + url = url_for( + "portfolios.view_member", portfolio_id=portfolio.id, member_id=prt_member.id + ) + get_url_assert_status(owner, url, 200) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(rando, url, 404) + + +# portfolios.view_task_order +def test_portfolios_view_task_order_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) + + url = url_for( + "portfolios.view_task_order", + portfolio_id=portfolio.id, + task_order_id=task_order.id, + ) + get_url_assert_status(owner, url, 200) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(rando, url, 404) + + +# task_orders.download_csp_estimate +def test_task_orders_download_csp_estimate_access(get_url_assert_status, monkeypatch): + monkeypatch.setattr( + "atst.routes.task_orders.index.send_file", lambda a: Response("") + ) + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) + + url = url_for("task_orders.download_csp_estimate", task_order_id=task_order.id) + get_url_assert_status(owner, url, 200) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(rando, url, 404) + + +# task_orders.download_summary +def test_task_orders_download_summary_access(get_url_assert_status): + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) + + url = url_for("task_orders.download_summary", task_order_id=task_order.id) + get_url_assert_status(owner, url, 200) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(rando, url, 404) + + +# task_orders.download_task_order_pdf +def test_task_orders_download_task_order_pdf_access(get_url_assert_status, monkeypatch): + monkeypatch.setattr( + "atst.routes.task_orders.index.send_file", lambda a: Response("") + ) + ccpo = user_with(PermissionSets.VIEW_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create( + portfolio=portfolio, pdf=AttachmentFactory.create() + ) + + url = url_for("task_orders.download_task_order_pdf", task_order_id=task_order.id) + get_url_assert_status(owner, url, 200) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(rando, url, 404) + + +# task_orders.invite +def test_task_orders_invite_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) + + url = url_for("task_orders.invite", task_order_id=task_order.id) + post_url_assert_status(owner, url, 302) + post_url_assert_status(ccpo, url, 302) + post_url_assert_status(rando, url, 404) + + +# task_orders.new +def test_task_orders_new_access(get_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + + url = url_for("task_orders.new", screen=1) + get_url_assert_status(owner, url, 200) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(rando, url, 200) + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) + + url = url_for("task_orders.new", screen=2, task_order_id=task_order.id) + get_url_assert_status(owner, url, 200) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(rando, url, 404) + + url = url_for("task_orders.new", screen=1, portfolio_id=portfolio.id) + get_url_assert_status(owner, url, 200) + get_url_assert_status(ccpo, url, 200) + get_url_assert_status(rando, url, 404) + + +# task_orders.record_signature +def test_task_orders_record_signature_access(post_url_assert_status, monkeypatch): + ccpo = UserFactory.create_ccpo() + owner = user_with() + rando = user_with() + ko = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko) + monkeypatch.setattr( + "atst.routes.task_orders.signing.find_unsigned_ko_to", lambda *a: task_order + ) + + url = url_for("task_orders.record_signature", task_order_id=task_order.id) + post_url_assert_status(ko, url, 400) + post_url_assert_status(owner, url, 404) + post_url_assert_status(ccpo, url, 404) + post_url_assert_status(rando, url, 404) + + +# task_orders.signature_requested +def test_task_orders_signature_requested_access(get_url_assert_status, monkeypatch): + ccpo = UserFactory.create_ccpo() + owner = user_with() + rando = user_with() + ko = user_with() + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko) + monkeypatch.setattr( + "atst.routes.task_orders.signing.find_unsigned_ko_to", lambda *a: task_order + ) + + url = url_for("task_orders.record_signature", task_order_id=task_order.id) + get_url_assert_status(ko, url, 200) + get_url_assert_status(owner, url, 404) + get_url_assert_status(ccpo, url, 404) + get_url_assert_status(rando, url, 404) + + +# task_orders.update +def test_task_orders_update_access(post_url_assert_status): + ccpo = user_with(PermissionSets.EDIT_PORTFOLIO_FUNDING) + owner = user_with() + rando = user_with() + + url = url_for("task_orders.update", screen=1) + post_url_assert_status(owner, url, 200) + post_url_assert_status(ccpo, url, 200) + post_url_assert_status(rando, url, 200) + + portfolio = PortfolioFactory.create(owner=owner) + task_order = TaskOrderFactory.create(portfolio=portfolio) + + url = url_for("task_orders.update", screen=2, task_order_id=task_order.id) + post_url_assert_status(owner, url, 302) + post_url_assert_status(ccpo, url, 302) + post_url_assert_status(rando, url, 404) + + url = url_for("task_orders.update", screen=1, portfolio_id=portfolio.id) + post_url_assert_status(owner, url, 302) + post_url_assert_status(ccpo, url, 302) + post_url_assert_status(rando, url, 404)