invites: Make it possible for non-admins to revoke multiuse invites.

This commit makes changes to allow non-admins to revoke multiuse
invitations created by them.
This commit is contained in:
Ujjawal Modi 2023-08-05 16:11:47 +05:30 committed by Tim Abbott
parent 9a96d19315
commit 5e31a6b1c0
5 changed files with 54 additions and 11 deletions

View File

@ -33,6 +33,15 @@ format used by the Zulip server that they are interacting with.
to create reusable invitation links. Previously, this endpoint was to create reusable invitation links. Previously, this endpoint was
restricted to admin users only. restricted to admin users only.
* `GET /invites`: Endpoint response for non-admin users now includes both
email invitations and reusable invitation links that they have created.
Previously, non-admin users could only create email invitations, and
therefore the response did not include reusable invitation links for these users.
* `DELETE /invites/multiuse/{invite_id}`: Non-admin users can now revoke
reusable invitation links they have created. Previously, only admin users could
create and revoke reusable invitation links.
**Feature level 208** **Feature level 208**
* [`POST /users/me/subscriptions`](/api/subscribe), * [`POST /users/me/subscriptions`](/api/subscribe),

View File

@ -1,5 +1,9 @@
{{#if is_multiuse}} {{#if is_multiuse}}
{{#if referred_by}}
<p>{{#tr}}Are you sure you want to revoke this invitation link created by <strong>{referred_by}</strong>?{{/tr}}</p> <p>{{#tr}}Are you sure you want to revoke this invitation link created by <strong>{referred_by}</strong>?{{/tr}}</p>
{{else}} {{else}}
<p>{{#tr}}Are you sure you want to revoke this invitation link?{{/tr}}</p>
{{/if}}
{{else}}
<p>{{#tr}}Are you sure you want to revoke the invitation to <strong>{email}</strong>?{{/tr}}</p> <p>{{#tr}}Are you sure you want to revoke the invitation to <strong>{email}</strong>?{{/tr}}</p>
{{/if}} {{/if}}

View File

@ -362,13 +362,19 @@ def do_get_invites_controlled_by_user(user_profile: UserProfile) -> List[Dict[st
) )
) )
if not user_profile.is_realm_admin: if user_profile.is_realm_admin:
# We do not return multiuse invites to non-admin users.
return invites
multiuse_confirmation_objs = Confirmation.objects.filter( multiuse_confirmation_objs = Confirmation.objects.filter(
realm=user_profile.realm, type=Confirmation.MULTIUSE_INVITE realm=user_profile.realm, type=Confirmation.MULTIUSE_INVITE
).filter(Q(expiry_date__gte=timezone_now()) | Q(expiry_date=None)) ).filter(Q(expiry_date__gte=timezone_now()) | Q(expiry_date=None))
else:
multiuse_invite_ids = MultiuseInvite.objects.filter(referred_by=user_profile).values_list(
"id", flat=True
)
multiuse_confirmation_objs = Confirmation.objects.filter(
type=Confirmation.MULTIUSE_INVITE,
object_id__in=multiuse_invite_ids,
).filter(Q(expiry_date__gte=timezone_now()) | Q(expiry_date=None))
for confirmation_obj in multiuse_confirmation_objs: for confirmation_obj in multiuse_confirmation_objs:
invite = confirmation_obj.content_object invite = confirmation_obj.content_object
assert invite is not None assert invite is not None

View File

@ -1747,8 +1747,11 @@ class InvitationsTestCase(InviteUserBase):
do_create_multiuse_invite_link( do_create_multiuse_invite_link(
user_profile, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes user_profile, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes
) )
self.assert_length(do_get_invites_controlled_by_user(user_profile), 5) do_create_multiuse_invite_link(
self.assert_length(do_get_invites_controlled_by_user(hamlet), 1) hamlet, PreregistrationUser.INVITE_AS["MEMBER"], invite_expires_in_minutes
)
self.assert_length(do_get_invites_controlled_by_user(user_profile), 6)
self.assert_length(do_get_invites_controlled_by_user(hamlet), 2)
self.assert_length(do_get_invites_controlled_by_user(othello), 1) self.assert_length(do_get_invites_controlled_by_user(othello), 1)
def test_successful_get_open_invitations(self) -> None: def test_successful_get_open_invitations(self) -> None:
@ -1999,6 +2002,26 @@ class InvitationsTestCase(InviteUserBase):
confirmation_settings.STATUS_REVOKED, confirmation_settings.STATUS_REVOKED,
) )
# Test non-admins can only delete invitations created by them.
multiuse_invite = MultiuseInvite.objects.create(
referred_by=self.example_user("hamlet"), realm=zulip_realm
)
create_confirmation_link(
multiuse_invite, Confirmation.MULTIUSE_INVITE, validity_in_minutes=validity_in_minutes
)
self.login("cordelia")
error_result = self.client_delete("/json/invites/multiuse/" + str(multiuse_invite.id))
self.assert_json_error(error_result, "Must be an organization administrator")
self.login("hamlet")
result = self.client_delete("/json/invites/multiuse/" + str(multiuse_invite.id))
self.assertEqual(result.status_code, 200)
self.assertEqual(
MultiuseInvite.objects.get(id=multiuse_invite.id).status,
confirmation_settings.STATUS_REVOKED,
)
# Test deleting multiuse invite from another realm # Test deleting multiuse invite from another realm
mit_realm = get_realm("zephyr") mit_realm = get_realm("zephyr")
multiuse_invite_in_mit = MultiuseInvite.objects.create( multiuse_invite_in_mit = MultiuseInvite.objects.create(

View File

@ -14,7 +14,7 @@ from zerver.actions.invites import (
do_revoke_multi_use_invite, do_revoke_multi_use_invite,
do_revoke_user_invite, do_revoke_user_invite,
) )
from zerver.decorator import require_member_or_admin, require_realm_admin from zerver.decorator import require_member_or_admin
from zerver.lib.exceptions import JsonableError, OrganizationOwnerRequiredError from zerver.lib.exceptions import JsonableError, OrganizationOwnerRequiredError
from zerver.lib.request import REQ, has_request_variables from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_success from zerver.lib.response import json_success
@ -140,7 +140,7 @@ def revoke_user_invite(
return json_success(request) return json_success(request)
@require_realm_admin @require_member_or_admin
@has_request_variables @has_request_variables
def revoke_multiuse_invite( def revoke_multiuse_invite(
request: HttpRequest, user_profile: UserProfile, invite_id: int request: HttpRequest, user_profile: UserProfile, invite_id: int
@ -153,6 +153,7 @@ def revoke_multiuse_invite(
if invite.realm != user_profile.realm: if invite.realm != user_profile.realm:
raise JsonableError(_("No such invitation")) raise JsonableError(_("No such invitation"))
if invite.referred_by_id != user_profile.id:
check_role_based_permissions(invite.invited_as, user_profile, require_admin=True) check_role_based_permissions(invite.invited_as, user_profile, require_admin=True)
if invite.status == confirmation_settings.STATUS_REVOKED: if invite.status == confirmation_settings.STATUS_REVOKED: