switch portfolio authorization to rely on new permission sets

This commit is contained in:
dandds 2019-03-11 17:25:35 -04:00
parent 6805041b13
commit 44a4d98978
22 changed files with 204 additions and 112 deletions

View File

@ -27,7 +27,7 @@ class Applications(object):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user,
portfolio, portfolio,
Permissions.VIEW_APPLICATION_IN_PORTFOLIO, Permissions.VIEW_APPLICATION,
"view application in portfolio", "view application in portfolio",
) )
@ -56,7 +56,7 @@ class Applications(object):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user,
portfolio, portfolio,
Permissions.VIEW_APPLICATION_IN_PORTFOLIO, Permissions.VIEW_APPLICATION,
"view application in portfolio", "view application in portfolio",
) )

View File

@ -36,6 +36,7 @@ class AuditLog(object):
@classmethod @classmethod
def get_all_events(cls, user, pagination_opts=None): def get_all_events(cls, user, pagination_opts=None):
# TODO: general audit log permissions
Authorization.check_atat_permission( Authorization.check_atat_permission(
user, Permissions.VIEW_AUDIT_LOG, "view audit log" user, Permissions.VIEW_AUDIT_LOG, "view audit log"
) )
@ -46,7 +47,7 @@ class AuditLog(object):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user,
portfolio, portfolio,
Permissions.VIEW_PORTFOLIO_AUDIT_LOG, Permissions.VIEW_PORTFOLIO_ACTIVITY_LOG,
"view portfolio audit log", "view portfolio audit log",
) )
return AuditEventQuery.get_ws_events(portfolio.id, pagination_opts) return AuditEventQuery.get_ws_events(portfolio.id, pagination_opts)

View File

@ -1,4 +1,3 @@
from atst.domain.portfolio_roles import PortfolioRoles
from atst.models.permissions import Permissions from atst.models.permissions import Permissions
from atst.domain.exceptions import UnauthorizedError from atst.domain.exceptions import UnauthorizedError
@ -6,9 +5,13 @@ from atst.domain.exceptions import UnauthorizedError
class Authorization(object): class Authorization(object):
@classmethod @classmethod
def has_portfolio_permission(cls, user, portfolio, permission): def has_portfolio_permission(cls, user, portfolio, permission):
return permission in PortfolioRoles.portfolio_role_permissions( port_role = next(
portfolio, user (pr for pr in user.portfolio_roles if pr.portfolio == portfolio), None
) or Authorization.is_ccpo(user) )
if port_role:
return permission in port_role.permissions
else:
return False
@classmethod @classmethod
def has_atat_permission(cls, user, permission): def has_atat_permission(cls, user, permission):

View File

@ -64,7 +64,7 @@ class Environments(object):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user,
portfolio, portfolio,
Permissions.ADD_AND_ASSIGN_CSP_ROLES, Permissions.EDIT_APPLICATION_MEMBER,
"assign environment roles", "assign environment roles",
) )
updated = False updated = False
@ -104,7 +104,7 @@ class Environments(object):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user,
environment.portfolio, environment.portfolio,
Permissions.REMOVE_CSP_ROLES, Permissions.EDIT_APPLICATION_MEMBER,
"revoke environment access", "revoke environment access",
) )
EnvironmentRoles.delete(environment.id, target_user.id) EnvironmentRoles.delete(environment.id, target_user.id)

View File

@ -119,7 +119,7 @@ class Invitations(object):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user,
portfolio, portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE, Permissions.CREATE_PORTFOLIO_USERS,
"resend a portfolio invitation", "resend a portfolio invitation",
) )

View File

