commit
13bc7f56b0
@ -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 ###
|
@ -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 ###
|
@ -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")
|
@ -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",
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
)
|
||||
|
||||
|
169
atst/domain/permission_sets.py
Normal file
169
atst/domain/permission_sets.py
Normal 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
|
||||
)
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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()
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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()],
|
||||
)
|
@ -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"),
|
||||
)
|
72
atst/forms/portfolio_member.py
Normal file
72
atst/forms/portfolio_member.py
Normal 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()],
|
||||
)
|
@ -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
|
||||
|
19
atst/models/permission_set.py
Normal file
19
atst/models/permission_set.py
Normal 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
|
||||
)
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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",
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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.',
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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'>
|
||||
|
@ -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.'
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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,
|
||||
|
32
tests/domain/test_permission_sets.py
Normal file
32
tests/domain/test_permission_sets.py
Normal 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
|
||||
)
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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")
|
@ -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()],
|
||||
)
|
||||
|
||||
|
||||
|
@ -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"})
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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 (
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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),
|
||||
|
@ -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}!'
|
||||
|
Loading…
x
Reference in New Issue
Block a user