Merge pull request #701 from dod-ccpo/portfolio-perms

Portfolio perms
This commit is contained in:
dandds 2019-03-18 08:40:35 -04:00 committed by GitHub
commit 13bc7f56b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 940 additions and 738 deletions

View File

@ -0,0 +1,30 @@
"""remove portfolio_roles role_id
Revision ID: 0e71ab219ada
Revises: 938a31795096
Create Date: 2019-03-12 05:58:17.029899
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '0e71ab219ada'
down_revision = '938a31795096'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('workspace_roles_role_id_fkey', 'portfolio_roles', type_='foreignkey')
op.drop_column('portfolio_roles', 'role_id')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('portfolio_roles', sa.Column('role_id', postgresql.UUID(), autoincrement=False, nullable=True))
op.create_foreign_key('workspace_roles_role_id_fkey', 'portfolio_roles', 'roles', ['role_id'], ['id'])
# ### end Alembic commands ###

View File

@ -0,0 +1,33 @@
"""add permission sets to portfolio_role
Revision ID: 938a31795096
Revises: db161adbafdf
Create Date: 2019-03-07 06:13:05.400911
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '938a31795096'
down_revision = 'db161adbafdf'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('portfolio_roles_roles',
sa.Column('portfolio_role_id', postgresql.UUID(as_uuid=True), nullable=True),
sa.Column('role_id', postgresql.UUID(as_uuid=True), nullable=True),
sa.ForeignKeyConstraint(['portfolio_role_id'], ['portfolio_roles.id'], ),
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], )
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('portfolio_roles_roles')
# ### end Alembic commands ###

View File