@ -1,4 +1,4 @@
from atst.domain.roles import Roles from atst.domain.roles import Roles, PORTFOLIO_PERMISSION_SETS
from atst.domain.authz import Authorization from atst.domain.authz import Authorization
from atst.models.permissions import Permissions from atst.models.permissions import Permissions
from atst.domain.users import Users from atst.domain.users import Users
@ -20,8 +20,13 @@ class Portfolios(object):
portfolio = PortfoliosQuery.create( portfolio = PortfoliosQuery.create(
name=name, defense_component=defense_component name=name, defense_component=defense_component
) )
perms_sets = [Roles.get(prms["name"]) for prms in PORTFOLIO_PERMISSION_SETS]
Portfolios._create_portfolio_role( Portfolios._create_portfolio_role(
user, portfolio, "owner", status=PortfolioRoleStatus.ACTIVE user,
portfolio,
"owner",
status=PortfolioRoleStatus.ACTIVE,
permission_sets=perms_sets,
) )
PortfoliosQuery.add_and_commit(portfolio) PortfoliosQuery.add_and_commit(portfolio)
return portfolio return portfolio
@ -39,7 +44,7 @@ class Portfolios(object):
def get_for_update_applications(cls, user, portfolio_id): def get_for_update_applications(cls, user, portfolio_id):
portfolio = PortfoliosQuery.get(portfolio_id) portfolio = PortfoliosQuery.get(portfolio_id)
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, portfolio, Permissions.ADD_APPLICATION_IN_PORTFOLIO, "add application" user, portfolio, Permissions.CREATE_APPLICATION, "add application"
) )
return portfolio return portfolio
@ -50,7 +55,7 @@ class Portfolios(object):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user,
portfolio, portfolio,
Permissions.EDIT_PORTFOLIO_INFORMATION, Permissions.EDIT_PORTFOLIO_NAME,
"update portfolio information", "update portfolio information",
) )
@ -62,7 +67,7 @@ class Portfolios(object):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user,
portfolio, portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE, Permissions.EDIT_PORTFOLIO_USERS,
"update a portfolio member", "update a portfolio member",
) )
@ -72,10 +77,7 @@ class Portfolios(object):
def get_with_members(cls, user, portfolio_id): def get_with_members(cls, user, portfolio_id):
portfolio = PortfoliosQuery.get(portfolio_id) portfolio = PortfoliosQuery.get(portfolio_id)
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user, portfolio, Permissions.VIEW_PORTFOLIO_USERS, "view portfolio members"
portfolio,
Permissions.VIEW_PORTFOLIO_MEMBERS,
"view portfolio members",
) )
return portfolio return portfolio
@ -91,10 +93,7 @@ class Portfolios(object):
@classmethod @classmethod
def create_member(cls, user, portfolio, data): def create_member(cls, user, portfolio, data):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user, portfolio, Permissions.EDIT_PORTFOLIO_USERS, "create portfolio member"
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
"create portfolio member",
) )
new_user = Users.get_or_create_by_dod_id( new_user = Users.get_or_create_by_dod_id(
@ -120,21 +119,27 @@ class Portfolios(object):
@classmethod @classmethod
def update_member(cls, user, portfolio, member, role_name): def update_member(cls, user, portfolio, member, role_name):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user, portfolio, Permissions.EDIT_PORTFOLIO_USERS, "edit portfolio member"
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
"edit portfolio member",
) )
return PortfolioRoles.update_role(member, role_name) return PortfolioRoles.update_role(member, role_name)
@classmethod @classmethod
def _create_portfolio_role( def _create_portfolio_role(
cls, user, portfolio, role_name, status=PortfolioRoleStatus.PENDING cls,
user,
portfolio,
role_name,
status=PortfolioRoleStatus.PENDING,
permission_sets=None,
): ):
role = Roles.get(role_name) role = Roles.get(role_name)
if permission_sets is None:
permission_sets = []
portfolio_role = PortfoliosQuery.create_portfolio_role( portfolio_role = PortfoliosQuery.create_portfolio_role(
user, role, portfolio, status=status user, role, portfolio, status=status, permission_sets=permission_sets
) )
PortfoliosQuery.add_and_commit(portfolio_role) PortfoliosQuery.add_and_commit(portfolio_role)
return portfolio_role return portfolio_role
@ -157,10 +162,7 @@ class Portfolios(object):
def revoke_access(cls, user, portfolio_id, portfolio_role_id): def revoke_access(cls, user, portfolio_id, portfolio_role_id):
portfolio = PortfoliosQuery.get(portfolio_id) portfolio = PortfoliosQuery.get(portfolio_id)
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user, portfolio, Permissions.EDIT_PORTFOLIO_USERS, "revoke portfolio access"
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
"revoke portfolio access",
) )
portfolio_role = PortfolioRoles.get_by_id(portfolio_role_id) portfolio_role = PortfolioRoles.get_by_id(portfolio_role_id)

View File

@ -31,7 +31,7 @@ class ScopedPortfolio(ScopedResource):
@property @property
def applications(self): def applications(self):
can_view_all_applications = Authorization.has_portfolio_permission( can_view_all_applications = Authorization.has_portfolio_permission(
self.user, self.resource, Permissions.VIEW_APPLICATION_IN_PORTFOLIO self.user, self.resource, Permissions.VIEW_APPLICATION
) )
if can_view_all_applications: if can_view_all_applications:
@ -54,9 +54,7 @@ class ScopedApplication(ScopedResource):
@property @property
def environments(self): def environments(self):
can_view_all_environments = Authorization.has_portfolio_permission( can_view_all_environments = Authorization.has_portfolio_permission(
self.user, self.user, self.resource.portfolio, Permissions.VIEW_ENVIRONMENT
self.resource.portfolio,
Permissions.VIEW_ENVIRONMENT_IN_APPLICATION,
) )
if can_view_all_environments: if can_view_all_environments:

View File

