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:
jagansivam28 2020-05-01 01:11:21 +05:30 committed by Tim Abbott
parent fc107d2c24
commit bbf5a5efed
7 changed files with 138 additions and 8 deletions

View File

@ -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: {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 = []

View File

@ -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(

View File

@ -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})