Merge pull request #252 from dod-ccpo/view-member-list-workspace-owner

View member list as workspace owner
This commit is contained in:
richard-dds 2018-09-06 16:53:19 -04:00 committed by GitHub
commit a96af2d095
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 148 additions and 12 deletions

View File

@ -0,0 +1,53 @@
"""add view_workspace_members_permission
Revision ID: ad30159ef19b
Revises: 2c2a2af465d3
Create Date: 2018-09-05 11:17:17.204089
"""
from alembic import op
from sqlalchemy.orm.session import Session
from atst.models.role import Role
from atst.models.permissions import Permissions
# revision identifiers, used by Alembic.
revision = 'ad30159ef19b'
down_revision = 'c1d074288e99'
branch_labels = None
depends_on = None
def upgrade():
session = Session(bind=op.get_bind())
all_roles_but_default = session.query(Role).filter(Role.name != "default").all()
for role in all_roles_but_default:
role.add_permission(Permissions.VIEW_WORKSPACE)
session.add(role)
owner_and_ccpo = session.query(Role).filter(Role.name.in_(["owner", "ccpo", "admin"])).all()
for role in owner_and_ccpo:
role.add_permission(Permissions.VIEW_WORKSPACE_MEMBERS)
session.add(role)
session.flush()
session.commit()
def downgrade():
session = Session(bind=op.get_bind())
all_roles_but_default = session.query(Role).filter(Role.name != "default").all()
for role in all_roles_but_default:
role.remove_permission(Permissions.VIEW_WORKSPACE)
session.add(role)
owner_and_ccpo = session.query(Role).filter(Role.name.in_(["owner", "ccpo"])).all()
for role in owner_and_ccpo:
role.remove_permission(Permissions.VIEW_WORKSPACE_MEMBERS)
session.add(role)
session.flush()
session.commit()

View File