@ -161,7 +161,13 @@ PORTFOLIO_ROLES = [
}, },
] ]
PORTFOLIO_PERMISSION_SETS = [ _VIEW_PORTFOLIO_PERMISSION_SETS = [
{
"name": "view_portfolio",
"description": "View basic portfolio info",
"display_name": "View Portfolio",
"permissions": [Permissions.VIEW_PORTFOLIO],
},
{ {
"name": "view_portfolio_application_management", "name": "view_portfolio_application_management",
"description": "View applications and related resources", "description": "View applications and related resources",
@ -172,6 +178,36 @@ PORTFOLIO_PERMISSION_SETS = [
Permissions.VIEW_ENVIRONMENT, Permissions.VIEW_ENVIRONMENT,
], ],
}, },
{
"name": "view_portfolio_funding",
"description": "View a portfolio's task orders",
"display_name": "Funding",
"permissions": [
Permissions.VIEW_PORTFOLIO_FUNDING,
Permissions.VIEW_TASK_ORDER_DETAILS,
],
},
{
"name": "view_portfolio_reports",
"description": "View a portfolio's reports",
"display_name": "Reporting",
"permissions": [Permissions.VIEW_PORTFOLIO_REPORTS],
},
{
"name": "view_portfolio_admin",
"description": "View a portfolio's admin options",
"display_name": "Portfolio Administration",
"permissions": [
Permissions.VIEW_PORTFOLIO_ADMIN,
Permissions.VIEW_PORTFOLIO_NAME,
Permissions.VIEW_PORTFOLIO_USERS,
Permissions.VIEW_PORTFOLIO_ACTIVITY_LOG,
Permissions.VIEW_PORTFOLIO_POC,
],
},
]
_EDIT_PORTFOLIO_PERMISSION_SETS = [
{ {
"name": "edit_portfolio_application_management", "name": "edit_portfolio_application_management",
"description": "Edit applications and related resources", "description": "Edit applications and related resources",
@ -185,15 +221,6 @@ PORTFOLIO_PERMISSION_SETS = [
Permissions.CREATE_ENVIRONMENT, Permissions.CREATE_ENVIRONMENT,
], ],
}, },
{
"name": "view_portfolio_funding",
"description": "View a portfolio's task orders",
"display_name": "Funding",
"permissions": [
Permissions.VIEW_PORTFOLIO_FUNDING,
Permissions.VIEW_TASK_ORDER_DETAILS,
],
},
{ {
"name": "edit_portfolio_funding", "name": "edit_portfolio_funding",
"description": "Edit a portfolio's task orders and add new ones", "description": "Edit a portfolio's task orders and add new ones",
@ -203,30 +230,12 @@ PORTFOLIO_PERMISSION_SETS = [
Permissions.EDIT_TASK_ORDER_DETAILS, Permissions.EDIT_TASK_ORDER_DETAILS,
], ],
}, },
{
"name": "view_portfolio_reports",
"description": "View a portfolio's reports",
"display_name": "Reporting",
"permissions": [Permissions.VIEW_PORTFOLIO_REPORTS],
},
{ {
"name": "edit_portfolio_reports", "name": "edit_portfolio_reports",
"description": "Edit a portfolio's reports (no-op)", "description": "Edit a portfolio's reports (no-op)",
"display_name": "Reporting", "display_name": "Reporting",
"permissions": [], "permissions": [],
}, },
{
"name": "view_portfolio_admin",
"description": "View a portfolio's admin options",
"display_name": "Portfolio Administration",
"permissions": [
Permissions.VIEW_PORTFOLIO_ADMIN,
Permissions.VIEW_PORTFOLIO_NAME,
Permissions.VIEW_PORTFOLIO_USERS,
Permissions.VIEW_PORTFOLIO_ACTIVITY_LOG,
Permissions.VIEW_PORTFOLIO_POC,
],
},
{ {
"name": "edit_portfolio_admin", "name": "edit_portfolio_admin",
"description": "Edit a portfolio's admin options", "description": "Edit a portfolio's admin options",
@ -237,13 +246,23 @@ PORTFOLIO_PERMISSION_SETS = [
Permissions.CREATE_PORTFOLIO_USERS, Permissions.CREATE_PORTFOLIO_USERS,
], ],
}, },
]
PORTFOLIO_PERMISSION_SETS = (
_VIEW_PORTFOLIO_PERMISSION_SETS
+ _EDIT_PORTFOLIO_PERMISSION_SETS
+ [
{ {
"name": "portfolio_poc", "name": "portfolio_poc",
"description": "Permissions belonging to the Portfolio POC", "description": "Permissions belonging to the Portfolio POC",
"display_name": "Portfolio Point of Contact", "display_name": "Portfolio Point of Contact",
"permissions": [Permissions.EDIT_PORTFOLIO_POC, Permissions.ARCHIVE_PORTFOLIO], "permissions": [
}, Permissions.EDIT_PORTFOLIO_POC,
] Permissions.ARCHIVE_PORTFOLIO,
],
}
]
)
class Roles(object): class Roles(object):

View File