@ -0,0 +1,28 @@
"""rename roles table to permission_sets
Revision ID: a19138e386c4
Revises: 0e71ab219ada
Create Date: 2019-03-13 10:18:35.770296
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'a19138e386c4'
down_revision = '0e71ab219ada'
branch_labels = None
depends_on = None
def upgrade():
op.rename_table("roles", "permission_sets")
op.rename_table("portfolio_roles_roles", "portfolio_roles_permission_sets")
op.alter_column("portfolio_roles_permission_sets", "role_id", new_column_name="permission_set_id")
def downgrade():
op.rename_table("permission_sets", "roles")
op.alter_column("portfolio_roles_permission_sets", "permission_set_id", new_column_name="role_id")
op.rename_table("portfolio_roles_permission_sets", "portfolio_roles_roles")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,169 @@
from sqlalchemy.orm.exc import NoResultFound
from atst.database import db
from atst.models import PermissionSet, Permissions
from .exceptions import NotFoundError
class PermissionSets(object):
VIEW_PORTFOLIO = "view_portfolio"
VIEW_PORTFOLIO_APPLICATION_MANAGEMENT = "view_portfolio_application_management"
VIEW_PORTFOLIO_FUNDING = "view_portfolio_funding"
VIEW_PORTFOLIO_REPORTS = "view_portfolio_reports"
VIEW_PORTFOLIO_ADMIN = "view_portfolio_admin"
EDIT_PORTFOLIO_APPLICATION_MANAGEMENT = "edit_portfolio_application_management"
EDIT_PORTFOLIO_FUNDING = "edit_portfolio_funding"
EDIT_PORTFOLIO_REPORTS = "edit_portfolio_reports"
EDIT_PORTFOLIO_ADMIN = "edit_portfolio_admin"
PORTFOLIO_POC = "portfolio_poc"
@classmethod
def get(cls, perms_set_name):
try:
role = db.session.query(PermissionSet).filter_by(name=perms_set_name).one()
except NoResultFound:
raise NotFoundError("permission_set")
return role
@classmethod
def get_all(cls):
return db.session.query(PermissionSet).all()
@classmethod
def get_many(cls, perms_set_names):
return (
db.session.query(PermissionSet)
.filter(PermissionSet.name.in_(perms_set_names))
.all()
)
ATAT_ROLES = [
{
"name": "ccpo",
"display_name": "CCPO",
"description": "",
"permissions": [Permissions.VIEW_AUDIT_LOG],
},
{
"name": "default",
"display_name": "Default",
"description": "",
"permissions": [],
},
]
_PORTFOLIO_BASIC_PERMISSION_SETS = [
{
"name": PermissionSets.VIEW_PORTFOLIO,
"description": "View basic portfolio info",
"display_name": "View Portfolio",
"permissions": [Permissions.VIEW_PORTFOLIO],
}
]
_PORTFOLIO_APP_MGMT_PERMISSION_SETS = [
{
"name": PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
"description": "View applications and related resources",
"display_name": "Application Management",
"permissions": [
Permissions.VIEW_APPLICATION,
Permissions.VIEW_APPLICATION_MEMBER,
Permissions.VIEW_ENVIRONMENT,
],
},
{
"name": PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT,
"description": "Edit applications and related resources",
"display_name": "Application Management",
"permissions": [
Permissions.EDIT_APPLICATION,
Permissions.CREATE_APPLICATION,
Permissions.EDIT_APPLICATION_MEMBER,
Permissions.CREATE_APPLICATION_MEMBER,
Permissions.EDIT_ENVIRONMENT,
Permissions.CREATE_ENVIRONMENT,
],
},
]
_PORTFOLIO_FUNDING_PERMISSION_SETS = [
{
"name": PermissionSets.VIEW_PORTFOLIO_FUNDING,
"description": "View a portfolio's task orders",
"display_name": "Funding",
"permissions": [
Permissions.VIEW_PORTFOLIO_FUNDING,
Permissions.VIEW_TASK_ORDER_DETAILS,
],
},
{
"name": PermissionSets.EDIT_PORTFOLIO_FUNDING,
"description": "Edit a portfolio's task orders and add new ones",
"display_name": "Funding",
"permissions": [
Permissions.CREATE_TASK_ORDER,
Permissions.EDIT_TASK_ORDER_DETAILS,
],
},
]
_PORTFOLIO_REPORTS_PERMISSION_SETS = [
{
"name": PermissionSets.VIEW_PORTFOLIO_REPORTS,
"description": "View a portfolio's reports",
"display_name": "Reporting",
"permissions": [Permissions.VIEW_PORTFOLIO_REPORTS],
},
{
"name": PermissionSets.EDIT_PORTFOLIO_REPORTS,
"description": "Edit a portfolio's reports (no-op)",
"display_name": "Reporting",
"permissions": [],
},
]
_PORTFOLIO_ADMIN_PERMISSION_SETS = [
{
"name": PermissionSets.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": PermissionSets.EDIT_PORTFOLIO_ADMIN,
"description": "Edit a portfolio's admin options",
"display_name": "Portfolio Administration",
"permissions": [
Permissions.EDIT_PORTFOLIO_NAME,
Permissions.EDIT_PORTFOLIO_USERS,
Permissions.CREATE_PORTFOLIO_USERS,
],
},
]
_PORTFOLIO_POC_PERMISSION_SETS = [
{
"name": "portfolio_poc",
"description": "Permissions belonging to the Portfolio POC",
"display_name": "Portfolio Point of Contact",
"permissions": [Permissions.EDIT_PORTFOLIO_POC, Permissions.ARCHIVE_PORTFOLIO],
}
]
PORTFOLIO_PERMISSION_SETS = (
_PORTFOLIO_BASIC_PERMISSION_SETS
+ _PORTFOLIO_APP_MGMT_PERMISSION_SETS
+ _PORTFOLIO_FUNDING_PERMISSION_SETS
+ _PORTFOLIO_REPORTS_PERMISSION_SETS
+ _PORTFOLIO_ADMIN_PERMISSION_SETS
+ _PORTFOLIO_POC_PERMISSION_SETS
)

View File

@ -8,8 +8,7 @@ from atst.models.portfolio_role import (
)
from atst.models.user import User
from .roles import Roles
from .users import Users
from .permission_sets import PermissionSets
from .exceptions import NotFoundError
@ -53,17 +52,6 @@ class PortfolioRoles(object):
except NoResultFound:
return None
@classmethod
def portfolio_role_permissions(cls, portfolio, user):
portfolio_role = PortfolioRoles._get_active_portfolio_role(
portfolio.id, user.id
)
atat_permissions = set(user.atat_role.permissions)
portfolio_permissions = (
[] if portfolio_role is None else portfolio_role.role.permissions
)
return set(portfolio_permissions).union(atat_permissions)
@classmethod
def _get_portfolio_role(cls, user, portfolio_id):
try:
@ -80,9 +68,7 @@ class PortfolioRoles(object):
raise NotFoundError("portfolio role")
@classmethod
def add(cls, user, portfolio_id, role_name):
role = Roles.get(role_name)
def add(cls, user, portfolio_id, permission_sets=None):
new_portfolio_role = None
try:
existing_portfolio_role = (
@ -94,13 +80,14 @@ class PortfolioRoles(object):
.one()
)
new_portfolio_role = existing_portfolio_role
new_portfolio_role.role = role
except NoResultFound:
new_portfolio_role = PortfolioRole(
user=user,
role_id=role.id,
portfolio_id=portfolio_id,
status=PortfolioRoleStatus.PENDING,
user=user, portfolio_id=portfolio_id, status=PortfolioRoleStatus.PENDING
)
if permission_sets:
new_portfolio_role.permission_sets = PortfolioRoles._permission_sets_for_names(
permission_sets
)
user.portfolio_roles.append(new_portfolio_role)
@ -109,56 +96,41 @@ class PortfolioRoles(object):
return new_portfolio_role
DEFAULT_PORTFOLIO_PERMISSION_SETS = {
PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
PermissionSets.VIEW_PORTFOLIO_FUNDING,
PermissionSets.VIEW_PORTFOLIO_REPORTS,
PermissionSets.VIEW_PORTFOLIO_ADMIN,
PermissionSets.VIEW_PORTFOLIO,
}
PORTFOLIO_PERMISSION_SETS = DEFAULT_PORTFOLIO_PERMISSION_SETS.union(
{
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT,
PermissionSets.EDIT_PORTFOLIO_FUNDING,
PermissionSets.EDIT_PORTFOLIO_REPORTS,
PermissionSets.EDIT_PORTFOLIO_ADMIN,
PermissionSets.PORTFOLIO_POC,
}
)
@classmethod
def update_role(cls, portfolio_role, role_name):
new_role = Roles.get(role_name)
portfolio_role.role = new_role
def _permission_sets_for_names(cls, set_names):
perms_set_names = PortfolioRoles.DEFAULT_PORTFOLIO_PERMISSION_SETS.union(
set(set_names)
)
return PermissionSets.get_many(perms_set_names)
@classmethod
def update(cls, portfolio_role, set_names):
new_permission_sets = PortfolioRoles._permission_sets_for_names(set_names)
portfolio_role.permission_sets = new_permission_sets
db.session.add(portfolio_role)
db.session.commit()
return portfolio_role
@classmethod
def add_many(cls, portfolio_id, portfolio_role_dicts):
portfolio_roles = []
for user_dict in portfolio_role_dicts:
try:
user = Users.get(user_dict["id"])
except NoResultFound:
default_role = Roles.get("developer")
user = User(id=user_dict["id"], atat_role=default_role)
try:
role = Roles.get(user_dict["portfolio_role"])
except NoResultFound:
raise NotFoundError("role")
try:
existing_portfolio_role = (
db.session.query(PortfolioRole)
.filter(
PortfolioRole.user == user,
PortfolioRole.portfolio_id == portfolio_id,
)
.one()
)
new_portfolio_role = existing_portfolio_role
new_portfolio_role.role = role
except NoResultFound:
new_portfolio_role = PortfolioRole(
user=user, role_id=role.id, portfolio_id=portfolio_id
)
user.portfolio_roles.append(new_portfolio_role)
portfolio_roles.append(new_portfolio_role)
db.session.add(user)
db.session.commit()
return portfolio_roles
@classmethod
def enable(cls, portfolio_role):
portfolio_role.status = PortfolioRoleStatus.ACTIVE

View File

@ -1,4 +1,4 @@
from atst.domain.roles import Roles
from atst.domain.permission_sets import PermissionSets
from atst.domain.authz import Authorization
from atst.models.permissions import Permissions
from atst.domain.users import Users
@ -20,8 +20,12 @@ class Portfolios(object):
portfolio = PortfoliosQuery.create(
name=name, defense_component=defense_component
)
perms_sets = PermissionSets.get_many(PortfolioRoles.PORTFOLIO_PERMISSION_SETS)
Portfolios._create_portfolio_role(
user, portfolio, "owner", status=PortfolioRoleStatus.ACTIVE
user,
portfolio,
status=PortfolioRoleStatus.ACTIVE,
permission_sets=perms_sets,
)
PortfoliosQuery.add_and_commit(portfolio)
return portfolio
@ -39,7 +43,7 @@ class Portfolios(object):
def get_for_update_applications(cls, user, portfolio_id):
portfolio = PortfoliosQuery.get(portfolio_id)
Authorization.check_portfolio_permission(
user, portfolio, Permissions.ADD_APPLICATION_IN_PORTFOLIO, "add application"
user, portfolio, Permissions.CREATE_APPLICATION, "add application"
)
return portfolio
@ -50,7 +54,7 @@ class Portfolios(object):
Authorization.check_portfolio_permission(
user,
portfolio,
Permissions.EDIT_PORTFOLIO_INFORMATION,
Permissions.EDIT_PORTFOLIO_NAME,
"update portfolio information",
)
@ -62,7 +66,7 @@ class Portfolios(object):
Authorization.check_portfolio_permission(
user,
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
Permissions.EDIT_PORTFOLIO_USERS,
"update a portfolio member",
)
@ -72,10 +76,7 @@ class Portfolios(object):
def get_with_members(cls, user, portfolio_id):
portfolio = PortfoliosQuery.get(portfolio_id)
Authorization.check_portfolio_permission(
user,
portfolio,
Permissions.VIEW_PORTFOLIO_MEMBERS,
"view portfolio members",
user, portfolio, Permissions.VIEW_PORTFOLIO_USERS, "view portfolio members"
)
return portfolio
@ -91,10 +92,7 @@ class Portfolios(object):
@classmethod
def create_member(cls, user, portfolio, data):
Authorization.check_portfolio_permission(
user,
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
"create portfolio member",
user, portfolio, Permissions.EDIT_PORTFOLIO_USERS, "create portfolio member"
)
new_user = Users.get_or_create_by_dod_id(
@ -105,31 +103,34 @@ class Portfolios(object):
atat_role_name="default",
provisional=True,
)
return Portfolios.add_member(portfolio, new_user, data["portfolio_role"])
permission_sets = data.get("permission_sets", [])
return Portfolios.add_member(
portfolio, new_user, permission_sets=permission_sets
)
@classmethod
def add_member(cls, portfolio, member, role_name):
portfolio_role = PortfolioRoles.add(member, portfolio.id, role_name)
def add_member(cls, portfolio, member, permission_sets=None):
portfolio_role = PortfolioRoles.add(member, portfolio.id, permission_sets)
return portfolio_role
@classmethod
def update_member(cls, user, portfolio, member, role_name):
def update_member(cls, user, portfolio, member, permission_sets):
Authorization.check_portfolio_permission(
user,
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
"edit portfolio member",
user, portfolio, Permissions.EDIT_PORTFOLIO_USERS, "edit portfolio member"
)
return PortfolioRoles.update_role(member, role_name)
# need to update perms sets here
return PortfolioRoles.update(member, permission_sets)
@classmethod
def _create_portfolio_role(
cls, user, portfolio, role_name, status=PortfolioRoleStatus.PENDING
cls, user, portfolio, status=PortfolioRoleStatus.PENDING, permission_sets=None
):
role = Roles.get(role_name)
if permission_sets is None:
permission_sets = []
portfolio_role = PortfoliosQuery.create_portfolio_role(
user, role, portfolio, status=status
user, portfolio, status=status, permission_sets=permission_sets
)
PortfoliosQuery.add_and_commit(portfolio_role)
return portfolio_role
@ -152,10 +153,7 @@ class Portfolios(object):
def revoke_access(cls, user, portfolio_id, portfolio_role_id):
portfolio = PortfoliosQuery.get(portfolio_id)
Authorization.check_portfolio_permission(
user,
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
"revoke portfolio access",
user, portfolio, Permissions.EDIT_PORTFOLIO_USERS, "revoke portfolio access"
)
portfolio_role = PortfolioRoles.get_by_id(portfolio_role_id)

View File

@ -18,5 +18,5 @@ class PortfoliosQuery(Query):
)
@classmethod
def create_portfolio_role(cls, user, role, portfolio, **kwargs):
return PortfolioRole(user=user, role=role, portfolio=portfolio, **kwargs)
def create_portfolio_role(cls, user, portfolio, **kwargs):
return PortfolioRole(user=user, portfolio=portfolio, **kwargs)

View File

@ -31,7 +31,7 @@ class ScopedPortfolio(ScopedResource):
@property
def applications(self):
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:
@ -54,9 +54,7 @@ class ScopedApplication(ScopedResource):
@property
def environments(self):
can_view_all_environments = Authorization.has_portfolio_permission(
self.user,
self.resource.portfolio,
Permissions.VIEW_ENVIRONMENT_IN_APPLICATION,
self.user, self.resource.portfolio, Permissions.VIEW_ENVIRONMENT
)
if can_view_all_environments:

View File

@ -1,177 +0,0 @@
from sqlalchemy.orm.exc import NoResultFound
from atst.database import db
from atst.models import Role, Permissions
from .exceptions import NotFoundError
ATAT_ROLES = [
{
"name": "ccpo",
"display_name": "CCPO",
"description": "",
"permissions": [
Permissions.VIEW_ORIGINAL_JEDI_REQEUST,
Permissions.REVIEW_AND_APPROVE_JEDI_PORTFOLIO_REQUEST,
Permissions.MODIFY_ATAT_ROLE_PERMISSIONS,
Permissions.CREATE_CSP_ROLE,
Permissions.DELETE_CSP_ROLE,
Permissions.DEACTIVE_CSP_ROLE,
Permissions.MODIFY_CSP_ROLE_PERMISSIONS,
Permissions.VIEW_USAGE_REPORT,
Permissions.VIEW_USAGE_DOLLARS,
Permissions.ADD_AND_ASSIGN_CSP_ROLES,
Permissions.REMOVE_CSP_ROLES,
Permissions.REQUEST_NEW_CSP_ROLE,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
Permissions.VIEW_ASSIGNED_ATAT_ROLE_CONFIGURATIONS,
Permissions.VIEW_ASSIGNED_CSP_ROLE_CONFIGURATIONS,
Permissions.DEACTIVATE_PORTFOLIO,
Permissions.VIEW_ATAT_PERMISSIONS,
Permissions.TRANSFER_OWNERSHIP_OF_PORTFOLIO,
Permissions.VIEW_PORTFOLIO,
Permissions.VIEW_PORTFOLIO_MEMBERS,
Permissions.ADD_APPLICATION_IN_PORTFOLIO,
Permissions.DELETE_APPLICATION_IN_PORTFOLIO,
Permissions.DEACTIVATE_APPLICATION_IN_PORTFOLIO,
Permissions.VIEW_APPLICATION_IN_PORTFOLIO,
Permissions.RENAME_APPLICATION_IN_PORTFOLIO,
Permissions.ADD_ENVIRONMENT_IN_APPLICATION,
Permissions.DELETE_ENVIRONMENT_IN_APPLICATION,
Permissions.DEACTIVATE_ENVIRONMENT_IN_APPLICATION,
Permissions.VIEW_ENVIRONMENT_IN_APPLICATION,
Permissions.RENAME_ENVIRONMENT_IN_APPLICATION,
Permissions.ADD_TAG_TO_PORTFOLIO,
Permissions.REMOVE_TAG_FROM_PORTFOLIO,
Permissions.VIEW_AUDIT_LOG,
Permissions.VIEW_PORTFOLIO_AUDIT_LOG,
],
},
{
"name": "default",
"display_name": "Default",
"description": "",
"permissions": [Permissions.REQUEST_JEDI_PORTFOLIO],
},
]
PORTFOLIO_ROLES = [
{
"name": "owner",
"display_name": "Portfolio Owner",
"description": "Adds, edits, deactivates access to all applications, environments, and members. Views budget reports. Initiates and edits JEDI Cloud requests.",
"permissions": [
Permissions.REQUEST_JEDI_PORTFOLIO,
Permissions.VIEW_ORIGINAL_JEDI_REQEUST,
Permissions.VIEW_USAGE_REPORT,
Permissions.VIEW_USAGE_DOLLARS,
Permissions.ADD_AND_ASSIGN_CSP_ROLES,
Permissions.REMOVE_CSP_ROLES,
Permissions.REQUEST_NEW_CSP_ROLE,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
Permissions.VIEW_ASSIGNED_ATAT_ROLE_CONFIGURATIONS,
Permissions.VIEW_ASSIGNED_CSP_ROLE_CONFIGURATIONS,
Permissions.DEACTIVATE_PORTFOLIO,
Permissions.VIEW_ATAT_PERMISSIONS,
Permissions.VIEW_PORTFOLIO,
Permissions.VIEW_PORTFOLIO_MEMBERS,
Permissions.EDIT_PORTFOLIO_INFORMATION,
Permissions.ADD_APPLICATION_IN_PORTFOLIO,
Permissions.DELETE_APPLICATION_IN_PORTFOLIO,
Permissions.DEACTIVATE_APPLICATION_IN_PORTFOLIO,
Permissions.VIEW_APPLICATION_IN_PORTFOLIO,
Permissions.RENAME_APPLICATION_IN_PORTFOLIO,
Permissions.ADD_ENVIRONMENT_IN_APPLICATION,
Permissions.DELETE_ENVIRONMENT_IN_APPLICATION,
Permissions.DEACTIVATE_ENVIRONMENT_IN_APPLICATION,
Permissions.VIEW_ENVIRONMENT_IN_APPLICATION,
Permissions.RENAME_ENVIRONMENT_IN_APPLICATION,
Permissions.VIEW_PORTFOLIO_AUDIT_LOG,
Permissions.VIEW_TASK_ORDER,
Permissions.UPDATE_TASK_ORDER,
Permissions.ADD_TASK_ORDER_OFFICER,
],
},
{
"name": "admin",
"display_name": "Administrator",
"description": "Adds and edits applications, environments, members, but cannot deactivate. Cannot view budget reports or JEDI Cloud requests.",
"permissions": [
Permissions.VIEW_USAGE_REPORT,
Permissions.ADD_AND_ASSIGN_CSP_ROLES,
Permissions.REMOVE_CSP_ROLES,
Permissions.REQUEST_NEW_CSP_ROLE,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
Permissions.VIEW_ASSIGNED_ATAT_ROLE_CONFIGURATIONS,
Permissions.VIEW_ASSIGNED_CSP_ROLE_CONFIGURATIONS,
Permissions.VIEW_PORTFOLIO,
Permissions.VIEW_PORTFOLIO_MEMBERS,
Permissions.EDIT_PORTFOLIO_INFORMATION,
Permissions.ADD_APPLICATION_IN_PORTFOLIO,
Permissions.DELETE_APPLICATION_IN_PORTFOLIO,
Permissions.DEACTIVATE_APPLICATION_IN_PORTFOLIO,
Permissions.VIEW_APPLICATION_IN_PORTFOLIO,
Permissions.RENAME_APPLICATION_IN_PORTFOLIO,
Permissions.ADD_ENVIRONMENT_IN_APPLICATION,
Permissions.DELETE_ENVIRONMENT_IN_APPLICATION,
Permissions.DEACTIVATE_ENVIRONMENT_IN_APPLICATION,
Permissions.VIEW_ENVIRONMENT_IN_APPLICATION,
Permissions.RENAME_ENVIRONMENT_IN_APPLICATION,
Permissions.VIEW_PORTFOLIO_AUDIT_LOG,
Permissions.VIEW_TASK_ORDER,
Permissions.UPDATE_TASK_ORDER,
Permissions.ADD_TASK_ORDER_OFFICER,
],
},
{
"name": "developer",
"display_name": "Developer",
"description": "Views only the applications and environments they are granted access to. Can also view members associated with each environment.",
"permissions": [Permissions.VIEW_USAGE_REPORT, Permissions.VIEW_PORTFOLIO],
},
{
"name": "billing_auditor",
"display_name": "Billing Auditor",
"description": "Views only the applications and environments they are granted access to. Can also view budgets and reports associated with the portfolio.",
"permissions": [
Permissions.VIEW_USAGE_REPORT,
Permissions.VIEW_USAGE_DOLLARS,
Permissions.VIEW_PORTFOLIO,
],
},
{
"name": "security_auditor",
"description": "Views only the applications and environments they are granted access to. Can also view activity logs.",
"display_name": "Security Auditor",
"permissions": [
Permissions.VIEW_ASSIGNED_ATAT_ROLE_CONFIGURATIONS,
Permissions.VIEW_ASSIGNED_CSP_ROLE_CONFIGURATIONS,
Permissions.VIEW_ATAT_PERMISSIONS,
Permissions.VIEW_PORTFOLIO,
],
},
{
"name": "officer",
"description": "Officer involved with setting up a Task Order",
"display_name": "Task Order Officer",
"permissions": [
Permissions.VIEW_PORTFOLIO,
Permissions.VIEW_USAGE_REPORT,
Permissions.VIEW_USAGE_DOLLARS,
],
},
]
class Roles(object):
@classmethod
def get(cls, role_name):
try:
role = db.session.query(Role).filter_by(name=role_name).one()
except NoResultFound:
raise NotFoundError("role")
return role
@classmethod
def get_all(cls):
return db.session.query(Role).all()

View File

@ -7,6 +7,7 @@ from atst.models.permissions import Permissions
from atst.models.dd_254 import DD254
from atst.domain.portfolios import Portfolios
from atst.domain.authz import Authorization
from atst.domain.permission_sets import PermissionSets
from .exceptions import NotFoundError
@ -57,7 +58,7 @@ class TaskOrders(object):
try:
task_order = db.session.query(TaskOrder).filter_by(id=task_order_id).one()
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
@ -67,7 +68,7 @@ class TaskOrders(object):
@classmethod
def create(cls, creator, portfolio):
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)
@ -79,7 +80,7 @@ class TaskOrders(object):
@classmethod
def update(cls, user, task_order, **kwargs):
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():
@ -150,7 +151,7 @@ class TaskOrders(object):
Authorization.check_portfolio_permission(
user,
task_order.portfolio,
Permissions.ADD_TASK_ORDER_OFFICER,
Permissions.EDIT_TASK_ORDER_DETAILS,
"add task order officer",
)
@ -170,7 +171,12 @@ class TaskOrders(object):
portfolio_user = existing_member.user
else:
member = Portfolios.create_member(
user, portfolio, {**officer_data, "portfolio_role": "officer"}
user,
portfolio,
{
**officer_data,
"permission_sets": [PermissionSets.EDIT_PORTFOLIO_FUNDING],
},
)
portfolio_user = member.user

View File

@ -4,7 +4,7 @@ from sqlalchemy.exc import IntegrityError
from atst.database import db
from atst.models import User
from .roles import Roles
from .permission_sets import PermissionSets
from .exceptions import NotFoundError, AlreadyExistsError, UnauthorizedError
@ -29,7 +29,7 @@ class Users(object):
@classmethod
def create(cls, dod_id, atat_role_name=None, **kwargs):
atat_role = Roles.get(atat_role_name)
atat_role = PermissionSets.get(atat_role_name)
try:
user = User(dod_id=dod_id, atat_role=atat_role, **kwargs)
@ -56,7 +56,7 @@ class Users(object):
def update_role(cls, user_id, atat_role_name):
user = Users.get(user_id)
atat_role = Roles.get(atat_role_name)
atat_role = PermissionSets.get(atat_role_name)
user.atat_role = atat_role
db.session.add(user)

View File

@ -1,4 +1,3 @@
from atst.domain.roles import PORTFOLIO_ROLES as PORTFOLIO_ROLE_DEFINITIONS
from atst.utils.localization import translate, translate_duration
@ -107,12 +106,6 @@ COMPLETION_DATE_RANGES = [
("Above 12 months", "Above 12 months"),
]
PORTFOLIO_ROLES = [
(role["name"], {"name": role["display_name"], "description": role["description"]})
for role in PORTFOLIO_ROLE_DEFINITIONS
if role["name"] is not "officer"
]
ENVIRONMENT_ROLES = [
(
"developer",

View File

@ -1,18 +0,0 @@
from wtforms.validators import Required
from .forms import BaseForm
from atst.forms.fields import SelectField
from atst.utils.localization import translate
from .data import PORTFOLIO_ROLES
class EditMemberForm(BaseForm):
# This form also accepts a field for each environment in each application
# that the user is a member of
portfolio_role = SelectField(
translate("forms.edit_member.portfolio_role_label"),
choices=PORTFOLIO_ROLES,
validators=[Required()],
)

View File

@ -1,34 +0,0 @@
from wtforms.fields import StringField
from wtforms.fields.html5 import EmailField
from wtforms.validators import Required, Email, Length
from .forms import BaseForm
from atst.forms.validators import IsNumber
from atst.forms.fields import SelectField
from atst.utils.localization import translate
from .data import PORTFOLIO_ROLES
class NewMemberForm(BaseForm):
first_name = StringField(
label=translate("forms.new_member.first_name_label"), validators=[Required()]
)
last_name = StringField(
label=translate("forms.new_member.last_name_label"), validators=[Required()]
)
email = EmailField(
translate("forms.new_member.email_label"), validators=[Required(), Email()]
)
dod_id = StringField(
translate("forms.new_member.dod_id_label"),
validators=[Required(), Length(min=10), IsNumber()],
)
portfolio_role = SelectField(
translate("forms.new_member.portfolio_role_label"),
choices=PORTFOLIO_ROLES,
validators=[Required()],
default="",
description=translate("forms.new_member.portfolio_role_description"),
)

View File

@ -0,0 +1,72 @@
from wtforms.fields import StringField
from wtforms.fields.html5 import EmailField
from wtforms.validators import Required, Email, Length
from atst.domain.permission_sets import PermissionSets
from .forms import BaseForm
from atst.forms.validators import IsNumber
from atst.forms.fields import SelectField
from atst.utils.localization import translate
class PermissionsForm(BaseForm):
perms_app_mgmt = SelectField(
None,
choices=[
(PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT, "View Only"),
(PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT, "Edit Access"),
],
)
perms_funding = SelectField(
None,
choices=[
(PermissionSets.VIEW_PORTFOLIO_FUNDING, "View Only"),
(PermissionSets.EDIT_PORTFOLIO_FUNDING, "Edit Access"),
],
)
perms_reporting = SelectField(
None,
choices=[
(PermissionSets.VIEW_PORTFOLIO_REPORTS, "View Only"),
(PermissionSets.EDIT_PORTFOLIO_REPORTS, "Edit Access"),
],
)
perms_portfolio_mgmt = SelectField(
None,
choices=[
(PermissionSets.VIEW_PORTFOLIO_ADMIN, "View Only"),
(PermissionSets.EDIT_PORTFOLIO_ADMIN, "Edit Access"),
],
)
@property
def data(self):
_data = super().data
_data["permission_sets"] = []
for field in _data:
if "perms" in field:
_data["permission_sets"].append(_data[field])
return _data
class EditForm(PermissionsForm):
# This form also accepts a field for each environment in each application
# that the user is a member of
pass
class NewForm(PermissionsForm):
first_name = StringField(
label=translate("forms.new_member.first_name_label"), validators=[Required()]
)
last_name = StringField(
label=translate("forms.new_member.last_name_label"), validators=[Required()]
)
email = EmailField(
translate("forms.new_member.email_label"), validators=[Required(), Email()]
)
dod_id = StringField(
translate("forms.new_member.dod_id_label"),
validators=[Required(), Length(min=10), IsNumber()],
)

View File

@ -3,7 +3,7 @@ from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from .permissions import Permissions
from .role import Role
from .permission_set import PermissionSet
from .user import User
from .portfolio_role import PortfolioRole
from .portfolio import Portfolio

View File

@ -0,0 +1,19 @@
from sqlalchemy import String, Column
from sqlalchemy.dialects.postgresql import ARRAY
from atst.models import Base, types, mixins
class PermissionSet(Base, mixins.TimestampsMixin):
__tablename__ = "permission_sets"
id = types.Id()
name = Column(String, index=True, unique=True, nullable=False)
display_name = Column(String, nullable=False)
description = Column(String, nullable=False)
permissions = Column(ARRAY(String), index=True, server_default="{}", nullable=False)
def __repr__(self):
return "<PermissionSet(name='{}', description='{}', permissions='{}', id='{}')>".format(
self.name, self.description, self.permissions, self.id
)

View File

@ -1,49 +1,41 @@
class Permissions(object):
VIEW_AUDIT_LOG = "view_audit_log"
VIEW_PORTFOLIO_AUDIT_LOG = "view_portfolio_audit_log"
REQUEST_JEDI_PORTFOLIO = "request_jedi_portfolio"
VIEW_ORIGINAL_JEDI_REQEUST = "view_original_jedi_request"
REVIEW_AND_APPROVE_JEDI_PORTFOLIO_REQUEST = (
"review_and_approve_jedi_portfolio_request"
)
MODIFY_ATAT_ROLE_PERMISSIONS = "modify_atat_role_permissions"
CREATE_CSP_ROLE = "create_csp_role"
DELETE_CSP_ROLE = "delete_csp_role"
DEACTIVE_CSP_ROLE = "deactivate_csp_role"
MODIFY_CSP_ROLE_PERMISSIONS = "modify_csp_role_permissions"
VIEW_USAGE_REPORT = "view_usage_report"
VIEW_USAGE_DOLLARS = "view_usage_dollars"
ADD_AND_ASSIGN_CSP_ROLES = "add_and_assign_csp_roles"
REMOVE_CSP_ROLES = "remove_csp_roles"
REQUEST_NEW_CSP_ROLE = "request_new_csp_role"
ASSIGN_AND_UNASSIGN_ATAT_ROLE = "assign_and_unassign_atat_role"
VIEW_ASSIGNED_ATAT_ROLE_CONFIGURATIONS = "view_assigned_atat_role_configurations"
VIEW_ASSIGNED_CSP_ROLE_CONFIGURATIONS = "view_assigned_csp_role_configurations"
EDIT_PORTFOLIO_INFORMATION = "edit_portfolio_information"
DEACTIVATE_PORTFOLIO = "deactivate_portfolio"
VIEW_ATAT_PERMISSIONS = "view_atat_permissions"
TRANSFER_OWNERSHIP_OF_PORTFOLIO = "transfer_ownership_of_portfolio"
VIEW_PORTFOLIO_MEMBERS = "view_portfolio_members"
# base portfolio perms
VIEW_PORTFOLIO = "view_portfolio"
ADD_APPLICATION_IN_PORTFOLIO = "add_application_in_portfolio"
DELETE_APPLICATION_IN_PORTFOLIO = "delete_application_in_portfolio"
DEACTIVATE_APPLICATION_IN_PORTFOLIO = "deactivate_application_in_portfolio"
VIEW_APPLICATION_IN_PORTFOLIO = "view_application_in_portfolio"
RENAME_APPLICATION_IN_PORTFOLIO = "rename_application_in_portfolio"
# application management
VIEW_APPLICATION = "view_application"
EDIT_APPLICATION = "edit_application"
CREATE_APPLICATION = "create_application"
VIEW_APPLICATION_MEMBER = "view_application_member"
EDIT_APPLICATION_MEMBER = "edit_application_member"
CREATE_APPLICATION_MEMBER = "create_application_member"
VIEW_ENVIRONMENT = "view_environment"
EDIT_ENVIRONMENT = "edit_environment"
CREATE_ENVIRONMENT = "create_environment"
ADD_ENVIRONMENT_IN_APPLICATION = "add_environment_in_application"
DELETE_ENVIRONMENT_IN_APPLICATION = "delete_environment_in_application"
DEACTIVATE_ENVIRONMENT_IN_APPLICATION = "deactivate_environment_in_application"
VIEW_ENVIRONMENT_IN_APPLICATION = "view_environment_in_application"
RENAME_ENVIRONMENT_IN_APPLICATION = "rename_environment_in_application"
# funding
VIEW_PORTFOLIO_FUNDING = "view_portfolio_funding" # TO summary page
CREATE_TASK_ORDER = "create_task_order" # create a new TO
VIEW_TASK_ORDER_DETAILS = "view_task_order_details" # individual TO page
EDIT_TASK_ORDER_DETAILS = (
"edit_task_order_details"
) # edit TO that has not been finalized
ADD_TAG_TO_PORTFOLIO = "add_tag_to_portfolio"
REMOVE_TAG_FROM_PORTFOLIO = "remove_tag_from_portfolio"
# reporting
VIEW_PORTFOLIO_REPORTS = "view_portfolio_reports"
VIEW_TASK_ORDER = "view_task_order"
UPDATE_TASK_ORDER = "update_task_order"
ADD_TASK_ORDER_OFFICER = "add_task_order_officers"
# portfolio admin
VIEW_PORTFOLIO_ADMIN = "view_portfolio_admin"
VIEW_PORTFOLIO_NAME = "view_portfolio_name"
EDIT_PORTFOLIO_NAME = "edit_portfolio_name"
VIEW_PORTFOLIO_USERS = "view_portfolio_users"
EDIT_PORTFOLIO_USERS = "edit_portfolio_users"
CREATE_PORTFOLIO_USERS = "create_portfolio_users"
VIEW_PORTFOLIO_ACTIVITY_LOG = "view_portfolio_activity_log"
VIEW_PORTFOLIO_POC = "view_portfolio_poc"
# portfolio POC
EDIT_PORTFOLIO_POC = "edit_portfolio_poc"
ARCHIVE_PORTFOLIO = "archive_portfolio"

View File

@ -4,6 +4,7 @@ from itertools import chain
from atst.models import Base, mixins, types
from atst.models.portfolio_role import PortfolioRole, Status as PortfolioRoleStatus
from atst.domain.permission_sets import PermissionSets
from atst.utils import first_or_none
from atst.database import db
@ -23,7 +24,9 @@ class Portfolio(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
@property
def owner(self):
def _is_portfolio_owner(portfolio_role):
return portfolio_role.role.name == "owner"
return PermissionSets.PORTFOLIO_POC in [
perms_set.name for perms_set in portfolio_role.permission_sets
]
owner = first_or_none(_is_portfolio_owner, self.roles)
return owner.user if owner else None

View File

@ -1,5 +1,5 @@
from enum import Enum
from sqlalchemy import Index, ForeignKey, Column, Enum as SQLAEnum
from sqlalchemy import Index, ForeignKey, Column, Enum as SQLAEnum, Table
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
@ -10,7 +10,6 @@ from atst.database import db
from atst.models.environment_role import EnvironmentRole
from atst.models.application import Application
from atst.models.environment import Environment
from atst.models.role import Role
MEMBER_STATUSES = {
@ -30,6 +29,14 @@ class Status(Enum):
PENDING = "pending"
portfolio_roles_permission_sets = Table(
"portfolio_roles_permission_sets",
Base.metadata,
Column("portfolio_role_id", UUID(as_uuid=True), ForeignKey("portfolio_roles.id")),
Column("permission_set_id", UUID(as_uuid=True), ForeignKey("permission_sets.id")),
)
class PortfolioRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
__tablename__ = "portfolio_roles"
@ -39,29 +46,32 @@ class PortfolioRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
)
portfolio = relationship("Portfolio", back_populates="roles")
role_id = Column(UUID(as_uuid=True), ForeignKey("roles.id"), nullable=False)
role = relationship("Role")
user_id = Column(
UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=False
)
status = Column(SQLAEnum(Status, native_enum=False), default=Status.PENDING)
permission_sets = relationship(
"PermissionSet", secondary=portfolio_roles_permission_sets
)
@property
def permissions(self):
return [
perm for permset in self.permission_sets for perm in permset.permissions
]
def __repr__(self):
return "<PortfolioRole(role='{}', portfolio='{}', user_id='{}', id='{}')>".format(
self.role.name, self.portfolio.name, self.user_id, self.id
return "<PortfolioRole(portfolio='{}', user_id='{}', id='{}', permissions={})>".format(
self.portfolio.name, self.user_id, self.id, self.permissions
)
@property
def history(self):
previous_state = self.get_changes()
change_set = {}
if "role_id" in previous_state:
from_role_id = previous_state["role_id"][0]
from_role = db.session.query(Role).filter(Role.id == from_role_id).one()
to_role = self.role_name
change_set["role"] = [from_role.name, to_role]
# TODO: need to update to include permission_sets
if "status" in previous_state:
from_status = previous_state["status"][0].value
to_status = self.status.value
@ -105,18 +115,10 @@ class PortfolioRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
def has_dod_id_error(self):
return self.latest_invitation and self.latest_invitation.is_rejected_wrong_user
@property
def role_name(self):
return self.role.name
@property
def user_name(self):
return self.user.full_name
@property
def role_displayname(self):
return self.role.display_name
@property
def is_active(self):
return self.status == Status.ACTIVE

View File

@ -1,32 +0,0 @@
from sqlalchemy import String, Column
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm.attributes import flag_modified
from atst.models import Base, types, mixins
class Role(Base, mixins.TimestampsMixin):
__tablename__ = "roles"
id = types.Id()
name = Column(String, index=True, unique=True, nullable=False)
display_name = Column(String, nullable=False)
description = Column(String, nullable=False)
permissions = Column(ARRAY(String), index=True, server_default="{}", nullable=False)
def add_permission(self, permission):
perms_set = set(self.permissions)
perms_set.add(permission)
self.permissions = list(perms_set)
flag_modified(self, "permissions")
def remove_permission(self, permission):
perms_set = set(self.permissions)
perms_set.discard(permission)
self.permissions = list(perms_set)
flag_modified(self, "permissions")
def __repr__(self):
return "<Role(name='{}', description='{}', permissions='{}', id='{}')>".format(
self.name, self.description, self.permissions, self.id
)

View File

@ -11,9 +11,9 @@ class User(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
id = types.Id()
username = Column(String)
atat_role_id = Column(UUID(as_uuid=True), ForeignKey("roles.id"))
atat_role_id = Column(UUID(as_uuid=True), ForeignKey("permission_sets.id"))
atat_role = relationship("Role")
atat_role = relationship("PermissionSet")
portfolio_roles = relationship("PortfolioRole", backref="user")
email = Column(String, unique=True)

View File

@ -64,7 +64,9 @@ def home():
elif num_portfolios == 1:
portfolio_role = user.portfolio_roles[0]
portfolio_id = portfolio_role.portfolio.id
is_portfolio_owner = portfolio_role.role.name == "owner"
is_portfolio_owner = "portfolio_poc" in [
ps.name for ps in portfolio_role.permission_sets
]
if is_portfolio_owner:
return redirect(

View File

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

View File

@ -10,13 +10,8 @@ from atst.domain.portfolio_roles import PortfolioRoles, MEMBER_STATUS_CHOICES
from atst.domain.environments import Environments
from atst.domain.environment_roles import EnvironmentRoles
from atst.services.invitation import Invitation as InvitationService
from atst.forms.new_member import NewMemberForm
from atst.forms.edit_member import EditMemberForm
from atst.forms.data import (
ENVIRONMENT_ROLES,
ENV_ROLE_MODAL_DESCRIPTION,
PORTFOLIO_ROLE_DEFINITIONS,
)
import atst.forms.portfolio_member as member_forms
from atst.forms.data import ENVIRONMENT_ROLES, ENV_ROLE_MODAL_DESCRIPTION
from atst.domain.authz import Authorization
from atst.models.permissions import Permissions
@ -28,7 +23,7 @@ def serialize_portfolio_role(portfolio_role):
"name": portfolio_role.user_name,
"status": portfolio_role.display_status,
"id": portfolio_role.user_id,
"role": portfolio_role.role_displayname,
"role": "admin",
"num_env": portfolio_role.num_environment_roles,
"edit_link": url_for(
"portfolios.view_member",
@ -46,7 +41,6 @@ def portfolio_members(portfolio_id):
return render_template(
"portfolios/members/index.html",
portfolio=portfolio,
role_choices=PORTFOLIO_ROLE_DEFINITIONS,
status_choices=MEMBER_STATUS_CHOICES,
members=members_list,
)
@ -70,7 +64,7 @@ def application_members(portfolio_id, application_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/members/new")
def new_member(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
form = NewMemberForm()
form = member_forms.NewForm()
return render_template(
"portfolios/members/new.html", portfolio=portfolio, form=form
)
@ -79,7 +73,7 @@ def new_member(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/members/new", methods=["POST"])
def create_member(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
form = NewMemberForm(http_request.form)
form = member_forms.NewForm(http_request.form)
if form.validate():
try:
@ -110,12 +104,12 @@ def view_member(portfolio_id, member_id):
Authorization.check_portfolio_permission(
g.current_user,
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
Permissions.EDIT_PORTFOLIO_USERS,
"edit this portfolio user",
)
member = PortfolioRoles.get(portfolio_id, member_id)
applications = Applications.get_all(g.current_user, member, portfolio)
form = EditMemberForm(portfolio_role=member.role_name)
form = member_forms.EditForm(portfolio_role="admin")
editable = g.current_user == member.user
can_revoke_access = Portfolios.can_revoke_access_for(portfolio, member)
@ -144,7 +138,7 @@ def update_member(portfolio_id, member_id):
Authorization.check_portfolio_permission(
g.current_user,
portfolio,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
Permissions.EDIT_PORTFOLIO_USERS,
"edit this portfolio user",
)
member = PortfolioRoles.get(portfolio_id, member_id)
@ -157,20 +151,11 @@ def update_member(portfolio_id, member_id):
env_role = form_dict[entry] or None
ids_and_roles.append({"id": env_id, "role": env_role})
form = EditMemberForm(http_request.form)
form = member_forms.EditForm(http_request.form)
if form.validate():
new_role_name = None
if form.data["portfolio_role"] != member.role.name:
member = Portfolios.update_member(
g.current_user, portfolio, member, form.data["portfolio_role"]
)
new_role_name = member.role_displayname
flash(
"portfolio_role_updated",
member_name=member.user_name,
updated_role=new_role_name,
)
member = Portfolios.update_member(
g.current_user, portfolio, member, form.data["permission_sets"]
)
updated_roles = Environments.update_environment_roles(
g.current_user, portfolio, member, ids_and_roles
)

View File

@ -16,7 +16,7 @@ from atst.models.audit_event import AuditEvent
from atst.models.environment import Environment
from atst.models.environment_role import EnvironmentRole
from atst.models.application import Application
from atst.models.role import Role
from atst.models.permission_set import PermissionSet
from atst.models.user import User
from atst.models.portfolio_role import PortfolioRole
from atst.models.portfolio import Portfolio

View File

@ -9,23 +9,31 @@ sys.path.append(parent_dir)
from sqlalchemy.orm.exc import NoResultFound
from atst.app import make_config, make_app
from atst.database import db
from atst.models import Role, Permissions
from atst.domain.roles import ATAT_ROLES, PORTFOLIO_ROLES
from atst.models import PermissionSet, Permissions
from atst.domain.permission_sets import ATAT_ROLES, PORTFOLIO_PERMISSION_SETS
def seed_roles():
for role_info in ATAT_ROLES + PORTFOLIO_ROLES:
role = Role(**role_info)
for permission_set_info in ATAT_ROLES + PORTFOLIO_PERMISSION_SETS:
permission_set = PermissionSet(**permission_set_info)
try:
existing_role = db.session.query(Role).filter_by(name=role.name).one()
existing_role.description = role.description
existing_role.permissions = role.permissions
existing_role.display_name = role.display_name
db.session.add(existing_role)
print("Updated existing role {}".format(existing_role.name))
existing_permission_set = (
db.session.query(PermissionSet)
.filter_by(name=permission_set.name)
.one()
)
existing_permission_set.description = permission_set.description
existing_permission_set.permissions = permission_set.permissions
existing_permission_set.display_name = permission_set.display_name
db.session.add(existing_permission_set)
print(
"Updated existing permission_set {}".format(
existing_permission_set.name
)
)
except NoResultFound:
db.session.add(role)
print("Added new role {}".format(role.name))
db.session.add(permission_set)
print("Added new permission_set {}".format(permission_set.name))
db.session.commit()

View File

@ -3,7 +3,7 @@
{% 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 %}
@ -41,13 +41,13 @@
<h3 class='icon-link accordian__title' v-on:click="props.toggle">{{ application.name }}</h3>
<span class='accordian__description'>{{ application.description }}</span>
<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) }}'>
<span>{{ "portfolios.applications.app_settings_text" | translate }}</span>
</a>
<div class='separator'></div>
{% 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) }}'>
<span>{{ "portfolios.applications.team_text" | translate }}</span>
<span class='counter'>{{ application.num_users }}</span>

View File

@ -11,7 +11,7 @@
{% 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(
'There are currently no members in this Portfolio.',

View File

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

View File

@ -21,7 +21,30 @@
<h1 class='member-card__heading'>{{ member.user.full_name }}</h1>
<div class="usa-input member-card__input">
{{ Selector(form.portfolio_role) }}
<table>
<thead>
<tr>
<th>{{ "portfolios.members.permissions.app_mgmt" | translate }}</th>
<th>{{ "portfolios.members.permissions.funding" | translate }}</th>
<th>{{ "portfolios.members.permissions.reporting" | translate }}</th>
<th>{{ "portfolios.members.permissions.portfolio_mgmt" | translate }}</th>
</tr>
</thead>
<tbody>
<td>
{{ form.perms_app_mgmt() }}
</td>
<td>
{{ form.perms_funding() }}
</td>
<td>
{{ form.perms_reporting() }}
</td>
<td>
{{ form.perms_portfolio_mgmt() }}
</td>
</tbody>
</table>
</div>
</div>

View File

@ -9,7 +9,7 @@
{% 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(
'There are currently no members in this Portfolio.',
@ -29,7 +29,6 @@
id="search-template"
class='member-list'
v-bind:members='{{ members | tojson}}'
v-bind:role_choices='{{ role_choices | tojson}}'
v-bind:status_choices='{{ status_choices | tojson}}'>
<div>
<form class='search-bar' @submit.prevent>
@ -50,17 +49,6 @@
{% endfor %}
</select>
</div>
<div class='usa-input'>
<label for='filter-role'>Filter members by role</label>
<select v-model="role" id="filter-role" name="filter-role">
<option value="" selected disabled>Filter by role</option>
<option value="all">View All</option>
{% for role in role_choices %}
<option value='{{ role.name }}'>{{ role.display_name }}</option>
{% endfor %}
</select>
</div>
</div>
</form>

View File

@ -22,11 +22,33 @@
{{ TextInput(form.last_name) }}
{{ TextInput(form.email,placeholder='jane@mail.mil', validation='email') }}
{{ TextInput(form.dod_id,placeholder='10-digit number on the back of the CAC', validation='dodId') }}
{{ Selector(form.portfolio_role) }}
<table>
<thead>
<tr>
<th>{{ "portfolios.members.permissions.app_mgmt" | translate }}</th>
<th>{{ "portfolios.members.permissions.funding" | translate }}</th>
<th>{{ "portfolios.members.permissions.reporting" | translate }}</th>
<th>{{ "portfolios.members.permissions.portfolio_mgmt" | translate }}</th>
</tr>
</thead>
<tbody>
<td>
{{ form.perms_app_mgmt() }}
</td>
<td>
{{ form.perms_funding() }}
</td>
<td>
{{ form.perms_reporting() }}
</td>
<td>
{{ form.perms_portfolio_mgmt() }}
</td>
</tbody>
</table>
</div>
</div>
<div class='action-group'>
<button class="usa-button usa-button-big usa-button-primary" tabindex="0">Add User</button>
<a href='{{ url_for("portfolios.portfolio_members", portfolio_id=portfolio.id) }}' class='action-group__action icon-link'>

View File

@ -134,7 +134,7 @@
{% 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.'
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.'

View File

@ -2,7 +2,7 @@ import pytest
from atst.domain.audit_log import AuditLog
from atst.domain.exceptions import UnauthorizedError
from atst.domain.roles import Roles
from atst.domain.permission_sets import PermissionSets
from atst.models.portfolio_role import Status as PortfolioRoleStatus
from tests.factories import (
UserFactory,
@ -19,7 +19,7 @@ def ccpo():
@pytest.fixture(scope="function")
def developer():
return UserFactory.from_atat_role("default")
return UserFactory.create()
def test_non_admin_cannot_view_audit_log(developer):
@ -27,6 +27,7 @@ def test_non_admin_cannot_view_audit_log(developer):
AuditLog.get_all_events(developer)
@pytest.mark.skip(reason="no ccpo access yet")
def test_ccpo_can_view_audit_log(ccpo):
events = AuditLog.get_all_events(ccpo)
assert len(events) > 0
@ -41,6 +42,7 @@ def test_paginate_audit_log(ccpo):
assert len(events) == 25
@pytest.mark.skip(reason="no ccpo access yet")
def test_ccpo_can_view_ws_audit_log(ccpo):
portfolio = PortfolioFactory.create()
events = AuditLog.get_portfolio_events(ccpo, portfolio)
@ -51,10 +53,7 @@ def test_ws_admin_can_view_ws_audit_log():
portfolio = PortfolioFactory.create()
admin = UserFactory.create()
PortfolioRoleFactory.create(
portfolio=portfolio,
user=admin,
role=Roles.get("admin"),
status=PortfolioRoleStatus.ACTIVE,
portfolio=portfolio, user=admin, status=PortfolioRoleStatus.ACTIVE
)
events = AuditLog.get_portfolio_events(admin, portfolio)
assert len(events) > 0
@ -66,6 +65,7 @@ def test_ws_owner_can_view_ws_audit_log():
assert len(events) > 0
@pytest.mark.skip(reason="all portfolio users can view audit log")
def test_other_users_cannot_view_ws_audit_log():
with pytest.raises(UnauthorizedError):
portfolio = PortfolioFactory.create()

View File

@ -1,8 +1,10 @@
import pytest
from tests.factories import TaskOrderFactory, UserFactory
from tests.factories import TaskOrderFactory, UserFactory, PortfolioRoleFactory
from atst.domain.authz import Authorization
from atst.domain.permission_sets import PermissionSets
from atst.domain.exceptions import UnauthorizedError
from atst.models.permissions import Permissions
@pytest.fixture
@ -40,3 +42,19 @@ def test_check_is_ko_or_cor(task_order, invalid_user):
with pytest.raises(UnauthorizedError):
Authorization.check_is_ko_or_cor(invalid_user, task_order)
def test_has_portfolio_permission():
role_one = PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING)
role_two = PermissionSets.get(PermissionSets.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

@ -14,7 +14,7 @@ def test_create_environments():
def test_create_environment_role_creates_cloud_id(session):
owner = UserFactory.create()
developer = UserFactory.from_atat_role("developer")
developer = UserFactory.create()
portfolio = PortfolioFactory.create(
owner=owner,
@ -38,7 +38,7 @@ def test_create_environment_role_creates_cloud_id(session):
def test_update_environment_roles():
owner = UserFactory.create()
developer = UserFactory.from_atat_role("developer")
developer = UserFactory.create()
portfolio = PortfolioFactory.create(
owner=owner,
@ -81,7 +81,7 @@ def test_update_environment_roles():
def test_remove_environment_role():
owner = UserFactory.create()
developer = UserFactory.from_atat_role("developer")
developer = UserFactory.create()
portfolio = PortfolioFactory.create(
owner=owner,
members=[{"user": developer, "role_name": "developer"}],
@ -132,7 +132,7 @@ def test_remove_environment_role():
def test_no_update_to_environment_roles():
owner = UserFactory.create()
developer = UserFactory.from_atat_role("developer")
developer = UserFactory.create()
portfolio = PortfolioFactory.create(
owner=owner,

View File

@ -0,0 +1,32 @@
import pytest
from atst.domain.permission_sets import PermissionSets
from atst.domain.exceptions import NotFoundError
from atst.utils import first_or_none
def test_get_all():
roles = PermissionSets.get_all()
assert roles
def test_get_existing_permission_set():
role = PermissionSets.get("portfolio_poc")
assert role.name == "portfolio_poc"
def test_get_nonexistent_permission_set():
with pytest.raises(NotFoundError):
PermissionSets.get("nonexistent")
def test_get_many():
perms_sets = PermissionSets.get_many(
[PermissionSets.VIEW_PORTFOLIO_FUNDING, PermissionSets.EDIT_PORTFOLIO_FUNDING]
)
assert len(perms_sets) == 2
assert first_or_none(
lambda p: p.name == PermissionSets.VIEW_PORTFOLIO_FUNDING, perms_sets
)
assert first_or_none(
lambda p: p.name == PermissionSets.EDIT_PORTFOLIO_FUNDING, perms_sets
)

View File

@ -1,7 +1,7 @@
from atst.domain.portfolio_roles import PortfolioRoles
from atst.domain.users import Users
from atst.models.portfolio_role import Status as PortfolioRoleStatus
from atst.domain.roles import Roles
from atst.domain.permission_sets import PermissionSets
from tests.factories import (
PortfolioFactory,
@ -11,55 +11,21 @@ from tests.factories import (
)
def test_can_create_new_portfolio_role():
def test_add_portfolio_role_with_permission_sets():
portfolio = PortfolioFactory.create()
new_user = UserFactory.create()
portfolio_role_dicts = [{"id": new_user.id, "portfolio_role": "owner"}]
portfolio_roles = PortfolioRoles.add_many(portfolio.id, portfolio_role_dicts)
assert portfolio_roles[0].user_id == new_user.id
assert portfolio_roles[0].user.atat_role.name == new_user.atat_role.name
assert portfolio_roles[0].role.name == new_user.portfolio_roles[0].role.name
def test_can_update_existing_portfolio_role():
portfolio = PortfolioFactory.create()
new_user = UserFactory.create()
PortfolioRoles.add_many(
portfolio.id, [{"id": new_user.id, "portfolio_role": "owner"}]
)
portfolio_roles = PortfolioRoles.add_many(
portfolio.id, [{"id": new_user.id, "portfolio_role": "developer"}]
)
assert portfolio_roles[0].user.atat_role.name == new_user.atat_role.name
assert portfolio_roles[0].role.name == new_user.portfolio_roles[0].role.name
def test_portfolio_role_permissions():
portfolio_one = PortfolioFactory.create()
portfolio_two = PortfolioFactory.create()
new_user = UserFactory.create()
PortfolioRoleFactory.create(
portfolio=portfolio_one,
user=new_user,
role=Roles.get("developer"),
status=PortfolioRoleStatus.ACTIVE,
)
PortfolioRoleFactory.create(
portfolio=portfolio_two,
user=new_user,
role=Roles.get("developer"),
status=PortfolioRoleStatus.PENDING,
)
default_perms = set(new_user.atat_role.permissions)
assert len(
PortfolioRoles.portfolio_role_permissions(portfolio_one, new_user)
) > len(default_perms)
assert (
PortfolioRoles.portfolio_role_permissions(portfolio_two, new_user)
== default_perms
permission_sets = [PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT]
port_role = PortfolioRoles.add(
new_user, portfolio.id, permission_sets=permission_sets
)
assert len(port_role.permission_sets) == 6
expected_names = [
PermissionSets.EDIT_PORTFOLIO_APPLICATION_MANAGEMENT,
PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
PermissionSets.VIEW_PORTFOLIO_FUNDING,
PermissionSets.VIEW_PORTFOLIO_REPORTS,
PermissionSets.VIEW_PORTFOLIO_ADMIN,
PermissionSets.VIEW_PORTFOLIO,
]
actual_names = [prms.name for prms in port_role.permission_sets]
assert expected_names == expected_names

View File

@ -6,9 +6,15 @@ from atst.domain.portfolios import Portfolios, PortfolioError
from atst.domain.portfolio_roles import PortfolioRoles
from atst.domain.applications import Applications
from atst.domain.environments import Environments
from atst.domain.permission_sets import PermissionSets, PORTFOLIO_PERMISSION_SETS
from atst.models.portfolio_role import Status as PortfolioRoleStatus
from tests.factories import UserFactory, PortfolioRoleFactory, PortfolioFactory
from tests.factories import (
UserFactory,
PortfolioRoleFactory,
PortfolioFactory,
get_all_portfolio_permission_sets,
)
@pytest.fixture(scope="function")
@ -52,7 +58,7 @@ def test_get_for_update_applications_allows_owner(portfolio, portfolio_owner):
def test_get_for_update_applications_blocks_developer(portfolio):
developer = UserFactory.create()
PortfolioRoles.add(developer, portfolio.id, "developer")
PortfolioRoles.add(developer, portfolio.id)
with pytest.raises(UnauthorizedError):
Portfolios.get_for_update_applications(developer, portfolio.id)
@ -113,13 +119,12 @@ def test_update_portfolio_role_role(portfolio, portfolio_owner):
}
PortfolioRoleFactory._meta.sqlalchemy_session_persistence = "flush"
member = PortfolioRoleFactory.create(portfolio=portfolio)
role_name = "admin"
permission_sets = [PermissionSets.EDIT_PORTFOLIO_FUNDING]
updated_member = Portfolios.update_member(
portfolio_owner, portfolio, member, role_name
portfolio_owner, portfolio, member, permission_sets=permission_sets
)
assert updated_member.portfolio == portfolio
assert updated_member.role_name == role_name
def test_need_permission_to_update_portfolio_role_role(portfolio, portfolio_owner):
@ -144,18 +149,40 @@ def test_owner_can_view_portfolio_members(portfolio, portfolio_owner):
assert portfolio
@pytest.mark.skip(reason="no ccpo access yet")
def test_ccpo_can_view_portfolio_members(portfolio, portfolio_owner):
ccpo = UserFactory.from_atat_role("ccpo")
assert Portfolios.get_with_members(ccpo, portfolio.id)
def test_random_user_cannot_view_portfolio_members(portfolio):
developer = UserFactory.from_atat_role("developer")
developer = UserFactory.create()
with pytest.raises(UnauthorizedError):
portfolio = Portfolios.get_with_members(developer, portfolio.id)
def test_scoped_portfolio_for_admin_missing_view_apps_perms(portfolio_owner, portfolio):
Applications.create(
portfolio_owner,
portfolio,
"My Application 2",
"My application 2",
["dev", "staging", "prod"],
)
restricted_admin = UserFactory.create()
PortfolioRoleFactory.create(
portfolio=portfolio,
user=restricted_admin,
permission_sets=[PermissionSets.get(PermissionSets.VIEW_PORTFOLIO)],
)
scoped_portfolio = Portfolios.get(restricted_admin, portfolio.id)
assert scoped_portfolio.id == portfolio.id
assert len(portfolio.applications) == 1
assert len(scoped_portfolio.applications) == 0
@pytest.mark.skip(reason="should be reworked pending application member changes")
def test_scoped_portfolio_only_returns_a_users_applications_and_environments(
portfolio, portfolio_owner
):
@ -173,7 +200,7 @@ def test_scoped_portfolio_only_returns_a_users_applications_and_environments(
"My application 2",
["dev", "staging", "prod"],
)
developer = UserFactory.from_atat_role("developer")
developer = UserFactory.create()
dev_environment = Environments.add_member(
new_application.environments[0], developer, "developer"
)
@ -198,9 +225,10 @@ def test_scoped_portfolio_returns_all_applications_for_portfolio_admin(
["dev", "staging", "prod"],
)
admin = UserFactory.from_atat_role("default")
Portfolios._create_portfolio_role(
admin, portfolio, "admin", status=PortfolioRoleStatus.ACTIVE
admin = UserFactory.create()
perm_sets = get_all_portfolio_permission_sets()
PortfolioRoleFactory.create(
user=admin, portfolio=portfolio, permission_sets=perm_sets
)
scoped_portfolio = Portfolios.get(admin, portfolio.id)
@ -227,7 +255,7 @@ def test_scoped_portfolio_returns_all_applications_for_portfolio_owner(
def test_for_user_returns_active_portfolios_for_user(portfolio, portfolio_owner):
bob = UserFactory.from_atat_role("default")
bob = UserFactory.create()
PortfolioRoleFactory.create(
user=bob, portfolio=portfolio, status=PortfolioRoleStatus.ACTIVE
)
@ -239,14 +267,15 @@ def test_for_user_returns_active_portfolios_for_user(portfolio, portfolio_owner)
def test_for_user_does_not_return_inactive_portfolios(portfolio, portfolio_owner):
bob = UserFactory.from_atat_role("default")
Portfolios.add_member(portfolio, bob, "developer")
bob = UserFactory.create()
Portfolios.add_member(portfolio, bob)
PortfolioFactory.create()
bobs_portfolios = Portfolios.for_user(bob)
assert len(bobs_portfolios) == 0
@pytest.mark.skip(reason="CCPO status not fully implemented")
def test_for_user_returns_all_portfolios_for_ccpo(portfolio, portfolio_owner):
sam = UserFactory.from_atat_role("ccpo")
PortfolioFactory.create()
@ -260,16 +289,18 @@ def test_get_for_update_information(portfolio, portfolio_owner):
assert portfolio == owner_ws
admin = UserFactory.create()
Portfolios._create_portfolio_role(
admin, portfolio, "admin", status=PortfolioRoleStatus.ACTIVE
perm_sets = get_all_portfolio_permission_sets()
PortfolioRoleFactory.create(
user=admin, portfolio=portfolio, permission_sets=perm_sets
)
admin_ws = Portfolios.get_for_update_information(admin, portfolio.id)
assert portfolio == admin_ws
ccpo = UserFactory.from_atat_role("ccpo")
assert Portfolios.get_for_update_information(ccpo, portfolio.id)
# TODO: implement ccpo roles
# ccpo = UserFactory.from_atat_role("ccpo")
# assert Portfolios.get_for_update_information(ccpo, portfolio.id)
developer = UserFactory.from_atat_role("developer")
developer = UserFactory.create()
with pytest.raises(UnauthorizedError):
Portfolios.get_for_update_information(developer, portfolio.id)

View File

@ -1,18 +0,0 @@
import pytest
from atst.domain.roles import Roles
from atst.domain.exceptions import NotFoundError
def test_get_all_roles():
roles = Roles.get_all()
assert roles
def test_get_existing_role():
role = Roles.get("developer")
assert role.name == "developer"
def test_get_nonexistent_role():
with pytest.raises(NotFoundError):
Roles.get("nonexistent")

View File

@ -2,6 +2,8 @@ import pytest
from atst.domain.task_orders import TaskOrders, TaskOrderError, DD254s
from atst.domain.exceptions import UnauthorizedError
from atst.domain.permission_sets import PermissionSets
from atst.domain.portfolio_roles import PortfolioRoles
from atst.models.attachment import Attachment
from tests.factories import (
@ -90,7 +92,7 @@ def test_add_officer_who_is_already_portfolio_member():
assert task_order.contracting_officer == owner
member = task_order.portfolio.members[0]
assert member.user == owner and member.role_name == "owner"
assert member.user == owner
def test_task_order_access():
@ -111,19 +113,26 @@ def test_task_order_access():
portfolio = PortfolioFactory.create(owner=creator)
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=[
PermissionSets.get(prms)
for prms in PortfolioRoles.DEFAULT_PORTFOLIO_PERMISSION_SETS
],
)
TaskOrders.add_officer(
creator, 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], "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],
[creator, officer],
[member, rando],
"add_officer",
[task_order, "contracting_officer", rando.to_dictionary()],
[task_order, "contracting_officer", UserFactory.dictionary()],
)

View File

@ -8,14 +8,14 @@ DOD_ID = "my_dod_id"
def test_create_user():
user = Users.create(DOD_ID, "developer")
assert user.atat_role.name == "developer"
user = Users.create(DOD_ID, "default")
assert user.atat_role.name == "default"
def test_create_user_with_existing_email():
Users.create(DOD_ID, "developer", email="thisusersemail@usersRus.com")
Users.create(DOD_ID, "default", email="thisusersemail@usersRus.com")
with pytest.raises(AlreadyExistsError):
Users.create(DOD_ID, "admin", email="thisusersemail@usersRus.com")
Users.create(DOD_ID, "ccpo", email="thisusersemail@usersRus.com")
def test_create_user_with_nonexistent_role():
@ -24,61 +24,61 @@ def test_create_user_with_nonexistent_role():
def test_get_or_create_nonexistent_user():
user = Users.get_or_create_by_dod_id(DOD_ID, atat_role_name="developer")
user = Users.get_or_create_by_dod_id(DOD_ID, atat_role_name="default")
assert user.dod_id == DOD_ID
def test_get_or_create_existing_user():
Users.get_or_create_by_dod_id(DOD_ID, atat_role_name="developer")
user = Users.get_or_create_by_dod_id(DOD_ID, atat_role_name="developer")
Users.get_or_create_by_dod_id(DOD_ID, atat_role_name="default")
user = Users.get_or_create_by_dod_id(DOD_ID, atat_role_name="default")
assert user
def test_get_user():
new_user = Users.create(DOD_ID, "developer")
new_user = Users.create(DOD_ID, "default")
user = Users.get(new_user.id)
assert user.id == new_user.id
def test_get_nonexistent_user():
Users.create(DOD_ID, "developer")
Users.create(DOD_ID, "default")
with pytest.raises(NotFoundError):
Users.get(uuid4())
def test_get_user_by_dod_id():
new_user = Users.create(DOD_ID, "developer")
new_user = Users.create(DOD_ID, "default")
user = Users.get_by_dod_id(DOD_ID)
assert user == new_user
def test_update_role():
new_user = Users.create(DOD_ID, "developer")
new_user = Users.create(DOD_ID, "default")
updated_user = Users.update_role(new_user.id, "ccpo")
assert updated_user.atat_role.name == "ccpo"
def test_update_role_with_nonexistent_user():
Users.create(DOD_ID, "developer")
Users.create(DOD_ID, "default")
with pytest.raises(NotFoundError):
Users.update_role(uuid4(), "ccpo")
def test_update_existing_user_with_nonexistent_role():
new_user = Users.create(DOD_ID, "developer")
new_user = Users.create(DOD_ID, "default")
with pytest.raises(NotFoundError):
Users.update_role(new_user.id, "nonexistent")
def test_update_user():
new_user = Users.create(DOD_ID, "developer")
new_user = Users.create(DOD_ID, "default")
updated_user = Users.update(new_user, {"first_name": "Jabba"})
assert updated_user.first_name == "Jabba"
def test_update_user_with_dod_id():
new_user = Users.create(DOD_ID, "developer")
new_user = Users.create(DOD_ID, "default")
with pytest.raises(UnauthorizedError) as excinfo:
Users.update(new_user, {"dod_id": "1234567890"})

View File

@ -12,14 +12,15 @@ from atst.models.environment import Environment
from atst.models.application import Application
from atst.models.task_order import TaskOrder
from atst.models.user import User
from atst.models.role import Role
from atst.models.permission_set import PermissionSet
from atst.models.portfolio import Portfolio
from atst.domain.roles import Roles, PORTFOLIO_ROLES
from atst.domain.permission_sets import PermissionSets, PORTFOLIO_PERMISSION_SETS
from atst.models.portfolio_role import PortfolioRole, Status as PortfolioRoleStatus
from atst.models.environment_role import EnvironmentRole
from atst.models.invitation import Invitation, Status as InvitationStatus
from atst.models.dd_254 import DD254
from atst.domain.invitations import Invitations
from atst.domain.portfolio_roles import PortfolioRoles
def random_choice(choices):
@ -63,9 +64,15 @@ def _random_date(year_min, year_max, operation):
)
def random_portfolio_role():
choice = random.choice(PORTFOLIO_ROLES)
return Roles.get(choice["name"])
def base_portfolio_permission_sets():
return [
PermissionSets.get(prms)
for prms in PortfolioRoles.DEFAULT_PORTFOLIO_PERMISSION_SETS
]
def get_all_portfolio_permission_sets():
return PermissionSets.get_many(PortfolioRoles.PORTFOLIO_PERMISSION_SETS)
class Base(factory.alchemy.SQLAlchemyModelFactory):
@ -82,7 +89,7 @@ class UserFactory(Base):
email = factory.Faker("email")
first_name = factory.Faker("first_name")
last_name = factory.Faker("last_name")
atat_role = factory.LazyFunction(lambda: Roles.get("default"))
atat_role = factory.LazyFunction(lambda: PermissionSets.get("default"))
dod_id = factory.LazyFunction(random_dod_id)
phone_number = factory.LazyFunction(random_phone_number)
service_branch = factory.LazyFunction(random_service_branch)
@ -95,7 +102,7 @@ class UserFactory(Base):
@classmethod
def from_atat_role(cls, atat_role_name, **kwargs):
role = Roles.get(atat_role_name)
role = PermissionSets.get(atat_role_name)
return cls.create(atat_role=role, **kwargs)
@ -121,19 +128,29 @@ class PortfolioFactory(Base):
PortfolioRoleFactory.create(
portfolio=portfolio,
role=Roles.get("owner"),
user=owner,
status=PortfolioRoleStatus.ACTIVE,
permission_sets=get_all_portfolio_permission_sets(),
)
for member in members:
user = member.get("user", UserFactory.create())
role_name = member["role_name"]
perms_set = None
if member.get("permissions_sets"):
perms_set = [
PermissionSets.get(perm_set)
for perm_set in member.get("permission_sets")
]
else:
perms_set = []
PortfolioRoleFactory.create(
portfolio=portfolio,
role=Roles.get(role_name),
user=user,
status=PortfolioRoleStatus.ACTIVE,
permission_sets=perms_set,
)
portfolio.applications = applications
@ -186,9 +203,9 @@ class PortfolioRoleFactory(Base):
model = PortfolioRole
portfolio = factory.SubFactory(PortfolioFactory)
role = factory.LazyFunction(random_portfolio_role)
user = factory.SubFactory(UserFactory)
status = PortfolioRoleStatus.PENDING
permission_sets = factory.LazyFunction(base_portfolio_permission_sets)
class EnvironmentRoleFactory(Base):

View File

@ -5,7 +5,7 @@ from tests.factories import PortfolioFactory, UserFactory
def test_add_user_to_environment():
owner = UserFactory.create()
developer = UserFactory.from_atat_role("developer")
developer = UserFactory.create()
portfolio = PortfolioFactory.create(owner=owner)
application = Applications.create(

View File

@ -1,10 +1,11 @@
import pytest
import datetime
from atst.domain.environments import Environments
from atst.domain.portfolios import Portfolios
from atst.domain.applications import Applications
from atst.domain.permission_sets import PermissionSets
from atst.models.portfolio_role import Status
from atst.models.role import Role
from atst.models.invitation import Status as InvitationStatus
from atst.models.audit_event import AuditEvent
from atst.models.portfolio_role import Status as PortfolioRoleStatus
@ -20,12 +21,12 @@ from tests.factories import (
from atst.domain.portfolio_roles import PortfolioRoles
def test_has_no_ws_role_history(session):
def test_has_no_portfolio_role_history(session):
owner = UserFactory.create()
user = UserFactory.create()
portfolio = PortfolioFactory.create(owner=owner)
portfolio_role = PortfolioRoles.add(user, portfolio.id, "developer")
portfolio_role = PortfolioRoles.add(user, portfolio.id)
create_event = (
session.query(AuditEvent)
.filter(
@ -37,7 +38,8 @@ def test_has_no_ws_role_history(session):
assert not create_event.changed_state
def test_has_ws_role_history(session):
@pytest.mark.skip(reason="need to update audit log permission set handling")
def test_has_portfolio_role_history(session):
owner = UserFactory.create()
user = UserFactory.create()
@ -46,9 +48,7 @@ def test_has_ws_role_history(session):
# in order to get the history, we don't want the PortfolioRoleFactory
# to commit after create()
PortfolioRoleFactory._meta.sqlalchemy_session_persistence = "flush"
portfolio_role = PortfolioRoleFactory.create(
portfolio=portfolio, user=user, role=role
)
portfolio_role = PortfolioRoleFactory.create(portfolio=portfolio, user=user)
PortfolioRoles.update_role(portfolio_role, "admin")
changed_events = (
session.query(AuditEvent)
@ -62,7 +62,7 @@ def test_has_ws_role_history(session):
assert changed_events[0].changed_state["role"][1] == "admin"
def test_has_ws_status_history(session):
def test_has_portfolio_status_history(session):
owner = UserFactory.create()
user = UserFactory.create()
@ -137,7 +137,7 @@ def test_event_details():
user = UserFactory.create()
portfolio = PortfolioFactory.create(owner=owner)
portfolio_role = PortfolioRoles.add(user, portfolio.id, "developer")
portfolio_role = PortfolioRoles.add(user, portfolio.id)
assert portfolio_role.event_details["updated_user_name"] == user.displayname
assert portfolio_role.event_details["updated_user_id"] == str(user.id)
@ -184,27 +184,16 @@ def test_has_environment_roles():
assert portfolio_role.has_environment_roles
def test_role_displayname():
owner = UserFactory.create()
developer_data = {
"dod_id": "1234567890",
"first_name": "Test",
"last_name": "User",
"email": "test.user@mail.com",
"portfolio_role": "developer",
}
portfolio = PortfolioFactory.create(owner=owner)
portfolio_role = Portfolios.create_member(owner, portfolio, developer_data)
assert portfolio_role.role_displayname == "Developer"
def test_status_when_member_is_active():
portfolio_role = PortfolioRoleFactory.create(status=Status.ACTIVE)
assert portfolio_role.display_status == "Active"
def test_status_when_member_is_disabled():
portfolio_role = PortfolioRoleFactory.create(status=Status.DISABLED)
assert portfolio_role.display_status == "Disabled"
def test_status_when_invitation_has_been_rejected_for_expirations():
portfolio = PortfolioFactory.create()
user = UserFactory.create()
@ -229,6 +218,18 @@ def test_status_when_invitation_has_been_rejected_for_wrong_user():
assert portfolio_role.display_status == "Error on invite"
def test_status_when_invitation_has_been_revoked():
portfolio = PortfolioFactory.create()
user = UserFactory.create()
portfolio_role = PortfolioRoleFactory.create(
portfolio=portfolio, user=user, status=PortfolioRoleStatus.PENDING
)
invitation = InvitationFactory.create(
portfolio_role=portfolio_role, status=InvitationStatus.REVOKED
)
assert portfolio_role.display_status == "Invite revoked"
def test_status_when_invitation_is_expired():
portfolio = PortfolioFactory.create()
user = UserFactory.create()
@ -298,3 +299,11 @@ def test_can_list_all_environments():
)
assert len(portfolio.all_environments) == 9
def test_can_list_all_permissions():
role_one = PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING)
role_two = PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_REPORTS)
port_role = PortfolioRoleFactory.create(permission_sets=[role_one, role_two])
expected_perms = role_one.permissions + role_two.permissions
assert expected_perms == expected_perms

View File

@ -12,7 +12,7 @@ from tests.factories import (
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.domain.roles import Roles
from atst.domain.permission_sets import PermissionSets
from atst.models.portfolio_role import Status as PortfolioRoleStatus
@ -29,7 +29,7 @@ def test_user_without_permission_has_no_budget_report_link(client, user_session)
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(
user, portfolio, "developer", status=PortfolioRoleStatus.ACTIVE
user, portfolio, status=PortfolioRoleStatus.ACTIVE
)
user_session(user)
response = client.get("/portfolios/{}/applications".format(portfolio.id))
@ -45,10 +45,7 @@ def test_user_with_permission_has_activity_log_link(client, user_session):
ccpo = UserFactory.from_atat_role("ccpo")
admin = UserFactory.create()
PortfolioRoleFactory.create(
portfolio=portfolio,
user=admin,
role=Roles.get("admin"),
status=PortfolioRoleStatus.ACTIVE,
portfolio=portfolio, user=admin, status=PortfolioRoleStatus.ACTIVE
)
user_session(portfolio.owner)
@ -103,7 +100,7 @@ def test_user_with_permission_has_add_application_link(client, user_session):
def test_user_without_permission_has_no_add_application_link(client, user_session):
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(user, portfolio, "developer")
Portfolios._create_portfolio_role(user, portfolio)
user_session(user)
response = client.get("/portfolios/{}/applications".format(portfolio.id))
assert (

View File

@ -12,6 +12,7 @@ from atst.domain.portfolios import Portfolios
from atst.models.portfolio_role import Status as PortfolioRoleStatus
from atst.models.invitation import Status as InvitationStatus
from atst.domain.users import Users
from atst.domain.permission_sets import PermissionSets
def test_existing_member_accepts_valid_invite(client, user_session):
@ -45,11 +46,18 @@ def test_new_member_accepts_valid_invite(monkeypatch, client, user_session):
user_info = UserFactory.dictionary()
user_session(portfolio.owner)
client.post(
response = client.post(
url_for("portfolios.create_member", portfolio_id=portfolio.id),
data={"portfolio_role": "developer", **user_info},
data={
"perms_app_mgmt": PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
"perms_funding": PermissionSets.VIEW_PORTFOLIO_FUNDING,
"perms_reporting": PermissionSets.VIEW_PORTFOLIO_REPORTS,
"perms_portfolio_mgmt": PermissionSets.VIEW_PORTFOLIO_ADMIN,
**user_info,
},
)
assert response.status_code == 302
user = Users.get_by_dod_id(user_info["dod_id"])
token = user.invitations[0].token
@ -94,7 +102,7 @@ def test_user_who_has_not_accepted_portfolio_invite_cannot_view(client, user_ses
user_session(portfolio.owner)
response = client.post(
url_for("portfolios.create_member", portfolio_id=portfolio.id),
data={"portfolio_role": "developer", **user.to_dictionary()},
data=user.to_dictionary(),
)
# user tries to view portfolio before accepting invitation

View File

@ -12,35 +12,48 @@ from atst.domain.portfolio_roles import PortfolioRoles
from atst.domain.applications import Applications
from atst.domain.environments import Environments
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.permission_sets import PermissionSets
from atst.queue import queue
from atst.models.portfolio_role import Status as PortfolioRoleStatus
from atst.models.invitation import Status as InvitationStatus
_DEFAULT_PERMS_FORM_DATA = {
"perms_app_mgmt": PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
"perms_funding": PermissionSets.VIEW_PORTFOLIO_FUNDING,
"perms_reporting": PermissionSets.VIEW_PORTFOLIO_REPORTS,
"perms_portfolio_mgmt": PermissionSets.VIEW_PORTFOLIO_ADMIN,
}
def create_portfolio_and_invite_user(
ws_role="developer",
ws_status=PortfolioRoleStatus.PENDING,
invite_status=InvitationStatus.PENDING,
):
portfolio = PortfolioFactory.create()
owner = UserFactory.create()
portfolio = PortfolioFactory.create(owner=owner)
if ws_role != "owner":
user = UserFactory.create()
member = PortfolioRoleFactory.create(
user=user, portfolio=portfolio, status=ws_status
)
InvitationFactory.create(
user=portfolio.owner,
inviter=portfolio.owner,
user=user,
portfolio_role=member,
email=member.user.email,
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):
portfolio = PortfolioFactory.create()
user_session(portfolio.owner)
response = client.get("/portfolios/{}/members".format(portfolio.id))
assert response.status_code == 200
assert (
'href="/portfolios/{}/members/new"'.format(portfolio.id).encode()
in response.data
@ -50,7 +63,7 @@ def test_user_with_permission_has_add_member_link(client, user_session):
def test_user_without_permission_has_no_add_member_link(client, user_session):
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(user, portfolio, "developer")
Portfolios._create_portfolio_role(user, portfolio)
user_session(user)
response = client.get("/portfolios/{}/members".format(portfolio.id))
assert (
@ -62,8 +75,8 @@ def test_user_without_permission_has_no_add_member_link(client, user_session):
def test_permissions_for_view_member(client, user_session):
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(user, portfolio, "developer")
member = PortfolioRoles.add(user, portfolio.id, "developer")
Portfolios._create_portfolio_role(user, portfolio)
member = PortfolioRoles.add(user, portfolio.id)
user_session(user)
response = client.get(
url_for("portfolios.view_member", portfolio_id=portfolio.id, member_id=user.id)
@ -85,6 +98,7 @@ def test_create_member(client, user_session):
"last_name": "Zuckerman",
"email": "some_pig@zuckermans.com",
"portfolio_role": "developer",
**_DEFAULT_PERMS_FORM_DATA,
},
follow_redirects=True,
)
@ -94,13 +108,16 @@ def test_create_member(client, user_session):
assert user.has_portfolios
assert user.invitations
assert len(queue.get_queue()) == queue_length + 1
portfolio_role = user.portfolio_roles[0]
assert len(portfolio_role.permission_sets) == 5
@pytest.mark.skip(reason="permission set display not implemented")
def test_view_member_shows_role(client, user_session):
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(user, portfolio, "developer")
member = PortfolioRoles.add(user, portfolio.id, "developer")
Portfolios._create_portfolio_role(user, portfolio)
member = PortfolioRoles.add(user, portfolio.id)
user_session(portfolio.owner)
response = client.get(
url_for("portfolios.view_member", portfolio_id=portfolio.id, member_id=user.id)
@ -112,25 +129,29 @@ def test_view_member_shows_role(client, user_session):
def test_update_member_portfolio_role(client, user_session):
portfolio = PortfolioFactory.create()
user = UserFactory.create()
member = PortfolioRoles.add(user, portfolio.id, "developer")
member = PortfolioRoles.add(user, portfolio.id)
user_session(portfolio.owner)
response = client.post(
url_for(
"portfolios.update_member", portfolio_id=portfolio.id, member_id=user.id
),
data={"portfolio_role": "security_auditor"},
data={
**_DEFAULT_PERMS_FORM_DATA,
"perms_funding": PermissionSets.EDIT_PORTFOLIO_FUNDING,
},
follow_redirects=True,
)
assert response.status_code == 200
assert b"role updated successfully" in response.data
assert member.role_name == "security_auditor"
edit_funding = PermissionSets.get(PermissionSets.EDIT_PORTFOLIO_FUNDING)
assert edit_funding in member.permission_sets
def test_update_member_portfolio_role_with_no_data(client, user_session):
portfolio = PortfolioFactory.create()
user = UserFactory.create()
member = PortfolioRoles.add(user, portfolio.id, "developer")
member = PortfolioRoles.add(user, portfolio.id)
user_session(portfolio.owner)
original_perms_len = len(member.permission_sets)
response = client.post(
url_for(
"portfolios.update_member", portfolio_id=portfolio.id, member_id=user.id
@ -139,13 +160,13 @@ def test_update_member_portfolio_role_with_no_data(client, user_session):
follow_redirects=True,
)
assert response.status_code == 200
assert member.role_name == "developer"
assert len(member.permission_sets) == original_perms_len
def test_update_member_environment_role(client, user_session):
portfolio = PortfolioFactory.create()
user = UserFactory.create()
member = PortfolioRoles.add(user, portfolio.id, "developer")
member = PortfolioRoles.add(user, portfolio.id)
application = Applications.create(
portfolio.owner,
portfolio,
@ -163,9 +184,9 @@ def test_update_member_environment_role(client, user_session):
"portfolios.update_member", portfolio_id=portfolio.id, member_id=user.id
),
data={
"portfolio_role": "developer",
"env_" + str(env1_id): "security_auditor",
"env_" + str(env2_id): "devops",
**_DEFAULT_PERMS_FORM_DATA,
},
follow_redirects=True,
)
@ -179,7 +200,7 @@ def test_update_member_environment_role(client, user_session):
def test_update_member_environment_role_with_no_data(client, user_session):
portfolio = PortfolioFactory.create()
user = UserFactory.create()
member = PortfolioRoles.add(user, portfolio.id, "developer")
member = PortfolioRoles.add(user, portfolio.id)
application = Applications.create(
portfolio.owner,
portfolio,
@ -242,11 +263,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):
portfolio = create_portfolio_and_invite_user(
portfolio, member = create_portfolio_and_invite_user(
ws_status=PortfolioRoleStatus.ACTIVE, invite_status=InvitationStatus.ACCEPTED
)
user_session(portfolio.owner)
member = portfolio.members[1]
response = client.get(
url_for(
"portfolios.view_member",
@ -254,17 +274,18 @@ def test_only_shows_revoke_access_button_if_active(client, user_session):
member_id=member.user.id,
)
)
assert response.status_code == 200
assert "Remove Portfolio Access" in response.data.decode()
assert "Revoke Invitation" not in response.data.decode()
assert "Resend Invitation" not in response.data.decode()
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
)
user_session(portfolio.owner)
member = portfolio.members[1]
# member = next((memb for memb in portfolio.members if memb != portfolio.owner), None)
response = client.get(
url_for(
"portfolios.view_member",
@ -278,12 +299,11 @@ def test_only_shows_revoke_invite_button_if_pending(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,
invite_status=InvitationStatus.REJECTED_EXPIRED,
)
user_session(portfolio.owner)
member = portfolio.members[1]
response = client.get(
url_for(
"portfolios.view_member",
@ -297,11 +317,10 @@ def test_only_shows_resend_button_if_expired(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
)
user_session(portfolio.owner)
member = portfolio.members[1]
response = client.get(
url_for(
"portfolios.view_member",

View File

@ -2,7 +2,7 @@ from flask import url_for
import pytest
from datetime import timedelta, date
from atst.domain.roles import Roles
from atst.domain.permission_sets import PermissionSets
from atst.domain.task_orders import TaskOrders
from atst.models.portfolio_role import Status as PortfolioStatus
from atst.models.invitation import Status as InvitationStatus
@ -230,10 +230,13 @@ class TestTaskOrderInvitations:
def test_ko_can_view_task_order(client, user_session, portfolio, user):
PortfolioRoleFactory.create(
role=Roles.get("owner"),
portfolio=portfolio,
user=user,
status=PortfolioStatus.ACTIVE,
permission_sets=[
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO),
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING),
],
)
task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=user)
user_session(user)
@ -294,16 +297,22 @@ def test_ko_can_view_ko_review_page(client, user_session):
cor = UserFactory.create()
PortfolioRoleFactory.create(
role=Roles.get("officer"),
portfolio=portfolio,
user=ko,
status=PortfolioStatus.ACTIVE,
permission_sets=[
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO),
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING),
],
)
PortfolioRoleFactory.create(
role=Roles.get("officer"),
portfolio=portfolio,
user=cor,
status=PortfolioStatus.ACTIVE,
permission_sets=[
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO),
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING),
],
)
task_order = TaskOrderFactory.create(
portfolio=portfolio,
@ -365,10 +374,13 @@ def test_mo_redirected_to_build_page(client, user_session, portfolio):
def test_cor_redirected_to_build_page(client, user_session, portfolio):
cor = UserFactory.create()
PortfolioRoleFactory.create(
role=Roles.get("officer"),
portfolio=portfolio,
user=cor,
status=PortfolioStatus.ACTIVE,
permission_sets=[
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO),
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING),
],
)
task_order = TaskOrderFactory.create(
portfolio=portfolio, contracting_officer_representative=cor
@ -384,10 +396,13 @@ def test_submit_completed_ko_review_page_as_cor(
client, user_session, pdf_upload, portfolio, user
):
PortfolioRoleFactory.create(
role=Roles.get("officer"),
portfolio=portfolio,
user=user,
status=PortfolioStatus.ACTIVE,
permission_sets=[
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO),
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING),
],
)
task_order = TaskOrderFactory.create(
@ -429,10 +444,13 @@ def test_submit_completed_ko_review_page_as_ko(
ko = UserFactory.create()
PortfolioRoleFactory.create(
role=Roles.get("officer"),
portfolio=portfolio,
user=ko,
status=PortfolioStatus.ACTIVE,
permission_sets=[
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO),
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING),
],
)
task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko)
@ -470,10 +488,13 @@ def test_submit_completed_ko_review_page_as_ko(
def test_so_review_page(app, client, user_session, portfolio):
so = UserFactory.create()
PortfolioRoleFactory.create(
role=Roles.get("officer"),
portfolio=portfolio,
user=so,
status=PortfolioStatus.ACTIVE,
permission_sets=[
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO),
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING),
],
)
task_order = TaskOrderFactory.create(portfolio=portfolio, security_officer=so)
@ -508,10 +529,13 @@ def test_so_review_page(app, client, user_session, portfolio):
def test_submit_so_review(app, client, user_session, portfolio):
so = UserFactory.create()
PortfolioRoleFactory.create(
role=Roles.get("officer"),
portfolio=portfolio,
user=so,
status=PortfolioStatus.ACTIVE,
permission_sets=[
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO),
PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING),
],
)
task_order = TaskOrderFactory.create(portfolio=portfolio, security_officer=so)
dd_254_data = DD254Factory.dictionary()
@ -548,10 +572,7 @@ def test_resend_invite_when_invalid_invite_officer(
)
PortfolioRoleFactory.create(
role=Roles.get("owner"),
portfolio=portfolio,
user=user,
status=PortfolioStatus.ACTIVE,
portfolio=portfolio, user=user, status=PortfolioStatus.ACTIVE
)
user_session(user)
@ -580,10 +601,7 @@ def test_resend_invite_when_officer_type_missing(
)
PortfolioRoleFactory.create(
role=Roles.get("owner"),
portfolio=portfolio,
user=user,
status=PortfolioStatus.ACTIVE,
portfolio=portfolio, user=user, status=PortfolioStatus.ACTIVE
)
user_session(user)
@ -610,10 +628,7 @@ def test_resend_invite_when_ko(app, client, user_session, portfolio, user):
)
portfolio_role = PortfolioRoleFactory.create(
role=Roles.get("owner"),
portfolio=portfolio,
user=user,
status=PortfolioStatus.ACTIVE,
portfolio=portfolio, user=user, status=PortfolioStatus.ACTIVE
)
original_invitation = Invitations.create(
@ -654,10 +669,7 @@ def test_resend_invite_when_not_pending(app, client, user_session, portfolio, us
)
portfolio_role = PortfolioRoleFactory.create(
role=Roles.get("owner"),
portfolio=portfolio,
user=user,
status=PortfolioStatus.ACTIVE,
portfolio=portfolio, user=user, status=PortfolioStatus.ACTIVE
)
original_invitation = InvitationFactory.create(

View File

@ -28,9 +28,6 @@ def test_invite_officers_to_task_order(client, user_session, queue):
# owner and three officers are portfolio members
assert len(portfolio.members) == 4
roles = [member.role.name for member in portfolio.members]
# officers exist in roles
assert roles.count("officer") == 3
# email invitations are enqueued
assert len(queue.get_queue()) == 3
# task order has relationship to user for each officer role

View File

@ -340,3 +340,15 @@ def test_review_task_order_form(client, user_session, task_order):
)
assert response.status_code == 200
def test_update_task_order_clears_unnecessary_other_responses():
user = UserFactory.create()
to_data = TaskOrderFactory.dictionary()
to_data["complexity"] = ["storage"]
to_data["complexity_other"] = "something else"
to_data["dev_team"] = ["civilians"]
to_data["dev_team_other"] = "something else"
workflow = UpdateTaskOrderWorkflow(user, to_data)
assert workflow.task_order_form_data["complexity_other"] is None
assert workflow.task_order_form_data["dev_team_other"] is None

View File

@ -33,7 +33,7 @@ def test_non_owner_user_with_one_portfolio_redirected_to_portfolio_applications(
user = UserFactory.create()
portfolio = PortfolioFactory.create()
Portfolios._create_portfolio_role(
user, portfolio, "developer", status=PortfolioRoleStatus.ACTIVE
user, portfolio, status=PortfolioRoleStatus.ACTIVE
)
user_session(user)
@ -51,7 +51,7 @@ def test_non_owner_user_with_mulitple_portfolios_redirected_to_portfolios(
portfolio = PortfolioFactory.create()
portfolios.append(portfolio)
role = Portfolios._create_portfolio_role(
user, portfolio, "developer", status=PortfolioRoleStatus.ACTIVE
user, portfolio, status=PortfolioRoleStatus.ACTIVE
)
user_session(user)

View File

@ -4,7 +4,7 @@ import pytest
from flask import session, url_for
from .mocks import DOD_SDN_INFO, DOD_SDN, FIXTURE_EMAIL_ADDRESS
from atst.domain.users import Users
from atst.domain.roles import Roles
from atst.domain.permission_sets import PermissionSets
from atst.domain.exceptions import NotFoundError
from atst.domain.authnid.crl import CRLInvalidException
from atst.domain.auth import UNPROTECTED_ROUTES
@ -49,7 +49,7 @@ def test_successful_login_redirect_ccpo(client, monkeypatch):
monkeypatch.setattr(
"atst.domain.authnid.AuthenticationContext.authenticate", lambda *args: True
)
role = Roles.get("ccpo")
role = PermissionSets.get("ccpo")
monkeypatch.setattr(
"atst.domain.authnid.AuthenticationContext.get_user",
lambda *args: UserFactory.create(atat_role=role),

View File

@ -558,6 +558,12 @@ portfolios:
subheading: Team Management
admin:
activity_log_title: Activity Log
members:
permissions:
app_mgmt: App Mgmt
funding: Funding
reporting: Reporting
portfolio_mgmt: Portfolio Mgmt
testing:
example_string: Hello World
example_with_variables: 'Hello, {name}!'