mirror of https://github.com/zulip/zulip.git
invitation: Make Member to see invitations sent by him/her.
Member of the org can able see list of invitations sent by him/her. given permission for the member to revoke and resend the invitations sent by him/her and added tests for test member can revoke and resend the invitations only sent by him/her. Fixes #14007.
This commit is contained in:
parent
fc107d2c24
commit
bbf5a5efed
|
@ -50,6 +50,7 @@ function populate_invites(invites_data) {
|
|||
name: 'admin_invites_list',
|
||||
modifier: function (item) {
|
||||
item.invited_absolute_time = timerender.absolute_time(item.invited * 1000);
|
||||
item.is_admin = page_params.is_admin;
|
||||
return render_admin_invites_list({ invite: item });
|
||||
},
|
||||
filter: {
|
||||
|
|
|
@ -7,9 +7,11 @@
|
|||
<span class="email">{{email}}</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
{{#if is_admin}}
|
||||
<td>
|
||||
<span class="referred_by">{{ref}}</span>
|
||||
</td>
|
||||
{{/if}}
|
||||
<td>
|
||||
<span class="invited_at">{{invited_absolute_time}}</span>
|
||||
</td>
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<div id="admin-invites-list" class="settings-section" data-name="invites-list-admin">
|
||||
{{#unless is_admin }}
|
||||
<div class="tip">{{t "Members can only view or manage invitations that you yourself sent." }}</div>
|
||||
{{/unless}}
|
||||
<a class="invite-user-link" href="#invite"><i class="fa fa-plus-circle" aria-hidden="true"></i>{{t "Invite more users" }}</a>
|
||||
<div>
|
||||
<h3 class="inline-block">{{t "Invites" }}</h3>
|
||||
|
@ -11,7 +14,9 @@
|
|||
<table class="table table-condensed table-striped">
|
||||
<thead>
|
||||
<th class="active" data-sort="invitee">{{t "Invitee" }}</th>
|
||||
{{#if is_admin }}
|
||||
<th data-sort="alphabetic" data-sort-prop="ref">{{t "Invited by" }}</th>
|
||||
{{/if}}
|
||||
<th data-sort="numeric" data-sort-prop="invited">{{t "Invited at" }}</th>
|
||||
<th data-sort="numeric" data-sort-prop="invited_as">{{t "Invited as" }}</th>
|
||||
<th class="actions">{{t "Actions" }}</th>
|
||||
|
|
|
@ -140,11 +140,13 @@
|
|||
<div class="text">{{ _('Custom profile fields') }}</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_admin %}
|
||||
{% if not guest %}
|
||||
<li tabindex="0" data-section="invites-list-admin">
|
||||
<i class="icon fa fa-user-plus" aria-hidden="true"></i>
|
||||
<div class="text">{{ _('Invitations') }}</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_admin %}
|
||||
<li tabindex="0" data-section="data-exports-admin">
|
||||
<i class="icon fa fa-database" aria-hidden="true"></i>
|
||||
<div class="text">{{ _('Data exports') }}</div>
|
||||
|
|
|
@ -5099,11 +5099,14 @@ def do_invite_users(user_profile: UserProfile,
|
|||
def do_get_user_invites(user_profile: UserProfile) -> List[Dict[str, Any]]:
|
||||
days_to_activate = settings.INVITATION_LINK_VALIDITY_DAYS
|
||||
active_value = getattr(confirmation_settings, 'STATUS_ACTIVE', 1)
|
||||
|
||||
lowest_datetime = timezone_now() - datetime.timedelta(days=days_to_activate)
|
||||
prereg_users = PreregistrationUser.objects.exclude(status=active_value).filter(
|
||||
invited_at__gte=lowest_datetime,
|
||||
referred_by__realm=user_profile.realm)
|
||||
base_query = PreregistrationUser.objects.exclude(status=active_value).filter(
|
||||
invited_at__gte=lowest_datetime)
|
||||
|
||||
if user_profile.is_realm_admin:
|
||||
prereg_users = base_query.filter(referred_by__realm=user_profile.realm)
|
||||
else:
|
||||
prereg_users = base_query.filter(referred_by=user_profile)
|
||||
|
||||
invites = []
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ from zerver.lib.send_email import send_future_email, FromAddress, \
|
|||
deliver_email
|
||||
from zerver.lib.initial_password import initial_password
|
||||
from zerver.lib.actions import (
|
||||
do_get_user_invites,
|
||||
do_deactivate_realm,
|
||||
do_deactivate_user,
|
||||
do_set_realm_property,
|
||||
|
@ -1469,6 +1470,26 @@ so we didn't send them an invitation. We did send invitations to everyone else!"
|
|||
"or been deactivated."], result)
|
||||
|
||||
class InvitationsTestCase(InviteUserBase):
|
||||
def test_do_get_user_invites(self) -> None:
|
||||
self.login('iago')
|
||||
user_profile = self.example_user("iago")
|
||||
hamlet = self.example_user('hamlet')
|
||||
othello = self.example_user('othello')
|
||||
prereg_user_one = PreregistrationUser(email="TestOne@zulip.com", referred_by=user_profile)
|
||||
prereg_user_one.save()
|
||||
prereg_user_two = PreregistrationUser(email="TestTwo@zulip.com", referred_by=user_profile)
|
||||
prereg_user_two.save()
|
||||
prereg_user_three = PreregistrationUser(email="TestThree@zulip.com", referred_by=hamlet)
|
||||
prereg_user_three.save()
|
||||
prereg_user_four = PreregistrationUser(email="TestFour@zulip.com", referred_by=othello)
|
||||
prereg_user_four.save()
|
||||
prereg_user_other_realm = PreregistrationUser(
|
||||
email="TestOne@zulip.com", referred_by=self.mit_user("sipbtest"))
|
||||
prereg_user_other_realm.save()
|
||||
self.assertEqual(len(do_get_user_invites(user_profile)), 4)
|
||||
self.assertEqual(len(do_get_user_invites(hamlet)), 1)
|
||||
self.assertEqual(len(do_get_user_invites(othello)), 1)
|
||||
|
||||
def test_successful_get_open_invitations(self) -> None:
|
||||
"""
|
||||
A GET call to /json/invites returns all unexpired invitations.
|
||||
|
@ -1537,6 +1558,46 @@ class InvitationsTestCase(InviteUserBase):
|
|||
lambda: ScheduledEmail.objects.get(address__iexact=invitee,
|
||||
type=ScheduledEmail.INVITATION_REMINDER))
|
||||
|
||||
def test_successful_member_delete_invitation(self) -> None:
|
||||
"""
|
||||
A DELETE call from member account to /json/invites/<ID> should delete the invite and
|
||||
any scheduled invitation reminder emails.
|
||||
"""
|
||||
user_profile = self.example_user('hamlet')
|
||||
self.login_user(user_profile)
|
||||
invitee = "DeleteMe@zulip.com"
|
||||
self.assert_json_success(self.invite(invitee, ['Denmark']))
|
||||
|
||||
# Verify that the scheduled email exists.
|
||||
prereg_user = PreregistrationUser.objects.get(email=invitee,
|
||||
referred_by=user_profile)
|
||||
ScheduledEmail.objects.get(address__iexact=invitee,
|
||||
type=ScheduledEmail.INVITATION_REMINDER)
|
||||
|
||||
# Verify another non-admin can't delete
|
||||
result = self.api_delete(self.example_user("othello"),
|
||||
'/api/v1/invites/' + str(prereg_user.id))
|
||||
self.assert_json_error(result, "Must be an organization administrator")
|
||||
|
||||
# Verify that the scheduled email still exists.
|
||||
prereg_user = PreregistrationUser.objects.get(email=invitee,
|
||||
referred_by=user_profile)
|
||||
ScheduledEmail.objects.get(address__iexact=invitee,
|
||||
type=ScheduledEmail.INVITATION_REMINDER)
|
||||
|
||||
# Verify deletion works.
|
||||
result = self.api_delete(user_profile,
|
||||
'/api/v1/invites/' + str(prereg_user.id))
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
result = self.api_delete(user_profile,
|
||||
'/api/v1/invites/' + str(prereg_user.id))
|
||||
self.assert_json_error(result, "No such invitation")
|
||||
|
||||
self.assertRaises(ScheduledEmail.DoesNotExist,
|
||||
lambda: ScheduledEmail.objects.get(address__iexact=invitee,
|
||||
type=ScheduledEmail.INVITATION_REMINDER))
|
||||
|
||||
def test_delete_multiuse_invite(self) -> None:
|
||||
"""
|
||||
A DELETE call to /json/invites/multiuse<ID> should delete the
|
||||
|
@ -1599,6 +1660,56 @@ class InvitationsTestCase(InviteUserBase):
|
|||
|
||||
self.check_sent_emails([invitee], custom_from_name="Zulip")
|
||||
|
||||
def test_successful_member_resend_invitation(self) -> None:
|
||||
"""A POST call from member a account to /json/invites/<ID>/resend
|
||||
should send an invitation reminder email and delete any
|
||||
scheduled invitation reminder email if they send the invite.
|
||||
"""
|
||||
self.login('hamlet')
|
||||
user_profile = self.example_user('hamlet')
|
||||
invitee = "resend_me@zulip.com"
|
||||
self.assert_json_success(self.invite(invitee, ['Denmark']))
|
||||
# Verify hamlet has only one invitation (Member can resend invitations only sent by him).
|
||||
invitation = PreregistrationUser.objects.filter(referred_by=user_profile)
|
||||
self.assertEqual(len(invitation), 1)
|
||||
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
||||
|
||||
# Verify and then clear from the outbox the original invite email
|
||||
self.check_sent_emails([invitee], custom_from_name="Zulip")
|
||||
from django.core.mail import outbox
|
||||
outbox.pop()
|
||||
|
||||
# Verify that the scheduled email exists.
|
||||
scheduledemail_filter = ScheduledEmail.objects.filter(
|
||||
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER)
|
||||
self.assertEqual(scheduledemail_filter.count(), 1)
|
||||
original_timestamp = scheduledemail_filter.values_list('scheduled_timestamp', flat=True)
|
||||
|
||||
# Resend invite
|
||||
result = self.client_post('/json/invites/' + str(prereg_user.id) + '/resend')
|
||||
self.assertEqual(ScheduledEmail.objects.filter(
|
||||
address__iexact=invitee, type=ScheduledEmail.INVITATION_REMINDER).count(), 1)
|
||||
|
||||
# Check that we have exactly one scheduled email, and that it is different
|
||||
self.assertEqual(scheduledemail_filter.count(), 1)
|
||||
self.assertNotEqual(original_timestamp,
|
||||
scheduledemail_filter.values_list('scheduled_timestamp', flat=True))
|
||||
|
||||
self.assertEqual(result.status_code, 200)
|
||||
error_result = self.client_post('/json/invites/' + str(9999) + '/resend')
|
||||
self.assert_json_error(error_result, "No such invitation")
|
||||
|
||||
self.check_sent_emails([invitee], custom_from_name="Zulip")
|
||||
|
||||
self.logout()
|
||||
self.login("othello")
|
||||
invitee = "TestOne@zulip.com"
|
||||
prereg_user_one = PreregistrationUser(email=invitee, referred_by=user_profile)
|
||||
prereg_user_one.save()
|
||||
prereg_user = PreregistrationUser.objects.get(email=invitee)
|
||||
error_result = self.client_post('/json/invites/' + str(prereg_user.id) + '/resend')
|
||||
self.assert_json_error(error_result, "Must be an organization administrator")
|
||||
|
||||
def test_accessing_invites_in_another_realm(self) -> None:
|
||||
inviter = UserProfile.objects.exclude(realm=get_realm('zulip')).first()
|
||||
prereg_user = PreregistrationUser.objects.create(
|
||||
|
|
|
@ -59,12 +59,12 @@ def get_invitee_emails_set(invitee_emails_raw: str) -> Set[str]:
|
|||
invitee_emails.add(email.strip())
|
||||
return invitee_emails
|
||||
|
||||
@require_realm_admin
|
||||
@require_member_or_admin
|
||||
def get_user_invites(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
||||
all_users = do_get_user_invites(user_profile)
|
||||
return json_success({'invites': all_users})
|
||||
|
||||
@require_realm_admin
|
||||
@require_member_or_admin
|
||||
@has_request_variables
|
||||
def revoke_user_invite(request: HttpRequest, user_profile: UserProfile,
|
||||
prereg_id: int) -> HttpResponse:
|
||||
|
@ -76,6 +76,9 @@ def revoke_user_invite(request: HttpRequest, user_profile: UserProfile,
|
|||
if prereg_user.referred_by.realm != user_profile.realm:
|
||||
raise JsonableError(_("No such invitation"))
|
||||
|
||||
if prereg_user.referred_by_id != user_profile.id and not user_profile.is_realm_admin:
|
||||
raise JsonableError(_("Must be an organization administrator"))
|
||||
|
||||
do_revoke_user_invite(prereg_user)
|
||||
return json_success()
|
||||
|
||||
|
@ -95,7 +98,7 @@ def revoke_multiuse_invite(request: HttpRequest, user_profile: UserProfile,
|
|||
do_revoke_multi_use_invite(invite)
|
||||
return json_success()
|
||||
|
||||
@require_realm_admin
|
||||
@require_member_or_admin
|
||||
@has_request_variables
|
||||
def resend_user_invite_email(request: HttpRequest, user_profile: UserProfile,
|
||||
prereg_id: int) -> HttpResponse:
|
||||
|
@ -109,6 +112,9 @@ def resend_user_invite_email(request: HttpRequest, user_profile: UserProfile,
|
|||
if prereg_user.referred_by is None or prereg_user.referred_by.realm != user_profile.realm:
|
||||
raise JsonableError(_("No such invitation"))
|
||||
|
||||
if prereg_user.referred_by_id != user_profile.id and not user_profile.is_realm_admin:
|
||||
raise JsonableError(_("Must be an organization administrator"))
|
||||
|
||||
timestamp = do_resend_user_invite_email(prereg_user)
|
||||
return json_success({'timestamp': timestamp})
|
||||
|
||||
|
|
Loading…
Reference in New Issue