Revoke invitation #160300315
This commit is contained in:
dandds 2018-11-08 10:23:22 -05:00 committed by GitHub
commit 42ecdaa275
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 91 additions and 2 deletions

View File

@ -99,3 +99,8 @@ class Invitations(object):
db.session.commit() db.session.commit()
return invite return invite
@classmethod
def revoke(cls, token):
invite = Invitations._get(token)
return Invitations._update_status(invite, InvitationStatus.REVOKED)

View File

@ -50,6 +50,18 @@ class Workspaces(object):
return workspace return workspace
@classmethod
def get_for_update_member(cls, user, workspace_id):
workspace = WorkspacesQuery.get(workspace_id)
Authorization.check_workspace_permission(
user,
workspace,
Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE,
"update a workspace member",
)
return workspace
@classmethod @classmethod
def get_by_request(cls, request): def get_by_request(cls, request):
return WorkspacesQuery.get_by_request(request) return WorkspacesQuery.get_by_request(request)

View File

@ -51,7 +51,9 @@ class WorkspaceRole(Base, mixins.TimestampsMixin, mixins.AuditableMixin):
if self.status == Status.ACTIVE: if self.status == Status.ACTIVE:
return "Active" return "Active"
elif self.latest_invitation: elif self.latest_invitation:
if self.latest_invitation.is_rejected_expired: if self.latest_invitation.is_revoked:
return "Revoked"
elif self.latest_invitation.is_rejected_expired:
return "Invite expired" return "Invite expired"
elif self.latest_invitation.is_rejected_wrong_user: elif self.latest_invitation.is_rejected_wrong_user:
return "Error on invite" return "Error on invite"

View File

@ -357,10 +357,18 @@ def update_member(workspace_id, member_id):
) )
@bp.route("/workspaces/invitation/<token>", methods=["GET"]) @bp.route("/workspaces/invitations/<token>", methods=["GET"])
def accept_invitation(token): def accept_invitation(token):
invite = Invitations.accept(g.current_user, token) invite = Invitations.accept(g.current_user, token)
return redirect( return redirect(
url_for("workspaces.show_workspace", workspace_id=invite.workspace.id) url_for("workspaces.show_workspace", workspace_id=invite.workspace.id)
) )
@bp.route("/workspaces/<workspace_id>/invitations/<token>/revoke", methods=["POST"])
def revoke_invitation(workspace_id, token):
workspace = Workspaces.get_for_update_member(g.current_user, workspace_id)
Invitations.revoke(token)
return redirect(url_for("workspaces.workspace_members", workspace_id=workspace.id))

View File

@ -0,0 +1,19 @@
{% macro ConfirmationButton(btn_text, action, csrf_token, confirm_msg="Are you sure?", confirm_btn="Confirm", cancel_btn="Cancel") -%}
<v-popover placement='top-start'>
<template slot="popover">
<p>{{ confirm_msg }}</p>
<div class='action-group'>
<form method="POST" action="{{ action }}">
{{ csrf_token }}
<button class='usa-button usa-button-primary' type='submit'>
{{ confirm_btn }}
</button>
</form>
<button class='usa-button usa-button-secondary' v-close-popover>
{{ cancel_btn }}
</button>
</div>
</template>
<button class="tooltip-target" type="button">{{ btn_text }}</button>
</v-popover>
{%- endmacro %}

View File

@ -5,6 +5,7 @@
{% from "components/selector.html" import Selector %} {% from "components/selector.html" import Selector %}
{% from "components/options_input.html" import OptionsInput %} {% from "components/options_input.html" import OptionsInput %}
{% from "components/alert.html" import Alert %} {% from "components/alert.html" import Alert %}
{% from "components/confirmation_button.html" import ConfirmationButton %}
{% block content %} {% block content %}
@ -38,6 +39,13 @@
{% if editable %} {% if editable %}
<a href='{{ url_for("users.user") }}' class='icon-link'>edit account details</a> <a href='{{ url_for("users.user") }}' class='icon-link'>edit account details</a>
{% endif %} {% endif %}
{% if member.latest_invitation.is_pending %}
{{ ConfirmationButton(
"Revoke Invitation",
url_for("workspaces.revoke_invitation", workspace_id=workspace.id, token=member.latest_invitation.token),
form.csrf_token
) }}
{% endif %}
</div> </div>
</div> </div>

View File

@ -93,3 +93,13 @@ def test_accept_invitation_twice():
Invitations.accept(user, invite.token) Invitations.accept(user, invite.token)
with pytest.raises(InvitationError): with pytest.raises(InvitationError):
Invitations.accept(user, invite.token) Invitations.accept(user, invite.token)
def test_revoke_invitation():
workspace = WorkspaceFactory.create()
user = UserFactory.create()
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
invite = Invitations.create(ws_role, workspace.owner, user)
assert invite.is_pending
Invitations.revoke(invite.token)
assert invite.is_revoked

View File

@ -432,3 +432,28 @@ def test_user_accepts_expired_invite(client, user_session):
response = client.get(url_for("workspaces.accept_invitation", token=invite.token)) response = client.get(url_for("workspaces.accept_invitation", token=invite.token))
assert response.status_code == 404 assert response.status_code == 404
def test_revoke_invitation(client, user_session):
workspace = WorkspaceFactory.create()
user = UserFactory.create()
ws_role = WorkspaceRoleFactory.create(
user=user, workspace=workspace, status=WorkspaceRoleStatus.PENDING
)
invite = InvitationFactory.create(
user_id=user.id,
workspace_role_id=ws_role.id,
status=InvitationStatus.REJECTED_EXPIRED,
expiration_time=datetime.datetime.now() - datetime.timedelta(seconds=1),
)
user_session(workspace.owner)
response = client.post(
url_for(
"workspaces.revoke_invitation",
workspace_id=workspace.id,
token=invite.token,
)
)
assert response.status_code == 302
assert invite.is_revoked