Add RealmAlias.allow_subdomains to model, frontend, and API.

Includes a database migration.

Fixes #1868.
This commit is contained in:
Harshit Bansal
2017-01-20 23:19:03 -08:00
committed by Tim Abbott
parent a16c48f0c4
commit 7d10cbc32b
15 changed files with 297 additions and 61 deletions

View File

@@ -97,6 +97,7 @@ function render(template_name, args) {
var args = { var args = {
alias: { alias: {
domain: 'zulip.org', domain: 'zulip.org',
allow_subdomains: true,
}, },
}; };
html += render("admin-alias-list", args); html += render("admin-alias-list", args);
@@ -104,11 +105,15 @@ function render(template_name, args) {
var button = $(html).find('.btn'); var button = $(html).find('.btn');
var domain = $(html).find('.domain'); var domain = $(html).find('.domain');
var row = button.closest('tr');
var subdomains_checkbox = row.find('.allow-subdomains');
assert.equal(button.text().trim(), "Remove"); assert.equal(button.text().trim(), "Remove");
assert(button.hasClass("delete_alias")); assert(button.hasClass("delete_alias"));
assert.equal(domain.text(), "zulip.org"); assert.equal(domain.text(), "zulip.org");
assert.equal(subdomains_checkbox.prop('checked'), true);
global.write_handlebars_output("admin-alias-list", html); global.write_handlebars_output("admin-alias-list", html);
}()); }());

View File

@@ -212,8 +212,8 @@ exports.populate_realm_aliases = function (aliases) {
return; return;
} }
var domains_list = _.map(page_params.domains, function (ADomain) { var domains_list = _.map(aliases, function (alias) {
return ADomain.domain; return (alias.allow_subdomains ? "*." + alias.domain : alias.domain);
}); });
var domains = domains_list.join(', '); var domains = domains_list.join(', ');
if (domains.length === 0) { if (domains.length === 0) {
@@ -955,17 +955,19 @@ function _setup_page() {
}); });
}); });
$("#add_alias").click(function () { $("#submit-add-alias").click(function () {
var aliases_info = $("#realm_aliases_modal").find(".aliases_info"); var aliases_info = $("#realm_aliases_modal").find(".aliases_info");
var data = { var data = {
domain: JSON.stringify($("#new_alias").val()), domain: JSON.stringify($("#add-alias-widget .new-alias-domain").val()),
allow_subdomains: JSON.stringify($("#add-alias-widget .new-alias-allow-subdomains").prop("checked")),
}; };
channel.post({ channel.post({
url: "/json/realm/domains", url: "/json/realm/domains",
data: data, data: data,
success: function () { success: function () {
$("#new_alias").val(""); $("#add-alias-widget .new-alias-domain").val("");
$("#add-alias-widget .new-alias-allow-subdomains").prop("checked", false);
aliases_info.removeClass("text-error"); aliases_info.removeClass("text-error");
aliases_info.addClass("text-success"); aliases_info.addClass("text-success");
aliases_info.text("Added successfully!"); aliases_info.text("Added successfully!");
@@ -978,6 +980,38 @@ function _setup_page() {
}); });
}); });
$("#alias_table").on("change", ".allow-subdomains", function (e) {
e.stopPropagation();
var aliases_info = $("#realm_aliases_modal").find(".aliases_info");
var domain = $(this).parents("tr").find(".domain").text();
var allow_subdomains = $(this).prop('checked');
var url = '/json/realm/domains/' + domain;
var data = {
allow_subdomains: JSON.stringify(allow_subdomains),
};
channel.patch({
url: url,
data: data,
success: function () {
aliases_info.removeClass("text-error");
aliases_info.addClass("text-success");
if (allow_subdomains) {
aliases_info.text(i18n.t("Update successful: Subdomains allowed for __domain__",
{domain: domain}));
} else {
aliases_info.text(i18n.t("Update successful: Subdomains no longer allowed for __domain__",
{domain: domain}));
}
},
error: function (xhr) {
aliases_info.removeClass("text-success");
aliases_info.addClass("text-error");
aliases_info.text(JSON.parse(xhr.responseText).msg);
},
});
});
} }
exports.setup_page = function () { exports.setup_page = function () {

View File

@@ -108,10 +108,17 @@ function dispatch_normal_event(event) {
break; break;
case 'realm_domains': case 'realm_domains':
var i;
if (event.op === 'add') { if (event.op === 'add') {
page_params.domains.push(event.alias); page_params.domains.push(event.alias);
} else if (event.op === 'change') {
for (i = 0; i < page_params.domains.length; i += 1) {
if (page_params.domains[i].domain === event.alias.domain) {
page_params.domains[i].allow_subdomains = event.alias.allow_subdomains;
break;
}
}
} else if (event.op === 'remove') { } else if (event.op === 'remove') {
var i;
for (i = 0; i < page_params.domains.length; i += 1) { for (i = 0; i < page_params.domains.length; i += 1) {
if (page_params.domains[i].domain === event.domain) { if (page_params.domains[i].domain === event.domain) {
page_params.domains.splice(i, 1); page_params.domains.splice(i, 1);
@@ -121,6 +128,7 @@ function dispatch_normal_event(event) {
} }
admin.populate_realm_aliases(page_params.domains); admin.populate_realm_aliases(page_params.domains);
break; break;
case 'realm_user': case 'realm_user':
if (event.op === 'add') { if (event.op === 'add') {
people.add_in_realm(event.person); people.add_in_realm(event.person);

View File

@@ -437,12 +437,12 @@ input[type=checkbox].inline-block {
padding: 10px 15px; padding: 10px 15px;
} }
#administration #new_alias { #administration .new-alias-domain {
width: 130px; width: 130px;
margin-bottom: auto; margin-bottom: auto;
padding: 0px 2px; padding: 0px 2px;
} }
#administration #add_alias { #administration #submit-add-alias {
width: 75px; width: 75px;
} }

View File

@@ -83,14 +83,16 @@
<table class="table table-condensed table-stripped" id="alias_table"> <table class="table table-condensed table-stripped" id="alias_table">
<thead> <thead>
<th>{{t "Domain" }}</th> <th>{{t "Domain" }}</th>
<th>{{t "Allow Subdomains"}}</th>
<th>{{t "Action" }}</th> <th>{{t "Action" }}</th>
</thead> </thead>
<tbody> <tbody>
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr id="add-alias-widget">
<td><input type="text" id="new_alias" placeholder={{t "acme.com" }}></input></td> <td><input type="text" class="new-alias-domain" placeholder={{t "acme.com" }}></input></td>
<td><button type="button" class="btn btn-primary" id="add_alias">{{t "Add" }}</button></td> <td><input type="checkbox" class="new-alias-allow-subdomains"></input></td>
<td><button type="button" class="btn btn-primary" id="submit-add-alias">{{t "Add" }}</button></td>
</tr> </tr>
</tfoot> </tfoot>
</table> </table>

View File

@@ -1,6 +1,7 @@
{{#with alias}} {{#with alias}}
<tr> <tr>
<td class="domain">{{domain}}</td> <td class="domain">{{domain}}</td>
<td><input type="checkbox" class="allow-subdomains" {{#if allow_subdomains}}checked{{/if}}></input></td>
<td><button class="btn btn-danger btn-sm delete_alias">{{t "Remove" }}</button></td> <td><button class="btn btn-danger btn-sm delete_alias">{{t "Remove" }}</button></td>
</tr> </tr>
{{/with}} {{/with}}

View File

@@ -3616,17 +3616,27 @@ def get_emails_from_user_ids(user_ids):
def get_realm_aliases(realm): def get_realm_aliases(realm):
# type: (Realm) -> List[Dict[str, Text]] # type: (Realm) -> List[Dict[str, Text]]
return list(realm.realmalias_set.values('domain')) return list(realm.realmalias_set.values('domain', 'allow_subdomains'))
def do_add_realm_alias(realm, domain): def do_add_realm_alias(realm, domain, allow_subdomains):
# type: (Realm, Text) -> (RealmAlias) # type: (Realm, Text, bool) -> (RealmAlias)
alias = RealmAlias.objects.create(realm=realm, domain=domain) alias = RealmAlias.objects.create(realm=realm, domain=domain,
allow_subdomains=allow_subdomains)
event = dict(type="realm_domains", op="add", event = dict(type="realm_domains", op="add",
alias=dict(domain=alias.domain, alias=dict(domain=alias.domain,
)) allow_subdomains=alias.allow_subdomains))
send_event(event, active_user_ids(realm)) send_event(event, active_user_ids(realm))
return alias return alias
def do_change_realm_alias(alias, allow_subdomains):
# type: (RealmAlias, bool) -> None
alias.allow_subdomains = allow_subdomains
alias.save(update_fields=['allow_subdomains'])
event = dict(type="realm_domains", op="change",
alias=dict(domain=alias.domain,
allow_subdomains=alias.allow_subdomains))
send_event(event, active_user_ids(alias.realm))
def do_remove_realm_alias(alias): def do_remove_realm_alias(alias):
# type: (RealmAlias) -> None # type: (RealmAlias) -> None
realm = alias.realm realm = alias.realm

View File

@@ -6,7 +6,10 @@ from typing import Any
from argparse import ArgumentParser from argparse import ArgumentParser
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from zerver.models import Realm, RealmAlias, get_realm, can_add_alias from django.db.utils import IntegrityError
from django.utils.translation import ugettext as _
from zerver.models import get_realm, can_add_alias, \
Realm, RealmAlias
from zerver.lib.actions import get_realm_aliases from zerver.lib.actions import get_realm_aliases
from zerver.lib.domains import validate_domain from zerver.lib.domains import validate_domain
import sys import sys
@@ -26,6 +29,11 @@ class Command(BaseCommand):
type=str, type=str,
default="show", default="show",
help='What operation to do (add, show, remove).') help='What operation to do (add, show, remove).')
parser.add_argument('--allow-subdomains',
dest='allow_subdomains',
action="store_true",
default=False,
help='Whether subdomains are allowed or not.')
parser.add_argument('alias', metavar='<alias>', type=str, nargs='?', parser.add_argument('alias', metavar='<alias>', type=str, nargs='?',
help="alias to add or remove") help="alias to add or remove")
@@ -35,7 +43,10 @@ class Command(BaseCommand):
if options["op"] == "show": if options["op"] == "show":
print("Aliases for %s:" % (realm.domain,)) print("Aliases for %s:" % (realm.domain,))
for alias in get_realm_aliases(realm): for alias in get_realm_aliases(realm):
print(alias["domain"]) if alias["allow_subdomains"]:
print(alias["domain"] + " (subdomains allowed)")
else:
print(alias["domain"] + " (subdomains not allowed)")
sys.exit(0) sys.exit(0)
domain = options['alias'].strip().lower() domain = options['alias'].strip().lower()
@@ -45,14 +56,23 @@ class Command(BaseCommand):
print(e.messages[0]) print(e.messages[0])
sys.exit(1) sys.exit(1)
if options["op"] == "add": if options["op"] == "add":
try:
if not can_add_alias(domain): if not can_add_alias(domain):
print("A Realm already exists for this domain, cannot add it as an alias for another realm!") print(_("The domain %(domain)s belongs to another organization.") % {'domain': domain})
sys.exit(1) sys.exit(1)
RealmAlias.objects.create(realm=realm, domain=domain) RealmAlias.objects.create(realm=realm, domain=domain,
allow_subdomains=options["allow_subdomains"])
sys.exit(0) sys.exit(0)
except IntegrityError:
print(_("The domain %(domain)s is already a part of your organization.") % {'domain': domain})
sys.exit(1)
elif options["op"] == "remove": elif options["op"] == "remove":
try:
RealmAlias.objects.get(realm=realm, domain=domain).delete() RealmAlias.objects.get(realm=realm, domain=domain).delete()
sys.exit(0) sys.exit(0)
except RealmAlias.DoesNotExist:
print("No such entry found!")
sys.exit(1)
else: else:
self.print_help("./manage.py", "realm_alias") self.print_help("./manage.py", "realm_alias")
sys.exit(1) sys.exit(1)

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-01-25 20:55
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('zerver', '0050_userprofile_avatar_version'),
]
operations = [
migrations.AddField(
model_name='realmalias',
name='allow_subdomains',
field=models.BooleanField(default=False),
),
migrations.AlterUniqueTogether(
name='realmalias',
unique_together=set([('realm', 'domain')]),
),
]

View File

@@ -283,6 +283,10 @@ class RealmAlias(models.Model):
realm = models.ForeignKey(Realm, null=True) # type: Optional[Realm] realm = models.ForeignKey(Realm, null=True) # type: Optional[Realm]
# should always be stored lowercase # should always be stored lowercase
domain = models.CharField(max_length=80, db_index=True) # type: Text domain = models.CharField(max_length=80, db_index=True) # type: Text
allow_subdomains = models.BooleanField(default=False)
class Meta(object):
unique_together = ("realm", "domain")
def can_add_alias(domain): def can_add_alias(domain):
# type: (Text) -> bool # type: (Text) -> bool
@@ -315,10 +319,18 @@ def get_realm_by_email_domain(email):
if settings.REALMS_HAVE_SUBDOMAINS: if settings.REALMS_HAVE_SUBDOMAINS:
raise GetRealmByDomainException( raise GetRealmByDomainException(
"Cannot get realm from email domain when settings.REALMS_HAVE_SUBDOMAINS = True") "Cannot get realm from email domain when settings.REALMS_HAVE_SUBDOMAINS = True")
try: domain = email_to_domain(email)
alias = RealmAlias.objects.select_related('realm').get(domain = email_to_domain(email)) query = RealmAlias.objects.select_related('realm')
alias = query.filter(domain=domain).first()
if alias is not None:
return alias.realm
else:
query = query.filter(allow_subdomains=True)
while len(domain) > 0:
subdomain, sep, domain = domain.partition('.')
alias = query.filter(domain=domain).first()
if alias is not None:
return alias.realm return alias.realm
except RealmAlias.DoesNotExist:
return None return None
# Is a user with the given email address allowed to be in the given realm? # Is a user with the given email address allowed to be in the given realm?
@@ -330,11 +342,20 @@ def email_allowed_for_realm(email, realm):
if not realm.restricted_to_domain: if not realm.restricted_to_domain:
return True return True
domain = email_to_domain(email) domain = email_to_domain(email)
return RealmAlias.objects.filter(realm = realm, domain = domain).exists() query = RealmAlias.objects.filter(realm=realm)
if query.filter(domain=domain).exists():
return True
else:
query = query.filter(allow_subdomains=True)
while len(domain) > 0:
subdomain, sep, domain = domain.partition('.')
if query.filter(domain=domain).exists():
return True
return False
def list_of_domains_for_realm(realm): def list_of_domains_for_realm(realm):
# type: (Realm) -> List[Text] # type: (Realm) -> List[Dict[str, Union[str, bool]]]
return list(RealmAlias.objects.filter(realm=realm).values('domain')) return list(RealmAlias.objects.filter(realm=realm).values('domain', 'allow_subdomains'))
class RealmEmoji(ModelReprMixin, models.Model): class RealmEmoji(ModelReprMixin, models.Model):
author = models.ForeignKey('UserProfile', blank=True, null=True) author = models.ForeignKey('UserProfile', blank=True, null=True)

View File

@@ -819,10 +819,11 @@ class EventsRegisterTest(ZulipTestCase):
('op', equals('add')), ('op', equals('add')),
('alias', check_dict([ ('alias', check_dict([
('domain', check_string), ('domain', check_string),
('allow_subdomains', check_bool),
])), ])),
]) ])
realm = get_realm('zulip') realm = get_realm('zulip')
events = self.do_test(lambda: do_add_realm_alias(realm, 'zulip.org')) events = self.do_test(lambda: do_add_realm_alias(realm, 'zulip.org', False))
error = schema_checker('events[0]', events[0]) error = schema_checker('events[0]', events[0])
self.assert_on_error(error) self.assert_on_error(error)

View File

@@ -1,77 +1,169 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import from __future__ import absolute_import
from zerver.lib.actions import do_change_is_admin, do_create_realm from django.core.exceptions import ValidationError
from django.db.utils import IntegrityError
from zerver.lib.actions import do_change_is_admin, \
do_create_realm, do_change_realm_alias
from zerver.lib.domains import validate_domain
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.models import get_realm, get_realm_by_email_domain, \ from zerver.models import email_allowed_for_realm, get_realm, \
get_user_profile_by_email, GetRealmByDomainException, RealmAlias get_realm_by_email_domain, get_user_profile_by_email, \
GetRealmByDomainException, RealmAlias
import ujson import ujson
class RealmAliasTest(ZulipTestCase): class RealmAliasTest(ZulipTestCase):
def test_list_aliases(self):
def test_list(self):
# type: () -> None # type: () -> None
self.login("iago@zulip.com") self.login("iago@zulip.com")
realm = get_realm('zulip') realm = get_realm('zulip')
alias = RealmAlias(realm=realm, domain='zulip.org') RealmAlias.objects.create(realm=realm, domain='acme.com', allow_subdomains=True)
alias.save()
result = self.client_get("/json/realm/domains") result = self.client_get("/json/realm/domains")
self.assert_json_success(result) self.assert_json_success(result)
self.assertEqual(200, result.status_code) received = ujson.dumps(ujson.loads(result.content)['domains'], sort_keys=True)
content = ujson.loads(result.content) expected = ujson.dumps([{'domain': 'zulip.com', 'allow_subdomains': False},
self.assertEqual(len(content['domains']), 2) {'domain': 'acme.com', 'allow_subdomains': True}],
sort_keys=True)
self.assertEqual(received, expected)
def test_not_realm_admin(self): def test_not_realm_admin(self):
# type: () -> None # type: () -> None
self.login("hamlet@zulip.com") self.login("hamlet@zulip.com")
result = self.client_post("/json/realm/domains") result = self.client_post("/json/realm/domains")
self.assert_json_error(result, 'Must be a realm administrator') self.assert_json_error(result, 'Must be a realm administrator')
result = self.client_patch("/json/realm/domains/15")
self.assert_json_error(result, 'Must be a realm administrator')
result = self.client_delete("/json/realm/domains/15") result = self.client_delete("/json/realm/domains/15")
self.assert_json_error(result, 'Must be a realm administrator') self.assert_json_error(result, 'Must be a realm administrator')
def test_create(self): def test_create_alias(self):
# type: () -> None # type: () -> None
self.login("iago@zulip.com") self.login("iago@zulip.com")
data = {'domain': ujson.dumps('')} data = {'domain': ujson.dumps(''),
'allow_subdomains': ujson.dumps(True)}
result = self.client_post("/json/realm/domains", info=data) result = self.client_post("/json/realm/domains", info=data)
self.assert_json_error(result, 'Invalid domain: Domain can\'t be empty.') self.assert_json_error(result, 'Invalid domain: Domain can\'t be empty.')
data = {'domain': ujson.dumps('zulip.org')} data['domain'] = ujson.dumps('acme.com')
result = self.client_post("/json/realm/domains", info=data) result = self.client_post("/json/realm/domains", info=data)
self.assert_json_success(result) self.assert_json_success(result)
realm = get_realm('zulip')
self.assertTrue(RealmAlias.objects.filter(realm=realm, domain='acme.com',
allow_subdomains=True).exists())
result = self.client_post("/json/realm/domains", info=data) result = self.client_post("/json/realm/domains", info=data)
self.assert_json_error(result, 'The domain zulip.org is already a part of your organization.') self.assert_json_error(result, 'The domain acme.com is already a part of your organization.')
self.login("sipbtest@mit.edu") self.login("sipbtest@mit.edu")
mit_user_profile = get_user_profile_by_email("sipbtest@mit.edu") mit_user_profile = get_user_profile_by_email("sipbtest@mit.edu")
do_change_is_admin(mit_user_profile, True) do_change_is_admin(mit_user_profile, True)
result = self.client_post("/json/realm/domains", info=data) result = self.client_post("/json/realm/domains", info=data)
self.assert_json_error(result, 'The domain zulip.org belongs to another organization.') self.assert_json_error(result, 'The domain acme.com belongs to another organization.')
with self.settings(REALMS_HAVE_SUBDOMAINS=True): with self.settings(REALMS_HAVE_SUBDOMAINS=True):
result = self.client_post("/json/realm/domains", info=data, result = self.client_post("/json/realm/domains", info=data,
HTTP_HOST=mit_user_profile.realm.host) HTTP_HOST=mit_user_profile.realm.host)
self.assert_json_success(result) self.assert_json_success(result)
def test_delete(self): def test_patch_alias(self):
# type: () -> None # type: () -> None
self.login("iago@zulip.com") self.login("iago@zulip.com")
realm = get_realm('zulip') realm = get_realm('zulip')
RealmAlias.objects.create(realm=realm, domain='zulip.org') RealmAlias.objects.create(realm=realm, domain='acme.com',
aliases_count = RealmAlias.objects.count() allow_subdomains=False)
data = {
'allow_subdomains': ujson.dumps(True),
}
url = "/json/realm/domains/acme.com"
result = self.client_patch(url, data)
self.assert_json_success(result)
self.assertTrue(RealmAlias.objects.filter(realm=realm, domain='acme.com',
allow_subdomains=True).exists())
url = "/json/realm/domains/non-existent.com"
result = self.client_patch(url, data)
self.assertEqual(result.status_code, 400)
self.assert_json_error(result, 'No entry found for domain non-existent.com.')
def test_delete_alias(self):
# type: () -> None
self.login("iago@zulip.com")
realm = get_realm('zulip')
RealmAlias.objects.create(realm=realm, domain='acme.com')
result = self.client_delete("/json/realm/domains/non-existent.com") result = self.client_delete("/json/realm/domains/non-existent.com")
self.assertEqual(result.status_code, 400) self.assertEqual(result.status_code, 400)
self.assert_json_error(result, 'No entry found for domain non-existent.com.') self.assert_json_error(result, 'No entry found for domain non-existent.com.')
result = self.client_delete("/json/realm/domains/zulip.org") result = self.client_delete("/json/realm/domains/acme.com")
self.assert_json_success(result) self.assert_json_success(result)
self.assertEqual(RealmAlias.objects.count(), aliases_count - 1) self.assertFalse(RealmAlias.objects.filter(domain='acme.com').exists())
def test_get_realm_by_email_domain(self): def test_get_realm_by_email_domain(self):
# type: () -> None # type: () -> None
realm1, created = do_create_realm('testrealm1', 'Test Realm 1')
realm2, created = do_create_realm('testrealm2', 'Test Realm 2')
realm3, created = do_create_realm('testrealm3', 'Test Realm 3')
alias1 = RealmAlias.objects.create(realm=realm1, domain='test1.com', allow_subdomains=True)
alias2 = RealmAlias.objects.create(realm=realm2, domain='test2.test1.com', allow_subdomains=False)
RealmAlias.objects.create(realm=realm3, domain='test3.test2.test1.com', allow_subdomains=True)
self.assertEqual(get_realm_by_email_domain('user@zulip.com').string_id, 'zulip') self.assertEqual(get_realm_by_email_domain('user@zulip.com').string_id, 'zulip')
self.assertEqual(get_realm_by_email_domain('user@fakedomain.com'), None) self.assertEqual(get_realm_by_email_domain('user@fakedomain.com'), None)
self.assertEqual(get_realm_by_email_domain('user@test1.com').string_id, 'testrealm1')
self.assertEqual(get_realm_by_email_domain('user@test2.test1.com').string_id, 'testrealm2')
self.assertEqual(get_realm_by_email_domain('user@test3.test2.test1.com').string_id, 'testrealm3')
self.assertEqual(get_realm_by_email_domain('user@test2.test1.com').string_id, 'testrealm2')
self.assertEqual(get_realm_by_email_domain('user@test2.test2.test1.com').string_id, 'testrealm1')
self.assertEqual(get_realm_by_email_domain('user@test1.test3.test2.test1.com').string_id, 'testrealm3')
do_change_realm_alias(alias1, False)
self.assertEqual(get_realm_by_email_domain('user@test1.test1.com'), None)
self.assertEqual(get_realm_by_email_domain('user@test1.com').string_id, 'testrealm1')
do_change_realm_alias(alias2, True)
self.assertEqual(get_realm_by_email_domain('user@test2.test1.com').string_id, 'testrealm2')
self.assertEqual(get_realm_by_email_domain('user@test2.test2.test1.com').string_id, 'testrealm2')
with self.settings(REALMS_HAVE_SUBDOMAINS = True), ( with self.settings(REALMS_HAVE_SUBDOMAINS = True), (
self.assertRaises(GetRealmByDomainException)): self.assertRaises(GetRealmByDomainException)):
get_realm_by_email_domain('user@zulip.com') get_realm_by_email_domain('user@zulip.com')
def test_email_allowed_for_realm(self):
# type: () -> None
realm1, created = do_create_realm('testrealm1', 'Test Realm 1', restricted_to_domain=True)
realm2, created = do_create_realm('testrealm2', 'Test Realm 2', restricted_to_domain=True)
alias = RealmAlias.objects.create(realm=realm1, domain='test1.com', allow_subdomains=False)
RealmAlias.objects.create(realm=realm2, domain='test2.test1.com', allow_subdomains=True)
self.assertEqual(email_allowed_for_realm('user@test1.com', realm1), True)
self.assertEqual(email_allowed_for_realm('user@test2.test1.com', realm1), False)
self.assertEqual(email_allowed_for_realm('user@test2.test1.com', realm2), True)
self.assertEqual(email_allowed_for_realm('user@test3.test2.test1.com', realm2), True)
self.assertEqual(email_allowed_for_realm('user@test3.test1.com', realm2), False)
do_change_realm_alias(alias, True)
self.assertEqual(email_allowed_for_realm('user@test1.com', realm1), True)
self.assertEqual(email_allowed_for_realm('user@test2.test1.com', realm1), True)
self.assertEqual(email_allowed_for_realm('user@test2.com', realm1), False)
def test_realm_aliases_uniqueness(self):
# type: () -> None
realm = get_realm('zulip')
with self.settings(REALMS_HAVE_SUBDOMAINS=True), self.assertRaises(IntegrityError):
RealmAlias.objects.create(realm=realm, domain='zulip.com', allow_subdomains=True)
def test_validate_domain(self):
# type: () -> None
invalid_domains = ['', 'test', 't.', 'test.', '.com', '-test', 'test...com',
'test-', 'test_domain.com', 'test.-domain.com']
for domain in invalid_domains:
with self.assertRaises(ValidationError):
validate_domain(domain)
valid_domains = ['acme.com', 'x-x.y.3.z']
for domain in valid_domains:
validate_domain(domain)

View File

@@ -766,6 +766,8 @@ def same_realm_zephyr_user(user_profile, email):
domain = email_to_domain(email) domain = email_to_domain(email)
# Assumes allow_subdomains=False for all RealmAlias's corresponding to
# these realms.
return user_profile.realm.is_zephyr_mirror_realm and \ return user_profile.realm.is_zephyr_mirror_realm and \
RealmAlias.objects.filter(realm=user_profile.realm, domain=domain).exists() RealmAlias.objects.filter(realm=user_profile.realm, domain=domain).exists()
@@ -781,6 +783,8 @@ def same_realm_irc_user(user_profile, email):
domain = email_to_domain(email).replace("irc.", "") domain = email_to_domain(email).replace("irc.", "")
# Assumes allow_subdomains=False for all RealmAlias's corresponding to
# these realms.
return RealmAlias.objects.filter(realm=user_profile.realm, domain=domain).exists() return RealmAlias.objects.filter(realm=user_profile.realm, domain=domain).exists()
def same_realm_jabber_user(user_profile, email): def same_realm_jabber_user(user_profile, email):
@@ -794,6 +798,8 @@ def same_realm_jabber_user(user_profile, email):
# Zulip users, this is where you would do any translation. # Zulip users, this is where you would do any translation.
domain = email_to_domain(email) domain = email_to_domain(email)
# Assumes allow_subdomains=False for all RealmAlias's corresponding to
# these realms.
return RealmAlias.objects.filter(realm=user_profile.realm, domain=domain).exists() return RealmAlias.objects.filter(realm=user_profile.realm, domain=domain).exists()
# We do not @require_login for send_message_backend, since it is used # We do not @require_login for send_message_backend, since it is used

View File

@@ -4,12 +4,12 @@ from django.core.exceptions import ValidationError
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from zerver.decorator import has_request_variables, REQ, require_realm_admin from zerver.decorator import has_request_variables, require_realm_admin, REQ
from zerver.lib.actions import get_realm_aliases, do_add_realm_alias, \ from zerver.lib.actions import do_add_realm_alias, do_change_realm_alias, \
do_remove_realm_alias do_remove_realm_alias, get_realm_aliases
from zerver.lib.domains import validate_domain from zerver.lib.domains import validate_domain
from zerver.lib.response import json_error, json_success from zerver.lib.response import json_error, json_success
from zerver.lib.validator import check_string from zerver.lib.validator import check_bool, check_string
from zerver.models import can_add_alias, RealmAlias, UserProfile from zerver.models import can_add_alias, RealmAlias, UserProfile
from typing import Text from typing import Text
@@ -21,8 +21,8 @@ def list_aliases(request, user_profile):
@require_realm_admin @require_realm_admin
@has_request_variables @has_request_variables
def create_alias(request, user_profile, domain=REQ(validator=check_string)): def create_alias(request, user_profile, domain=REQ(validator=check_string), allow_subdomains=REQ(validator=check_bool)):
# type: (HttpRequest, UserProfile, Text) -> (HttpResponse) # type: (HttpRequest, UserProfile, Text, bool) -> (HttpResponse)
domain = domain.strip().lower() domain = domain.strip().lower()
try: try:
validate_domain(domain) validate_domain(domain)
@@ -32,9 +32,20 @@ def create_alias(request, user_profile, domain=REQ(validator=check_string)):
return json_error(_("The domain %(domain)s is already a part of your organization.") % {'domain': domain}) return json_error(_("The domain %(domain)s is already a part of your organization.") % {'domain': domain})
if not can_add_alias(domain): if not can_add_alias(domain):
return json_error(_("The domain %(domain)s belongs to another organization.") % {'domain': domain}) return json_error(_("The domain %(domain)s belongs to another organization.") % {'domain': domain})
alias = do_add_realm_alias(user_profile.realm, domain) alias = do_add_realm_alias(user_profile.realm, domain, allow_subdomains)
return json_success({'new_domain': [alias.id, alias.domain]}) return json_success({'new_domain': [alias.id, alias.domain]})
@require_realm_admin
@has_request_variables
def patch_alias(request, user_profile, domain, allow_subdomains=REQ(validator=check_bool)):
# type: (HttpRequest, UserProfile, Text, bool) -> (HttpResponse)
try:
alias = RealmAlias.objects.get(realm=user_profile.realm, domain=domain)
do_change_realm_alias(alias, allow_subdomains)
except RealmAlias.DoesNotExist:
return json_error(_('No entry found for domain %(domain)s.' % {'domain': domain}))
return json_success()
@require_realm_admin @require_realm_admin
@has_request_variables @has_request_variables
def delete_alias(request, user_profile, domain): def delete_alias(request, user_profile, domain):

View File

@@ -165,12 +165,13 @@ v1_api_and_json_patterns = [
# Returns a 204, used by desktop app to verify connectivity status # Returns a 204, used by desktop app to verify connectivity status
url(r'generate_204$', zerver.views.registration.generate_204, name='zerver.views.registration.generate_204'), url(r'generate_204$', zerver.views.registration.generate_204, name='zerver.views.registration.generate_204'),
# realm/aliases -> zerver.views.realm_aliases # realm/domains -> zerver.views.realm_aliases
url(r'^realm/domains$', rest_dispatch, url(r'^realm/domains$', rest_dispatch,
{'GET': 'zerver.views.realm_aliases.list_aliases', {'GET': 'zerver.views.realm_aliases.list_aliases',
'POST': 'zerver.views.realm_aliases.create_alias'}), 'POST': 'zerver.views.realm_aliases.create_alias'}),
url(r'^realm/domains/(?P<domain>\S+)$', rest_dispatch, url(r'^realm/domains/(?P<domain>\S+)$', rest_dispatch,
{'DELETE': 'zerver.views.realm_aliases.delete_alias'}), {'PATCH': 'zerver.views.realm_aliases.patch_alias',
'DELETE': 'zerver.views.realm_aliases.delete_alias'}),
# realm/emoji -> zerver.views.realm_emoji # realm/emoji -> zerver.views.realm_emoji
url(r'^realm/emoji$', rest_dispatch, url(r'^realm/emoji$', rest_dispatch,