remove accepted column from workspace_roles

This commit is contained in:
dandds 2018-10-26 16:07:10 -04:00
parent 5c5f9c6c9c
commit b81a831c85
15 changed files with 148 additions and 49 deletions

View File

@ -0,0 +1,28 @@
"""remove accepted from workspace_role
Revision ID: 67955a4abaef
Revises: e62bcc460c26
Create Date: 2018-10-26 13:57:29.480236
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '67955a4abaef'
down_revision = 'e62bcc460c26'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('workspace_roles', 'accepted')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('workspace_roles', sa.Column('accepted', sa.BOOLEAN(), autoincrement=False, nullable=True))
# ### end Alembic commands ###

View File

@ -6,8 +6,7 @@ from atst.domain.exceptions import UnauthorizedError
class Authorization(object): class Authorization(object):
@classmethod @classmethod
def has_workspace_permission(cls, user, workspace, permission): def has_workspace_permission(cls, user, workspace, permission):
workspace_user = WorkspaceUsers.get(workspace.id, user.id) return permission in WorkspaceUsers.workspace_user_permissions(workspace, user)
return permission in workspace_user.permissions()
@classmethod @classmethod
def has_atat_permission(cls, user, permission): def has_atat_permission(cls, user, permission):

View File

@ -43,6 +43,20 @@ class Invitations(object):
return invite return invite
@classmethod
def create_for_owner(cls, workspace, user):
invite = Invitation(
workspace=workspace,
inviter=user,
user=user,
status=InvitationStatus.ACCEPTED,
expiration_time=Invitations.current_expiration_time(),
)
db.session.add(invite)
db.session.commit()
return invite
@classmethod @classmethod
def accept(cls, invite_id): def accept(cls, invite_id):
invite = Invitations._get(invite_id) invite = Invitations._get(invite_id)

View File

@ -4,6 +4,7 @@ from atst.database import db
from atst.models.workspace_role import WorkspaceRole from atst.models.workspace_role import WorkspaceRole
from atst.models.workspace_user import WorkspaceUser from atst.models.workspace_user import WorkspaceUser
from atst.models.user import User from atst.models.user import User
from atst.models.invitation import Invitation, Status as InvitationStatus
from .roles import Roles from .roles import Roles
from .users import Users from .users import Users
@ -30,6 +31,32 @@ class WorkspaceUsers(object):
return WorkspaceUser(user, workspace_role) return WorkspaceUser(user, workspace_role)
@classmethod
def _get_active_workspace_role(cls, workspace_id, user_id):
try:
return (
db.session.query(WorkspaceRole)
.join(User)
.filter(User.id == user_id, WorkspaceRole.workspace_id == workspace_id)
.join(Invitation, WorkspaceRole.workspace_id == Invitation.workspace_id)
.filter(Invitation.user_id == WorkspaceRole.user_id)
.filter(Invitation.status == InvitationStatus.ACCEPTED)
.one()
)
except NoResultFound:
return None
@classmethod
def workspace_user_permissions(cls, workspace, user):
workspace_role = WorkspaceUsers._get_active_workspace_role(
workspace.id, user.id
)
atat_permissions = set(user.atat_role.permissions)
workspace_permissions = (
[] if workspace_role is None else workspace_role.role.permissions
)
return set(workspace_permissions).union(atat_permissions)
@classmethod @classmethod
def _get_workspace_role(cls, user, workspace_id): def _get_workspace_role(cls, user, workspace_id):
try: try:
@ -63,7 +90,7 @@ class WorkspaceUsers(object):
new_workspace_role.role = role new_workspace_role.role = role
except NoResultFound: except NoResultFound:
new_workspace_role = WorkspaceRole( new_workspace_role = WorkspaceRole(
user=user, role_id=role.id, workspace_id=workspace_id, accepted=False user=user, role_id=role.id, workspace_id=workspace_id
) )
user.workspace_roles.append(new_workspace_role) user.workspace_roles.append(new_workspace_role)

View File

@ -5,6 +5,7 @@ from atst.domain.common import Query
from atst.domain.exceptions import NotFoundError from atst.domain.exceptions import NotFoundError
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.models.invitation import Invitation, Status as InvitationStatus
class WorkspacesQuery(Query): class WorkspacesQuery(Query):
@ -24,8 +25,10 @@ class WorkspacesQuery(Query):
return ( return (
db.session.query(Workspace) db.session.query(Workspace)
.join(WorkspaceRole) .join(WorkspaceRole)
.join(Invitation)
.filter(WorkspaceRole.user == user) .filter(WorkspaceRole.user == user)
.filter(WorkspaceRole.accepted == True) .filter(Invitation.user == user)
.filter(Invitation.status == InvitationStatus.ACCEPTED)
.all() .all()
) )

View File

@ -3,6 +3,7 @@ from atst.domain.authz import Authorization
from atst.models.permissions import Permissions from atst.models.permissions import Permissions
from atst.domain.users import Users from atst.domain.users import Users
from atst.domain.workspace_users import WorkspaceUsers from atst.domain.workspace_users import WorkspaceUsers
from atst.domain.invitations import Invitations
from .query import WorkspacesQuery from .query import WorkspacesQuery
from .scopes import ScopedWorkspace from .scopes import ScopedWorkspace
@ -13,9 +14,8 @@ class Workspaces(object):
def create(cls, request, name=None): def create(cls, request, name=None):
name = name or request.displayname name = name or request.displayname
workspace = WorkspacesQuery.create(request=request, name=name) workspace = WorkspacesQuery.create(request=request, name=name)
Workspaces._create_workspace_role( Workspaces._create_workspace_role(request.creator, workspace, "owner")
request.creator, workspace, "owner", accepted=True Invitations.create_for_owner(workspace, request.creator)
)
WorkspacesQuery.add_and_commit(workspace) WorkspacesQuery.add_and_commit(workspace)
return workspace return workspace
@ -109,11 +109,9 @@ class Workspaces(object):
return WorkspaceUsers.update_role(member, workspace.id, role_name) return WorkspaceUsers.update_role(member, workspace.id, role_name)
@classmethod @classmethod
def _create_workspace_role(cls, user, workspace, role_name, accepted=False): def _create_workspace_role(cls, user, workspace, role_name):
role = Roles.get(role_name) role = Roles.get(role_name)
workspace_role = WorkspacesQuery.create_workspace_role( workspace_role = WorkspacesQuery.create_workspace_role(user, role, workspace)
user, role, workspace, accepted=accepted
)
WorkspacesQuery.add_and_commit(workspace_role) WorkspacesQuery.add_and_commit(workspace_role)
return workspace_role return workspace_role
@ -123,11 +121,3 @@ class Workspaces(object):
workspace.name = new_data["name"] workspace.name = new_data["name"]
WorkspacesQuery.add_and_commit(workspace) WorkspacesQuery.add_and_commit(workspace)
@classmethod
def accept_workspace_role(cls, user, workspace):
workspace_role = WorkspacesQuery.get_role_for_workspace_and_user(
workspace, user
)
workspace_role.accepted = True
WorkspacesQuery.add_and_commit(workspace_role)

View File

@ -49,6 +49,6 @@ class Workspace(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
return self.id return self.id
def __repr__(self): def __repr__(self):
return "<Workspace(name='{}', request='{}', task_order='{}', user_count='{}', id='{}')>".format( return "<Workspace(name='{}', request='{}', user_count='{}', id='{}')>".format(
self.name, self.request_id, self.task_order.number, self.user_count, self.id self.name, self.request_id, self.user_count, self.id
) )

View File

@ -1,4 +1,4 @@
from sqlalchemy import Index, ForeignKey, Column, Boolean from sqlalchemy import Index, ForeignKey, Column
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
@ -22,8 +22,6 @@ class WorkspaceRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=False UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=False
) )
accepted = Column(Boolean)
def __repr__(self): def __repr__(self):
return "<WorkspaceRole(role='{}', workspace='{}', user_id='{}', id='{}')>".format( return "<WorkspaceRole(role='{}', workspace='{}', user_id='{}', id='{}')>".format(
self.role.name, self.workspace.name, self.user_id, self.id self.role.name, self.workspace.name, self.user_id, self.id

View File

@ -12,9 +12,7 @@ class WorkspaceUser(object):
def permissions(self): def permissions(self):
atat_permissions = set(self.user.atat_role.permissions) atat_permissions = set(self.user.atat_role.permissions)
workspace_permissions = ( workspace_permissions = (
[] [] if self.workspace_role is None else self.workspace_role.role.permissions
if self.workspace_role is None or not self.is_accepted
else self.workspace_role.role.permissions
) )
return set(workspace_permissions).union(atat_permissions) return set(workspace_permissions).union(atat_permissions)
@ -81,10 +79,3 @@ class WorkspaceUser(object):
self.workspace.name, self.workspace.name,
self.num_environment_roles, self.num_environment_roles,
) )
@property
def is_accepted(self):
if self.workspace_role:
return self.workspace_role.accepted
return False

View File

@ -48,7 +48,7 @@ def make_error_pages(app):
log_error(e) log_error(e)
return ( return (
render_template( render_template(
"error.html", message="The invitation you followed has expired." "error.html", message="The invitation link you clicked is invalid."
), ),
404, 404,
) )

View File

@ -340,10 +340,10 @@ def update_member(workspace_id, member_id):
@bp.route("/workspaces/invitation/<invite_id>", methods=["GET"]) @bp.route("/workspaces/invitation/<invite_id>", methods=["GET"])
def accept_invitation(invite_id): def accept_invitation(invite_id):
# TODO: check that the current_user DOD ID matches the user associated with
# the invitation
invite = Invitations.accept(invite_id) invite = Invitations.accept(invite_id)
Workspaces.accept_workspace_role(invite.user, invite.workspace)
return redirect( return redirect(
url_for("workspaces.show_workspace", workspace_id=invite.workspace.id) url_for("workspaces.show_workspace", workspace_id=invite.workspace.id)
) )

View File

@ -6,8 +6,9 @@ from atst.domain.workspaces import Workspaces
from atst.domain.workspace_users import WorkspaceUsers from atst.domain.workspace_users import WorkspaceUsers
from atst.domain.projects import Projects from atst.domain.projects import Projects
from atst.domain.environments import Environments from atst.domain.environments import Environments
from atst.models.invitation import Status as InvitationStatus
from tests.factories import RequestFactory, UserFactory from tests.factories import RequestFactory, UserFactory, InvitationFactory
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
@ -219,7 +220,12 @@ def test_scoped_workspace_returns_all_projects_for_workspace_admin(
admin = Workspaces.add_member( admin = Workspaces.add_member(
workspace, UserFactory.from_atat_role("default"), "admin" workspace, UserFactory.from_atat_role("default"), "admin"
).user ).user
Workspaces.accept_workspace_role(admin, workspace) InvitationFactory.create(
user=admin,
inviter=workspace.owner,
workspace=workspace,
status=InvitationStatus.ACCEPTED,
)
scoped_workspace = Workspaces.get(admin, workspace.id) scoped_workspace = Workspaces.get(admin, workspace.id)
assert len(scoped_workspace.projects) == 5 assert len(scoped_workspace.projects) == 5
@ -248,7 +254,12 @@ def test_for_user_returns_assigned_workspaces_for_user(workspace, workspace_owne
bob = UserFactory.from_atat_role("default") bob = UserFactory.from_atat_role("default")
Workspaces.add_member(workspace, bob, "developer") Workspaces.add_member(workspace, bob, "developer")
Workspaces.create(RequestFactory.create()) Workspaces.create(RequestFactory.create())
Workspaces.accept_workspace_role(bob, workspace) InvitationFactory.create(
user=bob,
inviter=workspace.owner,
workspace=workspace,
status=InvitationStatus.ACCEPTED,
)
bobs_workspaces = Workspaces.for_user(bob) bobs_workspaces = Workspaces.for_user(bob)
@ -275,12 +286,23 @@ def test_for_user_returns_all_workspaces_for_ccpo(workspace, workspace_owner):
def test_get_for_update_information(): def test_get_for_update_information():
workspace_owner = UserFactory.create() workspace_owner = UserFactory.create()
workspace = Workspaces.create(RequestFactory.create(creator=workspace_owner)) workspace = Workspaces.create(RequestFactory.create(creator=workspace_owner))
InvitationFactory.create(
user=workspace_owner,
inviter=workspace_owner,
workspace=workspace,
status=InvitationStatus.ACCEPTED,
)
owner_ws = Workspaces.get_for_update_information(workspace_owner, workspace.id) owner_ws = Workspaces.get_for_update_information(workspace_owner, workspace.id)
assert workspace == owner_ws assert workspace == owner_ws
admin = UserFactory.create() admin = UserFactory.create()
Workspaces.add_member(workspace, admin, "admin") Workspaces.add_member(workspace, admin, "admin")
Workspaces.accept_workspace_role(admin, workspace) InvitationFactory.create(
user=admin,
inviter=workspace_owner,
workspace=workspace,
status=InvitationStatus.ACCEPTED,
)
admin_ws = Workspaces.get_for_update_information(admin, workspace.id) admin_ws = Workspaces.get_for_update_information(admin, workspace.id)
assert workspace == admin_ws assert workspace == admin_ws

View File

@ -257,7 +257,13 @@ class WorkspaceFactory(Base):
workspace.request.creator = owner workspace.request.creator = owner
WorkspaceRoleFactory.create( WorkspaceRoleFactory.create(
workspace=workspace, role=Roles.get("owner"), user=owner, accepted=True workspace=workspace, role=Roles.get("owner"), user=owner
)
InvitationFactory.create(
user=owner,
inviter=owner,
workspace=workspace,
status=InvitationStatus.ACCEPTED,
) )
for member in members: for member in members:
@ -275,7 +281,12 @@ class WorkspaceFactory(Base):
user = UserFactory.create() user = UserFactory.create()
workspace = WorkspaceFactory.create() workspace = WorkspaceFactory.create()
Workspaces._create_workspace_role(user, workspace, role) Workspaces._create_workspace_role(user, workspace, role)
Workspaces.accept_workspace_role(user, workspace) InvitationFactory.create(
user=user,
inviter=workspace.owner,
workspace=workspace,
status=InvitationStatus.ACCEPTED,
)
return user, workspace return user, workspace

View File

@ -1,12 +1,23 @@
from tests.factories import UserFactory, WorkspaceFactory, RequestFactory from tests.factories import (
UserFactory,
WorkspaceFactory,
RequestFactory,
InvitationFactory,
)
from atst.domain.workspaces import Workspaces from atst.domain.workspaces import Workspaces
from atst.models.invitation import Status as InvitationStatus
def test_user_with_workspaces_has_workspaces_nav(client, user_session): def test_user_with_workspaces_has_workspaces_nav(client, user_session):
user = UserFactory.create() user = UserFactory.create()
workspace = WorkspaceFactory.create() workspace = WorkspaceFactory.create()
Workspaces._create_workspace_role(user, workspace, "developer") Workspaces._create_workspace_role(user, workspace, "developer")
Workspaces.accept_workspace_role(user, workspace) InvitationFactory.create(
user=user,
inviter=workspace.owner,
workspace=workspace,
status=InvitationStatus.ACCEPTED,
)
user_session(user) user_session(user)
response = client.get("/home", follow_redirects=True) response = client.get("/home", follow_redirects=True)

View File

@ -341,11 +341,16 @@ def test_new_member_accept_invalid_invite(client, user_session):
def test_user_who_has_not_accepted_workspace_invite_cannot_view(client, user_session): def test_user_who_has_not_accepted_workspace_invite_cannot_view(client, user_session):
user = UserFactory.create() user = UserFactory.create()
workspace = WorkspaceFactory.create() workspace = WorkspaceFactory.create()
Workspaces.create_member( Invitations.create_for_owner(workspace, workspace.owner)
workspace.owner,
workspace, # create user in workspace with invitation
{"workspace_role": "developer", **user.to_dictionary()}, user_session(workspace.owner)
response = client.post(
url_for("workspaces.create_member", workspace_id=workspace.id),
data={"workspace_role": "developer", **user.to_dictionary()},
) )
# user tries to view workspace before accepting invitation
user_session(user) user_session(user)
response = client.get("/workspaces/{}/projects".format(workspace.id)) response = client.get("/workspaces/{}/projects".format(workspace.id))
assert response.status_code == 404 assert response.status_code == 404