backend: Allow Administrators to invite new users as admins.

Tweaked by tabbott to have the field before the invitation is
completed be called invite_as_admins, not invited_as_admins, for
readability.

Fixes #6834.
This commit is contained in:
Vishnu Ks
2017-10-15 22:04:47 +05:30
committed by Tim Abbott
parent 4b78f69a87
commit 8c68a167fe
7 changed files with 77 additions and 11 deletions

View File

@@ -18,6 +18,17 @@
<div class="controls"> <div class="controls">
<textarea rows="2" id="custom_invite_body" name="custom_body" placeholder="{{ _('Custom message') }}"></textarea> <textarea rows="2" id="custom_invite_body" name="custom_body" placeholder="{{ _('Custom message') }}"></textarea>
</div> </div>
{% if is_admin %}
<label class="control-label" id="invite_as_admin" for"invite_as_admin">{{ _('User(s) join as') }}</label>
<div class="controls">
<label class="radio">
<input type="radio" name="invite_as_admin" value="false" checked/>{{ _('Regular users') }}
</label>
<label class="radio">
<input type="radio" name="invite_as_admin" value="true"/>{{ _('Organization administrators') }}
</label>
</div>
{% endif %}
</div> </div>
<div class="alert" id="invite_status"></div> <div class="alert" id="invite_status"></div>
{% if development_environment %} {% if development_environment %}

View File

@@ -3754,8 +3754,8 @@ class InvitationError(JsonableError):
self.errors = errors # type: List[Tuple[Text, str]] self.errors = errors # type: List[Tuple[Text, str]]
self.sent_invitations = sent_invitations # type: bool self.sent_invitations = sent_invitations # type: bool
def do_invite_users(user_profile, invitee_emails, streams, body=None): def do_invite_users(user_profile, invitee_emails, streams, invite_as_admin=False, body=None):
# type: (UserProfile, SizedTextIterable, Iterable[Stream], Optional[str]) -> None # type: (UserProfile, SizedTextIterable, Iterable[Stream], Optional[bool], Optional[str]) -> None
validated_emails = [] # type: List[Text] validated_emails = [] # type: List[Text]
errors = [] # type: List[Tuple[Text, str]] errors = [] # type: List[Tuple[Text, str]]
skipped = [] # type: List[Tuple[Text, str]] skipped = [] # type: List[Tuple[Text, str]]
@@ -3787,7 +3787,8 @@ def do_invite_users(user_profile, invitee_emails, streams, body=None):
# the PreregistrationUser objects and trigger the email invitations. # the PreregistrationUser objects and trigger the email invitations.
for email in validated_emails: for email in validated_emails:
# The logged in user is the referrer. # The logged in user is the referrer.
prereg_user = PreregistrationUser(email=email, referred_by=user_profile) prereg_user = PreregistrationUser(email=email, referred_by=user_profile,
invited_as_admin=invite_as_admin)
prereg_user.save() prereg_user.save()
stream_ids = [stream.id for stream in streams] stream_ids = [stream.id for stream in streams]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-10-19 21:42
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('zerver', '0113_default_stream_group'),
]
operations = [
migrations.AddField(
model_name='preregistrationuser',
name='invited_as_admin',
field=models.BooleanField(default=False),
),
]

View File

@@ -794,6 +794,8 @@ class PreregistrationUser(models.Model):
realm = models.ForeignKey(Realm, null=True, on_delete=CASCADE) # type: Optional[Realm] realm = models.ForeignKey(Realm, null=True, on_delete=CASCADE) # type: Optional[Realm]
invited_as_admin = models.BooleanField(default=False) # type: Optional[bool]
class MultiuseInvite(models.Model): class MultiuseInvite(models.Model):
referred_by = models.ForeignKey(UserProfile, on_delete=CASCADE) # Optional[UserProfile] referred_by = models.ForeignKey(UserProfile, on_delete=CASCADE) # Optional[UserProfile]
streams = models.ManyToManyField('Stream') # type: Manager streams = models.ManyToManyField('Stream') # type: Manager

View File

