diff --git a/alembic/versions/c7feaa7b6b0c_add_accepted_column_to_workspace_role.py b/alembic/versions/c7feaa7b6b0c_add_accepted_column_to_workspace_role.py new file mode 100644 index 00000000..9a1fb904 --- /dev/null +++ b/alembic/versions/c7feaa7b6b0c_add_accepted_column_to_workspace_role.py @@ -0,0 +1,28 @@ +"""add accepted column to workspace role + +Revision ID: c7feaa7b6b0c +Revises: 5284ac1ac77c +Create Date: 2018-10-25 11:44:17.654892 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c7feaa7b6b0c' +down_revision = '5284ac1ac77c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('workspace_roles', sa.Column('accepted', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('workspace_roles', 'accepted') + # ### end Alembic commands ### diff --git a/atst/domain/workspace_users.py b/atst/domain/workspace_users.py index d37ec736..84e20c50 100644 --- a/atst/domain/workspace_users.py +++ b/atst/domain/workspace_users.py @@ -63,7 +63,7 @@ class WorkspaceUsers(object): new_workspace_role.role = role except NoResultFound: new_workspace_role = WorkspaceRole( - user=user, role_id=role.id, workspace_id=workspace_id + user=user, role_id=role.id, workspace_id=workspace_id, accepted=False ) user.workspace_roles.append(new_workspace_role) diff --git a/atst/domain/workspaces/query.py b/atst/domain/workspaces/query.py index ec32c73c..45f29b3b 100644 --- a/atst/domain/workspaces/query.py +++ b/atst/domain/workspaces/query.py @@ -25,9 +25,19 @@ class WorkspacesQuery(Query): db.session.query(Workspace) .join(WorkspaceRole) .filter(WorkspaceRole.user == user) + .filter(WorkspaceRole.accepted == True) .all() ) @classmethod def create_workspace_role(cls, user, role, workspace): return WorkspaceRole(user=user, role=role, workspace=workspace) + + @classmethod + def get_role_for_workspace_and_user(cls, workspace, user): + return ( + db.session.query(WorkspaceRole) + .filter(WorkspaceRole.user == user) + .filter(WorkspaceRole.workspace == workspace) + .one() + ) diff --git a/atst/domain/workspaces/workspaces.py b/atst/domain/workspaces/workspaces.py index dc2f4202..b3884ed2 100644 --- a/atst/domain/workspaces/workspaces.py +++ b/atst/domain/workspaces/workspaces.py @@ -119,3 +119,11 @@ class Workspaces(object): workspace.name = new_data["name"] 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) diff --git a/atst/models/workspace_role.py b/atst/models/workspace_role.py index 87c8f46f..93da1488 100644 --- a/atst/models/workspace_role.py +++ b/atst/models/workspace_role.py @@ -1,4 +1,4 @@ -from sqlalchemy import Index, ForeignKey, Column +from sqlalchemy import Index, ForeignKey, Column, Boolean from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship @@ -22,6 +22,8 @@ class WorkspaceRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin): UUID(as_uuid=True), ForeignKey("users.id"), index=True, nullable=False ) + accepted = Column(Boolean) + def __repr__(self): return "".format( self.role.name, self.workspace.name, self.user_id, self.id diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index 674f9dd3..cc2f9f48 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -341,6 +341,7 @@ def update_member(workspace_id, member_id): @bp.route("/workspaces/invitation/", methods=["GET"]) def accept_invitation(invite_id): invite = Invitations.accept(invite_id) + Workspaces.accept_workspace_role(invite.user, invite.workspace) return redirect( url_for("workspaces.show_workspace", workspace_id=invite.workspace.id) diff --git a/tests/domain/test_workspaces.py b/tests/domain/test_workspaces.py index 79d8c57b..6141f9bf 100644 --- a/tests/domain/test_workspaces.py +++ b/tests/domain/test_workspaces.py @@ -247,11 +247,22 @@ def test_for_user_returns_assigned_workspaces_for_user(workspace, workspace_owne bob = UserFactory.from_atat_role("default") Workspaces.add_member(workspace, bob, "developer") Workspaces.create(RequestFactory.create()) + Workspaces.accept_workspace_role(bob, workspace) + bobs_workspaces = Workspaces.for_user(bob) assert len(bobs_workspaces) == 1 +def test_for_user_does_not_return_unaccepted_workspaces(workspace, workspace_owner): + bob = UserFactory.from_atat_role("default") + Workspaces.add_member(workspace, bob, "developer") + Workspaces.create(RequestFactory.create()) + bobs_workspaces = Workspaces.for_user(bob) + + assert len(bobs_workspaces) == 0 + + def test_for_user_returns_all_workspaces_for_ccpo(workspace, workspace_owner): sam = UserFactory.from_atat_role("ccpo") Workspaces.create(RequestFactory.create()) diff --git a/tests/routes/test_workspaces.py b/tests/routes/test_workspaces.py index 32a85f8e..2edaa0ee 100644 --- a/tests/routes/test_workspaces.py +++ b/tests/routes/test_workspaces.py @@ -316,7 +316,7 @@ def test_update_member_environment_role_with_no_data(client, user_session): assert EnvironmentRoles.get(user.id, env1_id).role == "developer" -def test_new_member_accept_valid_invite(client, user_session): +def test_new_member_accepts_valid_invite(client, user_session): owner = UserFactory.create() workspace = WorkspaceFactory.create() Workspaces._create_workspace_role(owner, workspace, "admin") @@ -324,15 +324,23 @@ def test_new_member_accept_valid_invite(client, user_session): user = UserFactory.create() member = WorkspaceUsers.add(user, workspace.id, "developer") invite = InvitationFactory.create(user_id=member.user.id, workspace_id=workspace.id) + + # the user does not have access to the workspace before accepting the invite + assert len(Workspaces.for_user(user)) == 0 + user_session(user) response = client.get(url_for("workspaces.accept_invitation", invite_id=invite.id)) + # user is redirected to the workspace view assert response.status_code == 302 assert ( url_for("workspaces.show_workspace", workspace_id=invite.workspace.id) in response.headers["Location"] ) + # the one-time use invite is no longer usable assert invite.valid == False + # the user has access to the workspace + assert len(Workspaces.for_user(user)) == 1 def test_new_member_accept_invalid_invite(client, user_session):