@ -57,7 +57,7 @@ class TaskOrders(object):
try: try:
task_order = db.session.query(TaskOrder).filter_by(id=task_order_id).one() task_order = db.session.query(TaskOrder).filter_by(id=task_order_id).one()
Authorization.check_task_order_permission( Authorization.check_task_order_permission(
user, task_order, Permissions.VIEW_TASK_ORDER, "view task order" user, task_order, Permissions.VIEW_TASK_ORDER_DETAILS, "view task order"
) )
return task_order return task_order
@ -67,7 +67,7 @@ class TaskOrders(object):
@classmethod @classmethod
def create(cls, creator, portfolio): def create(cls, creator, portfolio):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
creator, portfolio, Permissions.UPDATE_TASK_ORDER, "add task order" creator, portfolio, Permissions.CREATE_TASK_ORDER, "add task order"
) )
task_order = TaskOrder(portfolio=portfolio, creator=creator) task_order = TaskOrder(portfolio=portfolio, creator=creator)
@ -79,7 +79,7 @@ class TaskOrders(object):
@classmethod @classmethod
def update(cls, user, task_order, **kwargs): def update(cls, user, task_order, **kwargs):
Authorization.check_task_order_permission( Authorization.check_task_order_permission(
user, task_order, Permissions.UPDATE_TASK_ORDER, "update task order" user, task_order, Permissions.EDIT_TASK_ORDER_DETAILS, "update task order"
) )
for key, value in kwargs.items(): for key, value in kwargs.items():
@ -150,7 +150,7 @@ class TaskOrders(object):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
user, user,
task_order.portfolio, task_order.portfolio,
Permissions.ADD_TASK_ORDER_OFFICER, Permissions.EDIT_TASK_ORDER_DETAILS,
"add task order officer", "add task order officer",
) )
@ -170,7 +170,13 @@ class TaskOrders(object):
portfolio_user = existing_member.user portfolio_user = existing_member.user
else: else:
member = Portfolios.create_member( member = Portfolios.create_member(
user, portfolio, {**officer_data, "portfolio_role": "officer"} user,
portfolio,
{
**officer_data,
"portfolio_role": "officer",
"permission_sets": ["edit_portfolio_funding"],
},
) )
portfolio_user = member.user portfolio_user = member.user

View File

@ -64,7 +64,7 @@ def portfolio_reports(portfolio_id):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
g.current_user, g.current_user,
portfolio, portfolio,
Permissions.VIEW_USAGE_DOLLARS, Permissions.VIEW_PORTFOLIO_REPORTS,
"view portfolio reports", "view portfolio reports",
) )

View File

@ -110,7 +110,7 @@ def view_member(portfolio_id, member_id):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
g.current_user, g.current_user,
portfolio, portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE, Permissions.EDIT_PORTFOLIO_USERS,
"edit this portfolio user", "edit this portfolio user",
) )
member = PortfolioRoles.get(portfolio_id, member_id) member = PortfolioRoles.get(portfolio_id, member_id)
@ -144,7 +144,7 @@ def update_member(portfolio_id, member_id):
Authorization.check_portfolio_permission( Authorization.check_portfolio_permission(
g.current_user, g.current_user,
portfolio, portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE, Permissions.EDIT_PORTFOLIO_USERS,
"edit this portfolio user", "edit this portfolio user",
) )
member = PortfolioRoles.get(portfolio_id, member_id) member = PortfolioRoles.get(portfolio_id, member_id)

View File

@ -3,7 +3,7 @@
{% extends "portfolios/base.html" %} {% extends "portfolios/base.html" %}
{% set can_create_applications = user_can(permissions.ADD_APPLICATION_IN_PORTFOLIO) %} {% set can_create_applications = user_can(permissions.CREATE_APPLICATION) %}
{% block portfolio_content %} {% block portfolio_content %}
@ -41,13 +41,13 @@
<h3 class='icon-link accordian__title' v-on:click="props.toggle">{{ application.name }}</h3> <h3 class='icon-link accordian__title' v-on:click="props.toggle">{{ application.name }}</h3>
<span class='accordian__description'>{{ application.description }}</span> <span class='accordian__description'>{{ application.description }}</span>
<div class='accordian__actions'> <div class='accordian__actions'>
{% if user_can(permissions.RENAME_APPLICATION_IN_PORTFOLIO) %} {% if user_can(permissions.EDIT_APPLICATION) %}
<a class='icon-link' href='{{ url_for("portfolios.edit_application", portfolio_id=portfolio.id, application_id=application.id) }}'> <a class='icon-link' href='{{ url_for("portfolios.edit_application", portfolio_id=portfolio.id, application_id=application.id) }}'>
<span>{{ "portfolios.applications.app_settings_text" | translate }}</span> <span>{{ "portfolios.applications.app_settings_text" | translate }}</span>
</a> </a>
<div class='separator'></div> <div class='separator'></div>
{% endif %} {% endif %}
{% if user_can(permissions.VIEW_PORTFOLIO_MEMBERS) %} {% if user_can(permissions.VIEW_PORTFOLIO_USERS) %}
<a class='icon-link' href='{{ url_for("portfolios.application_members", portfolio_id=portfolio.id, application_id=application.id) }}'> <a class='icon-link' href='{{ url_for("portfolios.application_members", portfolio_id=portfolio.id, application_id=application.id) }}'>
<span>{{ "portfolios.applications.team_text" | translate }}</span> <span>{{ "portfolios.applications.team_text" | translate }}</span>
<span class='counter'>{{ application.num_users }}</span> <span class='counter'>{{ application.num_users }}</span>

View File

@ -11,7 +11,7 @@
{% if not portfolio.members %} {% if not portfolio.members %}
{% set user_can_invite = user_can(permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE) %} {% set user_can_invite = user_can(permissions.CREATE_PORTFOLIO_USERS) %}
{{ EmptyState( {{ EmptyState(
'There are currently no members in this Portfolio.', 'There are currently no members in this Portfolio.',

View File

@ -56,7 +56,7 @@
</div> </div>
</div> </div>
<div class='row links'> <div class='row links'>
{% if user_can(permissions.VIEW_USAGE_DOLLARS) %} {% if user_can(permissions.VIEW_PORTFOLIO_REPORTS) %}
{{ Link( {{ Link(
icon='chart-pie', icon='chart-pie',
text='navigation.portfolio_navigation.breadcrumbs.reports' | translate, text='navigation.portfolio_navigation.breadcrumbs.reports' | translate,
@ -70,7 +70,7 @@
url=url_for("portfolios.portfolio_funding", portfolio_id=portfolio.id), url=url_for("portfolios.portfolio_funding", portfolio_id=portfolio.id),
active=request.url_rule.endpoint == "portfolios.portfolio_funding", active=request.url_rule.endpoint == "portfolios.portfolio_funding",
) }} ) }}
{% if user_can(permissions.EDIT_PORTFOLIO_INFORMATION) %} {% if user_can(permissions.EDIT_PORTFOLIO_NAME) %}
{{ Link( {{ Link(
icon='cog', icon='cog',
text='navigation.portfolio_navigation.breadcrumbs.admin' | translate, text='navigation.portfolio_navigation.breadcrumbs.admin' | translate,

View File

@ -9,7 +9,7 @@
{% if not portfolio.members %} {% if not portfolio.members %}
{% set user_can_invite = user_can(permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE) %} {% set user_can_invite = user_can(permissions.CREATE_PORTFOLIO_USERS) %}
{{ EmptyState( {{ EmptyState(
'There are currently no members in this Portfolio.', 'There are currently no members in this Portfolio.',

View File

@ -134,7 +134,7 @@
{% if not portfolio.applications %} {% if not portfolio.applications %}
{% set can_create_applications = user_can(permissions.ADD_APPLICATION_IN_PORTFOLIO) %} {% set can_create_applications = user_can(permissions.CREATE_APPLICATION) %}
{% set message = 'This portfolio has no cloud environments set up, so there is no spending data to report. Create an application with some cloud environments to get started.' {% set message = 'This portfolio has no cloud environments set up, so there is no spending data to report. Create an application with some cloud environments to get started.'
if can_create_applications if can_create_applications
else 'This portfolio has no cloud environments set up, so there is no spending data to report. Contact the portfolio owner to set up some cloud environments.' else 'This portfolio has no cloud environments set up, so there is no spending data to report. Contact the portfolio owner to set up some cloud environments.'

View File

@ -27,6 +27,7 @@ def test_non_admin_cannot_view_audit_log(developer):
AuditLog.get_all_events(developer) AuditLog.get_all_events(developer)
@pytest.mark.skip(reason="no ccpo access yet")
def test_ccpo_can_view_audit_log(ccpo): def test_ccpo_can_view_audit_log(ccpo):
events = AuditLog.get_all_events(ccpo) events = AuditLog.get_all_events(ccpo)
assert len(events) > 0 assert len(events) > 0
@ -41,6 +42,7 @@ def test_paginate_audit_log(ccpo):
assert len(events) == 25 assert len(events) == 25
@pytest.mark.skip(reason="no ccpo access yet")
def test_ccpo_can_view_ws_audit_log(ccpo): def test_ccpo_can_view_ws_audit_log(ccpo):
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
events = AuditLog.get_portfolio_events(ccpo, portfolio) events = AuditLog.get_portfolio_events(ccpo, portfolio)
@ -66,6 +68,7 @@ def test_ws_owner_can_view_ws_audit_log():
assert len(events) > 0 assert len(events) > 0
@pytest.mark.skip(reason="all portfolio users can view audit log")
def test_other_users_cannot_view_ws_audit_log(): def test_other_users_cannot_view_ws_audit_log():
with pytest.raises(UnauthorizedError): with pytest.raises(UnauthorizedError):
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()

View File

@ -1,8 +1,10 @@
import pytest import pytest
from tests.factories import TaskOrderFactory, UserFactory from tests.factories import TaskOrderFactory, UserFactory, PortfolioRoleFactory
from atst.domain.authz import Authorization from atst.domain.authz import Authorization
from atst.domain.roles import Roles
from atst.domain.exceptions import UnauthorizedError from atst.domain.exceptions import UnauthorizedError
from atst.models.permissions import Permissions
@pytest.fixture @pytest.fixture
@ -40,3 +42,19 @@ def test_check_is_ko_or_cor(task_order, invalid_user):
with pytest.raises(UnauthorizedError): with pytest.raises(UnauthorizedError):
Authorization.check_is_ko_or_cor(invalid_user, task_order) Authorization.check_is_ko_or_cor(invalid_user, task_order)
def test_has_portfolio_permission():
role_one = Roles.get("view_portfolio_funding")
role_two = Roles.get("view_portfolio_reports")
port_role = PortfolioRoleFactory.create(permission_sets=[role_one, role_two])
different_user = UserFactory.create()
assert Authorization.has_portfolio_permission(
port_role.user, port_role.portfolio, Permissions.VIEW_PORTFOLIO_REPORTS
)
assert not Authorization.has_portfolio_permission(
port_role.user, port_role.portfolio, Permissions.CREATE_TASK_ORDER
)
assert not Authorization.has_portfolio_permission(
different_user, port_role.portfolio, Permissions.VIEW_PORTFOLIO_REPORTS
)

View File

@ -6,6 +6,7 @@ from atst.domain.portfolios import Portfolios, PortfolioError
from atst.domain.portfolio_roles import PortfolioRoles from atst.domain.portfolio_roles import PortfolioRoles
from atst.domain.applications import Applications from atst.domain.applications import Applications
from atst.domain.environments import Environments from atst.domain.environments import Environments
from atst.domain.roles import Roles, PORTFOLIO_PERMISSION_SETS
from atst.models.portfolio_role import Status as PortfolioRoleStatus from atst.models.portfolio_role import Status as PortfolioRoleStatus
from tests.factories import UserFactory, PortfolioRoleFactory, PortfolioFactory from tests.factories import UserFactory, PortfolioRoleFactory, PortfolioFactory
@ -144,6 +145,7 @@ def test_owner_can_view_portfolio_members(portfolio, portfolio_owner):
assert portfolio assert portfolio
@pytest.mark.skip(reason="no ccpo access yet")
def test_ccpo_can_view_portfolio_members(portfolio, portfolio_owner): def test_ccpo_can_view_portfolio_members(portfolio, portfolio_owner):
ccpo = UserFactory.from_atat_role("ccpo") ccpo = UserFactory.from_atat_role("ccpo")
assert Portfolios.get_with_members(ccpo, portfolio.id) assert Portfolios.get_with_members(ccpo, portfolio.id)
@ -156,6 +158,7 @@ def test_random_user_cannot_view_portfolio_members(portfolio):
portfolio = Portfolios.get_with_members(developer, portfolio.id) portfolio = Portfolios.get_with_members(developer, portfolio.id)
@pytest.mark.skip(reason="should be reworked pending application member changes")
def test_scoped_portfolio_only_returns_a_users_applications_and_environments( def test_scoped_portfolio_only_returns_a_users_applications_and_environments(
portfolio, portfolio_owner portfolio, portfolio_owner
): ):
@ -199,8 +202,9 @@ def test_scoped_portfolio_returns_all_applications_for_portfolio_admin(
) )
admin = UserFactory.from_atat_role("default") admin = UserFactory.from_atat_role("default")
Portfolios._create_portfolio_role( perm_sets = [Roles.get(prms["name"]) for prms in PORTFOLIO_PERMISSION_SETS]
admin, portfolio, "admin", status=PortfolioRoleStatus.ACTIVE PortfolioRoleFactory.create(
user=admin, portfolio=portfolio, permission_sets=perm_sets
) )
scoped_portfolio = Portfolios.get(admin, portfolio.id) scoped_portfolio = Portfolios.get(admin, portfolio.id)
@ -260,14 +264,16 @@ def test_get_for_update_information(portfolio, portfolio_owner):
assert portfolio == owner_ws assert portfolio == owner_ws
admin = UserFactory.create() admin = UserFactory.create()
Portfolios._create_portfolio_role( perm_sets = [Roles.get(prms["name"]) for prms in PORTFOLIO_PERMISSION_SETS]
admin, portfolio, "admin", status=PortfolioRoleStatus.ACTIVE PortfolioRoleFactory.create(
user=admin, portfolio=portfolio, permission_sets=perm_sets
) )
admin_ws = Portfolios.get_for_update_information(admin, portfolio.id) admin_ws = Portfolios.get_for_update_information(admin, portfolio.id)
assert portfolio == admin_ws assert portfolio == admin_ws
ccpo = UserFactory.from_atat_role("ccpo") # TODO: implement ccpo roles
assert Portfolios.get_for_update_information(ccpo, portfolio.id) # ccpo = UserFactory.from_atat_role("ccpo")
# assert Portfolios.get_for_update_information(ccpo, portfolio.id)
developer = UserFactory.from_atat_role("developer") developer = UserFactory.from_atat_role("developer")
with pytest.raises(UnauthorizedError): with pytest.raises(UnauthorizedError):

View File

@ -93,6 +93,9 @@ def test_add_officer_who_is_already_portfolio_member():
assert member.user == owner and member.role_name == "owner" assert member.user == owner and member.role_name == "owner"
from atst.domain.roles import Roles, _VIEW_PORTFOLIO_PERMISSION_SETS
def test_task_order_access(): def test_task_order_access():
creator = UserFactory.create() creator = UserFactory.create()
member = UserFactory.create() member = UserFactory.create()
@ -111,19 +114,25 @@ def test_task_order_access():
portfolio = PortfolioFactory.create(owner=creator) portfolio = PortfolioFactory.create(owner=creator)
task_order = TaskOrderFactory.create(creator=creator, portfolio=portfolio) task_order = TaskOrderFactory.create(creator=creator, portfolio=portfolio)
PortfolioRoleFactory.create(user=member, portfolio=task_order.portfolio) PortfolioRoleFactory.create(
user=member,
portfolio=task_order.portfolio,
permission_sets=[
Roles.get(prms["name"]) for prms in _VIEW_PORTFOLIO_PERMISSION_SETS
],
)
TaskOrders.add_officer( TaskOrders.add_officer(
creator, task_order, "contracting_officer", officer.to_dictionary() creator, task_order, "contracting_officer", officer.to_dictionary()
) )
check_access([creator, officer], [member, rando], "get", [task_order.id]) check_access([creator, officer, member], [rando], "get", [task_order.id])
check_access([creator], [officer, member, rando], "create", [portfolio]) check_access([creator, officer], [member, rando], "create", [portfolio])
check_access([creator, officer], [member, rando], "update", [task_order]) check_access([creator, officer], [member, rando], "update", [task_order])
check_access( check_access(
[creator], [creator, officer],
[officer, member, rando], [member, rando],
"add_officer", "add_officer",
[task_order, "contracting_officer", rando.to_dictionary()], [task_order, "contracting_officer", UserFactory.dictionary()],
) )

View File

@ -14,7 +14,13 @@ from atst.models.task_order import TaskOrder
from atst.models.user import User from atst.models.user import User
from atst.models.role import Role from atst.models.role import Role
from atst.models.portfolio import Portfolio from atst.models.portfolio import Portfolio
from atst.domain.roles import Roles, PORTFOLIO_ROLES from atst.domain.roles import (
Roles,
PORTFOLIO_ROLES,
PORTFOLIO_PERMISSION_SETS,
_VIEW_PORTFOLIO_PERMISSION_SETS,
_EDIT_PORTFOLIO_PERMISSION_SETS,
)
from atst.models.portfolio_role import PortfolioRole, Status as PortfolioRoleStatus from atst.models.portfolio_role import PortfolioRole, Status as PortfolioRoleStatus
from atst.models.environment_role import EnvironmentRole from atst.models.environment_role import EnvironmentRole
from atst.models.invitation import Invitation, Status as InvitationStatus from atst.models.invitation import Invitation, Status as InvitationStatus
@ -68,6 +74,14 @@ def random_portfolio_role():
return Roles.get(choice["name"]) return Roles.get(choice["name"])
def base_portfolio_permission_sets():
return [Roles.get(prms["name"]) for prms in _VIEW_PORTFOLIO_PERMISSION_SETS]
def get_all_portfolio_permission_sets():
return [Roles.get(prms["name"]) for prms in PORTFOLIO_PERMISSION_SETS]
class Base(factory.alchemy.SQLAlchemyModelFactory): class Base(factory.alchemy.SQLAlchemyModelFactory):
@classmethod @classmethod
def dictionary(cls, **attrs): def dictionary(cls, **attrs):
@ -124,16 +138,27 @@ class PortfolioFactory(Base):
role=Roles.get("owner"), role=Roles.get("owner"),
user=owner, user=owner,
status=PortfolioRoleStatus.ACTIVE, status=PortfolioRoleStatus.ACTIVE,
permission_sets=get_all_portfolio_permission_sets(),
) )
for member in members: for member in members:
user = member.get("user", UserFactory.create()) user = member.get("user", UserFactory.create())
role_name = member["role_name"] role_name = member["role_name"]
perms_set = None
if member.get("permissions_sets"):
perms_set = [
Roles.get(perm_set) for perm_set in member.get("permission_sets")
]
else:
perms_set = []
PortfolioRoleFactory.create( PortfolioRoleFactory.create(
portfolio=portfolio, portfolio=portfolio,
role=Roles.get(role_name), role=Roles.get(role_name),
user=user, user=user,
status=PortfolioRoleStatus.ACTIVE, status=PortfolioRoleStatus.ACTIVE,
permission_sets=perms_set,
) )
portfolio.applications = applications portfolio.applications = applications
@ -189,6 +214,7 @@ class PortfolioRoleFactory(Base):
role = factory.LazyFunction(random_portfolio_role) role = factory.LazyFunction(random_portfolio_role)
user = factory.SubFactory(UserFactory) user = factory.SubFactory(UserFactory)
status = PortfolioRoleStatus.PENDING status = PortfolioRoleStatus.PENDING
permission_sets = factory.LazyFunction(base_portfolio_permission_sets)
class EnvironmentRoleFactory(Base): class EnvironmentRoleFactory(Base):

View File

@ -22,19 +22,23 @@ def create_portfolio_and_invite_user(
ws_status=PortfolioRoleStatus.PENDING, ws_status=PortfolioRoleStatus.PENDING,
invite_status=InvitationStatus.PENDING, invite_status=InvitationStatus.PENDING,
): ):
portfolio = PortfolioFactory.create() owner = UserFactory.create()
portfolio = PortfolioFactory.create(owner=owner)
if ws_role != "owner": if ws_role != "owner":
user = UserFactory.create() user = UserFactory.create()
member = PortfolioRoleFactory.create( member = PortfolioRoleFactory.create(
user=user, portfolio=portfolio, status=ws_status user=user, portfolio=portfolio, status=ws_status
) )
InvitationFactory.create( InvitationFactory.create(
user=portfolio.owner, inviter=portfolio.owner,
user=user,
portfolio_role=member, portfolio_role=member,
email=member.user.email, email=member.user.email,
status=invite_status, status=invite_status,
) )
return portfolio return (portfolio, member)
else:
return (portfolio, portfolio.members[0])
def test_user_with_permission_has_add_member_link(client, user_session): def test_user_with_permission_has_add_member_link(client, user_session):
@ -248,11 +252,10 @@ def test_does_not_show_any_buttons_if_owner(client, user_session):
def test_only_shows_revoke_access_button_if_active(client, user_session): def test_only_shows_revoke_access_button_if_active(client, user_session):
portfolio = create_portfolio_and_invite_user( portfolio, member = create_portfolio_and_invite_user(
ws_status=PortfolioRoleStatus.ACTIVE, invite_status=InvitationStatus.ACCEPTED ws_status=PortfolioRoleStatus.ACTIVE, invite_status=InvitationStatus.ACCEPTED
) )
user_session(portfolio.owner) user_session(portfolio.owner)
member = portfolio.members[1]
response = client.get( response = client.get(
url_for( url_for(
"portfolios.view_member", "portfolios.view_member",
@ -266,11 +269,11 @@ def test_only_shows_revoke_access_button_if_active(client, user_session):
def test_only_shows_revoke_invite_button_if_pending(client, user_session): def test_only_shows_revoke_invite_button_if_pending(client, user_session):
portfolio = create_portfolio_and_invite_user( portfolio, member = create_portfolio_and_invite_user(
ws_status=PortfolioRoleStatus.PENDING, invite_status=InvitationStatus.PENDING ws_status=PortfolioRoleStatus.PENDING, invite_status=InvitationStatus.PENDING
) )
user_session(portfolio.owner) user_session(portfolio.owner)
member = portfolio.members[1] # member = next((memb for memb in portfolio.members if memb != portfolio.owner), None)
response = client.get( response = client.get(
url_for( url_for(
"portfolios.view_member", "portfolios.view_member",
@ -284,12 +287,11 @@ def test_only_shows_revoke_invite_button_if_pending(client, user_session):
def test_only_shows_resend_button_if_expired(client, user_session): def test_only_shows_resend_button_if_expired(client, user_session):
portfolio = create_portfolio_and_invite_user( portfolio, member = create_portfolio_and_invite_user(
ws_status=PortfolioRoleStatus.PENDING, ws_status=PortfolioRoleStatus.PENDING,
invite_status=InvitationStatus.REJECTED_EXPIRED, invite_status=InvitationStatus.REJECTED_EXPIRED,
) )
user_session(portfolio.owner) user_session(portfolio.owner)
member = portfolio.members[1]
response = client.get( response = client.get(
url_for( url_for(
"portfolios.view_member", "portfolios.view_member",
@ -303,11 +305,10 @@ def test_only_shows_resend_button_if_expired(client, user_session):
def test_only_shows_resend_button_if_revoked(client, user_session): def test_only_shows_resend_button_if_revoked(client, user_session):
portfolio = create_portfolio_and_invite_user( portfolio, member = create_portfolio_and_invite_user(
ws_status=PortfolioRoleStatus.PENDING, invite_status=InvitationStatus.REVOKED ws_status=PortfolioRoleStatus.PENDING, invite_status=InvitationStatus.REVOKED
) )
user_session(portfolio.owner) user_session(portfolio.owner)
member = portfolio.members[1]
response = client.get( response = client.get(
url_for( url_for(
"portfolios.view_member", "portfolios.view_member",