@ -3,7 +3,7 @@ from sqlalchemy.orm.exc import NoResultFound
from atst.database import db from atst.database import db
from atst.models.workspace import Workspace from atst.models.workspace import Workspace
from atst.models.workspace_role import WorkspaceRole from atst.models.workspace_role import WorkspaceRole
from atst.domain.exceptions import NotFoundError, UnauthorizedError from atst.domain.exceptions import NotFoundError
from atst.domain.roles import Roles from atst.domain.roles import Roles
from atst.domain.authz import Authorization from atst.domain.authz import Authorization
from atst.models.permissions import Permissions from atst.models.permissions import Permissions
@ -25,19 +25,16 @@ class Workspaces(object):
@classmethod @classmethod
def get(cls, user, workspace_id): def get(cls, user, workspace_id):
try: workspace = Workspaces._get(workspace_id)
workspace = db.session.query(Workspace).filter_by(id=workspace_id).one() Authorization.check_workspace_permission(
except NoResultFound: user, workspace, Permissions.VIEW_WORKSPACE, "get workspace"
raise NotFoundError("workspace") )
if not Authorization.is_in_workspace(user, workspace):
raise UnauthorizedError(user, "get workspace")
return workspace return workspace
@classmethod @classmethod
def get_for_update(cls, user, workspace_id): def get_for_update(cls, user, workspace_id):
workspace = Workspaces.get(user, workspace_id) workspace = Workspaces._get(workspace_id)
Authorization.check_workspace_permission( Authorization.check_workspace_permission(
user, workspace, Permissions.ADD_APPLICATION_IN_WORKSPACE, "add project" user, workspace, Permissions.ADD_APPLICATION_IN_WORKSPACE, "add project"
) )
@ -53,6 +50,18 @@ class Workspaces(object):
return workspace return workspace
@classmethod
def get_with_members(cls, user, workspace_id):
workspace = Workspaces._get(workspace_id)
Authorization.check_workspace_permission(
user,
workspace,
Permissions.VIEW_WORKSPACE_MEMBERS,
"view workspace members",
)
return workspace
@classmethod @classmethod
def get_many(cls, user): def get_many(cls, user):
workspaces = ( workspaces = (
@ -100,3 +109,12 @@ class Workspaces(object):
workspace_role = WorkspaceRole(user=user, role=role, workspace=workspace) workspace_role = WorkspaceRole(user=user, role=role, workspace=workspace)
db.session.add(workspace_role) db.session.add(workspace_role)
return workspace_role return workspace_role
@classmethod
def _get(cls, workspace_id):
try:
workspace = db.session.query(Workspace).filter_by(id=workspace_id).one()
except NoResultFound:
raise NotFoundError("workspace")
return workspace

View File

@ -23,6 +23,8 @@ class Permissions(object):
DEACTIVATE_WORKSPACE = "deactivate_workspace" DEACTIVATE_WORKSPACE = "deactivate_workspace"
VIEW_ATAT_PERMISSIONS = "view_atat_permissions" VIEW_ATAT_PERMISSIONS = "view_atat_permissions"
TRANSFER_OWNERSHIP_OF_WORKSPACE = "transfer_ownership_of_workspace" TRANSFER_OWNERSHIP_OF_WORKSPACE = "transfer_ownership_of_workspace"
VIEW_WORKSPACE_MEMBERS = "view_workspace_members"
VIEW_WORKSPACE = "view_workspace"
ADD_APPLICATION_IN_WORKSPACE = "add_application_in_workspace" ADD_APPLICATION_IN_WORKSPACE = "add_application_in_workspace"
DELETE_APPLICATION_IN_WORKSPACE = "delete_application_in_workspace" DELETE_APPLICATION_IN_WORKSPACE = "delete_application_in_workspace"

View File

@ -1,5 +1,6 @@
from sqlalchemy import String, Column from sqlalchemy import String, Column
from sqlalchemy.dialects.postgresql import ARRAY from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm.attributes import flag_modified
from atst.models import Base from atst.models import Base
from .types import Id from .types import Id
@ -12,3 +13,15 @@ class Role(Base):
name = Column(String, index=True, unique=True) name = Column(String, index=True, unique=True)
description = Column(String) description = Column(String)
permissions = Column(ARRAY(String), index=True, server_default="{}") permissions = Column(ARRAY(String), index=True, server_default="{}")
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")

View File

@ -4,8 +4,8 @@ from sqlalchemy.orm import relationship
from atst.models import Base from atst.models import Base
from atst.models.types import Id from atst.models.types import Id
from atst.models.mixins import TimestampsMixin from atst.models.mixins import TimestampsMixin
from atst.utils import first_or_none
from atst.models.workspace_user import WorkspaceUser from atst.models.workspace_user import WorkspaceUser
from atst.utils import first_or_none
class Workspace(Base, TimestampsMixin): class Workspace(Base, TimestampsMixin):

View File

@ -38,7 +38,7 @@ class WorkspaceUser(object):
@property @property
def status(self): def status(self):
return "radical" return "active"
@property @property
def has_environment_roles(self): def has_environment_roles(self):

View File

@ -63,7 +63,7 @@ def show_workspace(workspace_id):
@bp.route("/workspaces/<workspace_id>/members") @bp.route("/workspaces/<workspace_id>/members")
def workspace_members(workspace_id): def workspace_members(workspace_id):
workspace = Workspaces.get(g.current_user, workspace_id) workspace = Workspaces.get_with_members(g.current_user, workspace_id)
return render_template("workspaces/members/index.html", workspace=workspace) return render_template("workspaces/members/index.html", workspace=workspace)

View File

@ -15,6 +15,29 @@ from atst.domain.exceptions import AlreadyExistsError
from tests.factories import RequestFactory from tests.factories import RequestFactory
from atst.routes.dev import _DEV_USERS as DEV_USERS from atst.routes.dev import _DEV_USERS as DEV_USERS
WORKSPACE_USERS = [
{
"first_name": "Danny",
"last_name": "Knight",
"email": "knight@mil.gov",
"workspace_role": "developer",
"dod_id": "0000000001"
},
{
"first_name": "Mario",
"last_name": "Hudson",
"email": "hudson@mil.gov",
"workspace_role": "ccpo",
"dod_id": "0000000002"
},
{
"first_name": "Louise",
"last_name": "Greer",
"email": "greer@mil.gov",
"workspace_role": "admin",
"dod_id": "0000000003"
},
]
def seed_db(): def seed_db():
users = [] users = []
@ -41,6 +64,9 @@ def seed_db():
requests.append(request) requests.append(request)
workspace = Workspaces.create(requests[0], name="{}'s workspace".format(user.first_name)) workspace = Workspaces.create(requests[0], name="{}'s workspace".format(user.first_name))
for workspace_user in WORKSPACE_USERS:
Workspaces.create_member(user, workspace, workspace_user)
Projects.create( Projects.create(
workspace=workspace, workspace=workspace,
name="First Project", name="First Project",

View File

@ -155,3 +155,27 @@ def test_need_permission_to_update_workspace_user_role():
with pytest.raises(UnauthorizedError): with pytest.raises(UnauthorizedError):
Workspaces.update_member(random_user, workspace, member, role_name) Workspaces.update_member(random_user, workspace, member, role_name)
def test_owner_can_view_workspace_members():
owner = UserFactory.create()
workspace = Workspaces.create(RequestFactory.create(creator=owner))
workspace = Workspaces.get_with_members(owner, workspace.id)
assert workspace
def test_ccpo_can_view_workspace_members():
workspace = Workspaces.create(RequestFactory.create(creator=UserFactory.create()))
ccpo = UserFactory.from_atat_role("ccpo")
workspace = Workspaces.get_with_members(ccpo, workspace.id)
assert workspace
def test_random_user_cannot_view_workspace_members():
workspace = Workspaces.create(RequestFactory.create(creator=UserFactory.create()))
developer = UserFactory.from_atat_role("developer")
with pytest.raises(UnauthorizedError):
workspace = Workspaces.get_with_members(developer, workspace.id)