@@ -443,9 +443,8 @@ class InviteUserBase(ZulipTestCase):
self.assertIn(FromAddress.NOREPLY, outbox[0].from_email) self.assertIn(FromAddress.NOREPLY, outbox[0].from_email)
class InviteUserTest(InviteUserBase): class InviteUserTest(InviteUserBase):
def invite(self, users, streams, body='', invite_as_admin="false"):
def invite(self, users, streams, body=''): # type: (Text, List[Text], str, str) -> HttpResponse
# type: (Text, List[Text], str) -> HttpResponse
""" """
Invites the specified users to Zulip with the specified streams. Invites the specified users to Zulip with the specified streams.
@@ -458,6 +457,7 @@ class InviteUserTest(InviteUserBase):
return self.client_post("/json/invites", return self.client_post("/json/invites",
{"invitee_emails": users, {"invitee_emails": users,
"stream": streams, "stream": streams,
"invite_as_admin": invite_as_admin,
"custom_body": body}) "custom_body": body})
def test_successful_invite_user(self): def test_successful_invite_user(self):
@@ -472,6 +472,32 @@ class InviteUserTest(InviteUserBase):
self.assertTrue(find_key_by_email(invitee)) self.assertTrue(find_key_by_email(invitee))
self.check_sent_emails([invitee], custom_from_name="Hamlet") self.check_sent_emails([invitee], custom_from_name="Hamlet")
def test_successful_invite_user_as_admin_from_admin_account(self):
# type: () -> None
"""
Test that a new user invited to a stream receives some initial
history but only from public streams.
"""
self.login(self.example_email('iago'))
invitee = self.nonreg_email('alice')
self.assert_json_success(self.invite(invitee, ["Denmark"], invite_as_admin="true"))
self.assertTrue(find_key_by_email(invitee))
self.submit_reg_form_for_user(invitee, "password")
invitee_profile = self.nonreg_user('alice')
self.assertTrue(invitee_profile.is_realm_admin)
def test_invite_user_as_admin_from_normal_account(self):
# type: () -> None
"""
Test that a new user invited to a stream receives some initial
history but only from public streams.
"""
self.login(self.example_email('hamlet'))
invitee = self.nonreg_email('alice')
response = self.invite(invitee, ["Denmark"], invite_as_admin="true")
self.assert_json_error(response, "Must be a realm administrator")
def test_successful_invite_user_with_custom_body(self): def test_successful_invite_user_with_custom_body(self):
# type: () -> None # type: () -> None
""" """
@@ -583,7 +609,7 @@ class InviteUserTest(InviteUserBase):
UserMessage.objects.filter(user_profile=invitee_profile)] UserMessage.objects.filter(user_profile=invitee_profile)]
self.assertTrue(public_msg_id in invitee_msg_ids) self.assertTrue(public_msg_id in invitee_msg_ids)
self.assertFalse(secret_msg_id in invitee_msg_ids) self.assertFalse(secret_msg_id in invitee_msg_ids)
self.assertFalse(invitee_profile.is_realm_admin)
# Test that exactly 2 new Zulip messages were sent, both notifications. # Test that exactly 2 new Zulip messages were sent, both notifications.
last_3_messages = list(reversed(list(Message.objects.all().order_by("-id")[0:3]))) last_3_messages = list(reversed(list(Message.objects.all().order_by("-id")[0:3])))
first_msg = last_3_messages[0] first_msg = last_3_messages[0]

View File

@@ -11,7 +11,7 @@ from zerver.lib.actions import do_invite_users, \
from zerver.lib.request import REQ, has_request_variables, JsonableError from zerver.lib.request import REQ, has_request_variables, JsonableError
from zerver.lib.response import json_success, json_error from zerver.lib.response import json_success, json_error
from zerver.lib.streams import access_stream_by_name from zerver.lib.streams import access_stream_by_name
from zerver.lib.validator import check_string, check_list from zerver.lib.validator import check_string, check_list, check_bool
from zerver.models import PreregistrationUser, Stream, UserProfile from zerver.models import PreregistrationUser, Stream, UserProfile
import re import re
@@ -19,10 +19,14 @@ import re
@has_request_variables @has_request_variables
def invite_users_backend(request, user_profile, def invite_users_backend(request, user_profile,
invitee_emails_raw=REQ("invitee_emails"), invitee_emails_raw=REQ("invitee_emails"),
invite_as_admin=REQ(validator=check_bool, default=False),
body=REQ("custom_body", default=None)): body=REQ("custom_body", default=None)):
# type: (HttpRequest, UserProfile, str, Optional[str]) -> HttpResponse # type: (HttpRequest, UserProfile, str, Optional[bool], Optional[str]) -> HttpResponse
if user_profile.realm.invite_by_admins_only and not user_profile.is_realm_admin: if user_profile.realm.invite_by_admins_only and not user_profile.is_realm_admin:
return json_error(_("Must be a realm administrator")) return json_error(_("Must be a realm administrator"))
if invite_as_admin and not user_profile.is_realm_admin:
return json_error(_("Must be a realm administrator"))
if not invitee_emails_raw: if not invitee_emails_raw:
return json_error(_("You must specify at least one email address.")) return json_error(_("You must specify at least one email address."))
if body == '': if body == '':
@@ -48,7 +52,7 @@ def invite_users_backend(request, user_profile,
return json_error(_("Stream does not exist: %s. No invites were sent.") % (stream_name,)) return json_error(_("Stream does not exist: %s. No invites were sent.") % (stream_name,))
streams.append(stream) streams.append(stream)
do_invite_users(user_profile, invitee_emails, streams, body) do_invite_users(user_profile, invitee_emails, streams, invite_as_admin, body)
return json_success() return json_success()
def get_invitee_emails_set(invitee_emails_raw): def get_invitee_emails_set(invitee_emails_raw):

View File

@@ -75,6 +75,7 @@ def accounts_register(request):
email = prereg_user.email email = prereg_user.email
realm_creation = prereg_user.realm_creation realm_creation = prereg_user.realm_creation
password_required = prereg_user.password_required password_required = prereg_user.password_required
is_realm_admin = prereg_user.invited_as_admin or realm_creation
validators.validate_email(email) validators.validate_email(email)
if prereg_user.referred_by: if prereg_user.referred_by:
@@ -228,8 +229,9 @@ def accounts_register(request):
do_change_full_name(user_profile, full_name, user_profile) do_change_full_name(user_profile, full_name, user_profile)
do_set_user_display_setting(user_profile, 'timezone', timezone) do_set_user_display_setting(user_profile, 'timezone', timezone)
else: else:
# TODO: When we clean up this code path, make it respect is_realm_admin.
user_profile = do_create_user(email, password, realm, full_name, short_name, user_profile = do_create_user(email, password, realm, full_name, short_name,
prereg_user=prereg_user, is_realm_admin=realm_creation, prereg_user=prereg_user, is_realm_admin=is_realm_admin,
tos_version=settings.TOS_VERSION, tos_version=settings.TOS_VERSION,
timezone=timezone, timezone=timezone,
newsletter_data={"IP": request.META['REMOTE_ADDR']}, newsletter_data={"IP": request.META['REMOTE_ADDR']},