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

Portfolio perms
This commit is contained in:
dandds
2019-03-18 08:40:35 -04:00
committed by GitHub
60 changed files with 940 additions and 738 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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