diff --git a/frontend_tests/node_tests/templates.js b/frontend_tests/node_tests/templates.js index 665468a0c4..15f4974c83 100644 --- a/frontend_tests/node_tests/templates.js +++ b/frontend_tests/node_tests/templates.js @@ -97,6 +97,7 @@ function render(template_name, args) { var args = { alias: { domain: 'zulip.org', + allow_subdomains: true, }, }; html += render("admin-alias-list", args); @@ -104,11 +105,15 @@ function render(template_name, args) { var button = $(html).find('.btn'); var domain = $(html).find('.domain'); + var row = button.closest('tr'); + var subdomains_checkbox = row.find('.allow-subdomains'); assert.equal(button.text().trim(), "Remove"); assert(button.hasClass("delete_alias")); assert.equal(domain.text(), "zulip.org"); + assert.equal(subdomains_checkbox.prop('checked'), true); + global.write_handlebars_output("admin-alias-list", html); }()); diff --git a/static/js/admin.js b/static/js/admin.js index e19449a8a2..9cae4caff9 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -212,8 +212,8 @@ exports.populate_realm_aliases = function (aliases) { return; } - var domains_list = _.map(page_params.domains, function (ADomain) { - return ADomain.domain; + var domains_list = _.map(aliases, function (alias) { + return (alias.allow_subdomains ? "*." + alias.domain : alias.domain); }); var domains = domains_list.join(', '); 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 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({ url: "/json/realm/domains", data: data, 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.addClass("text-success"); 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 () { diff --git a/static/js/server_events.js b/static/js/server_events.js index 9f38c8a18c..a6aa6f1e84 100644 --- a/static/js/server_events.js +++ b/static/js/server_events.js @@ -108,10 +108,17 @@ function dispatch_normal_event(event) { break; case 'realm_domains': + var i; if (event.op === 'add') { 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') { - var i; for (i = 0; i < page_params.domains.length; i += 1) { if (page_params.domains[i].domain === event.domain) { page_params.domains.splice(i, 1); @@ -121,6 +128,7 @@ function dispatch_normal_event(event) { } admin.populate_realm_aliases(page_params.domains); break; + case 'realm_user': if (event.op === 'add') { people.add_in_realm(event.person); diff --git a/static/styles/settings.css b/static/styles/settings.css index 29955cd433..888c221c5f 100644 --- a/static/styles/settings.css +++ b/static/styles/settings.css @@ -437,12 +437,12 @@ input[type=checkbox].inline-block { padding: 10px 15px; } -#administration #new_alias { +#administration .new-alias-domain { width: 130px; margin-bottom: auto; padding: 0px 2px; } -#administration #add_alias { +#administration #submit-add-alias { width: 75px; } diff --git a/static/templates/admin_tab.handlebars b/static/templates/admin_tab.handlebars index 7fdc8cd58e..3199322a8b 100644 --- a/static/templates/admin_tab.handlebars +++ b/static/templates/admin_tab.handlebars @@ -83,14 +83,16 @@ + - - - + + + +
{{t "Domain" }}{{t "Allow Subdomains"}} {{t "Action" }}
diff --git a/static/templates/settings/admin-alias-list.handlebars b/static/templates/settings/admin-alias-list.handlebars index a6a5607674..c15b142f6a 100644 --- a/static/templates/settings/admin-alias-list.handlebars +++ b/static/templates/settings/admin-alias-list.handlebars @@ -1,6 +1,7 @@ {{#with alias}} {{domain}} + {{/with}} diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index b49696c312..be7c76482f 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -3616,17 +3616,27 @@ def get_emails_from_user_ids(user_ids): def get_realm_aliases(realm): # 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): - # type: (Realm, Text) -> (RealmAlias) - alias = RealmAlias.objects.create(realm=realm, domain=domain) +def do_add_realm_alias(realm, domain, allow_subdomains): + # type: (Realm, Text, bool) -> (RealmAlias) + alias = RealmAlias.objects.create(realm=realm, domain=domain, + allow_subdomains=allow_subdomains) event = dict(type="realm_domains", op="add", alias=dict(domain=alias.domain, - )) + allow_subdomains=alias.allow_subdomains)) send_event(event, active_user_ids(realm)) 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): # type: (RealmAlias) -> None realm = alias.realm diff --git a/zerver/management/commands/realm_alias.py b/zerver/management/commands/realm_alias.py index 947b21533b..63ee07e016 100644 --- a/zerver/management/commands/realm_alias.py +++ b/zerver/management/commands/realm_alias.py @@ -6,7 +6,10 @@ from typing import Any from argparse import ArgumentParser from django.core.exceptions import ValidationError 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.domains import validate_domain import sys @@ -26,6 +29,11 @@ class Command(BaseCommand): type=str, default="show", 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='', type=str, nargs='?', help="alias to add or remove") @@ -35,7 +43,10 @@ class Command(BaseCommand): if options["op"] == "show": print("Aliases for %s:" % (realm.domain,)) 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) domain = options['alias'].strip().lower() @@ -45,14 +56,23 @@ class Command(BaseCommand): print(e.messages[0]) sys.exit(1) if options["op"] == "add": - if not can_add_alias(domain): - print("A Realm already exists for this domain, cannot add it as an alias for another realm!") + try: + if not can_add_alias(domain): + print(_("The domain %(domain)s belongs to another organization.") % {'domain': domain}) + sys.exit(1) + RealmAlias.objects.create(realm=realm, domain=domain, + allow_subdomains=options["allow_subdomains"]) + sys.exit(0) + except IntegrityError: + print(_("The domain %(domain)s is already a part of your organization.") % {'domain': domain}) sys.exit(1) - RealmAlias.objects.create(realm=realm, domain=domain) - sys.exit(0) elif options["op"] == "remove": - RealmAlias.objects.get(realm=realm, domain=domain).delete() - sys.exit(0) + try: + RealmAlias.objects.get(realm=realm, domain=domain).delete() + sys.exit(0) + except RealmAlias.DoesNotExist: + print("No such entry found!") + sys.exit(1) else: self.print_help("./manage.py", "realm_alias") sys.exit(1) diff --git a/zerver/migrations/0051_realmalias_add_allow_subdomains.py b/zerver/migrations/0051_realmalias_add_allow_subdomains.py new file mode 100644 index 0000000000..14aafa1ed0 --- /dev/null +++ b/zerver/migrations/0051_realmalias_add_allow_subdomains.py @@ -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')]), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index 263e0cb9af..e340546637 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -283,6 +283,10 @@ class RealmAlias(models.Model): realm = models.ForeignKey(Realm, null=True) # type: Optional[Realm] # should always be stored lowercase 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): # type: (Text) -> bool @@ -315,11 +319,19 @@ def get_realm_by_email_domain(email): if settings.REALMS_HAVE_SUBDOMAINS: raise GetRealmByDomainException( "Cannot get realm from email domain when settings.REALMS_HAVE_SUBDOMAINS = True") - try: - alias = RealmAlias.objects.select_related('realm').get(domain = email_to_domain(email)) + 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 - except RealmAlias.DoesNotExist: - return None + 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 None # Is a user with the given email address allowed to be in the given realm? # (This function does not check whether the user has been invited to the realm. @@ -330,11 +342,20 @@ def email_allowed_for_realm(email, realm): if not realm.restricted_to_domain: return True 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): - # type: (Realm) -> List[Text] - return list(RealmAlias.objects.filter(realm=realm).values('domain')) + # type: (Realm) -> List[Dict[str, Union[str, bool]]] + return list(RealmAlias.objects.filter(realm=realm).values('domain', 'allow_subdomains')) class RealmEmoji(ModelReprMixin, models.Model): author = models.ForeignKey('UserProfile', blank=True, null=True) diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index c43326cf64..79203255e4 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -819,10 +819,11 @@ class EventsRegisterTest(ZulipTestCase): ('op', equals('add')), ('alias', check_dict([ ('domain', check_string), + ('allow_subdomains', check_bool), ])), ]) 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]) self.assert_on_error(error) diff --git a/zerver/tests/test_realm_aliases.py b/zerver/tests/test_realm_aliases.py index ef180276b6..7b8db2c8ff 100644 --- a/zerver/tests/test_realm_aliases.py +++ b/zerver/tests/test_realm_aliases.py @@ -1,77 +1,169 @@ # -*- coding: utf-8 -*- 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.models import get_realm, get_realm_by_email_domain, \ - get_user_profile_by_email, GetRealmByDomainException, RealmAlias +from zerver.models import email_allowed_for_realm, get_realm, \ + get_realm_by_email_domain, get_user_profile_by_email, \ + GetRealmByDomainException, RealmAlias + import ujson class RealmAliasTest(ZulipTestCase): - - def test_list(self): + def test_list_aliases(self): # type: () -> None self.login("iago@zulip.com") realm = get_realm('zulip') - alias = RealmAlias(realm=realm, domain='zulip.org') - alias.save() + RealmAlias.objects.create(realm=realm, domain='acme.com', allow_subdomains=True) result = self.client_get("/json/realm/domains") self.assert_json_success(result) - self.assertEqual(200, result.status_code) - content = ujson.loads(result.content) - self.assertEqual(len(content['domains']), 2) + received = ujson.dumps(ujson.loads(result.content)['domains'], sort_keys=True) + expected = ujson.dumps([{'domain': 'zulip.com', 'allow_subdomains': False}, + {'domain': 'acme.com', 'allow_subdomains': True}], + sort_keys=True) + self.assertEqual(received, expected) def test_not_realm_admin(self): # type: () -> None self.login("hamlet@zulip.com") result = self.client_post("/json/realm/domains") 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") self.assert_json_error(result, 'Must be a realm administrator') - def test_create(self): + def test_create_alias(self): # type: () -> None 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) 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) 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) - 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") mit_user_profile = get_user_profile_by_email("sipbtest@mit.edu") do_change_is_admin(mit_user_profile, True) 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): result = self.client_post("/json/realm/domains", info=data, HTTP_HOST=mit_user_profile.realm.host) self.assert_json_success(result) - def test_delete(self): + def test_patch_alias(self): # type: () -> None self.login("iago@zulip.com") realm = get_realm('zulip') - RealmAlias.objects.create(realm=realm, domain='zulip.org') - aliases_count = RealmAlias.objects.count() + RealmAlias.objects.create(realm=realm, domain='acme.com', + 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") self.assertEqual(result.status_code, 400) 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.assertEqual(RealmAlias.objects.count(), aliases_count - 1) + self.assertFalse(RealmAlias.objects.filter(domain='acme.com').exists()) def test_get_realm_by_email_domain(self): # 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@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), ( self.assertRaises(GetRealmByDomainException)): 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) diff --git a/zerver/views/messages.py b/zerver/views/messages.py index a26cc8326f..80345a3f97 100644 --- a/zerver/views/messages.py +++ b/zerver/views/messages.py @@ -766,6 +766,8 @@ def same_realm_zephyr_user(user_profile, 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 \ 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.", "") + # Assumes allow_subdomains=False for all RealmAlias's corresponding to + # these realms. return RealmAlias.objects.filter(realm=user_profile.realm, domain=domain).exists() 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. 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() # We do not @require_login for send_message_backend, since it is used diff --git a/zerver/views/realm_aliases.py b/zerver/views/realm_aliases.py index 73529017ad..976354ad2f 100644 --- a/zerver/views/realm_aliases.py +++ b/zerver/views/realm_aliases.py @@ -4,12 +4,12 @@ from django.core.exceptions import ValidationError from django.http import HttpRequest, HttpResponse from django.utils.translation import ugettext as _ -from zerver.decorator import has_request_variables, REQ, require_realm_admin -from zerver.lib.actions import get_realm_aliases, do_add_realm_alias, \ - do_remove_realm_alias +from zerver.decorator import has_request_variables, require_realm_admin, REQ +from zerver.lib.actions import do_add_realm_alias, do_change_realm_alias, \ + do_remove_realm_alias, get_realm_aliases from zerver.lib.domains import validate_domain 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 typing import Text @@ -21,8 +21,8 @@ def list_aliases(request, user_profile): @require_realm_admin @has_request_variables -def create_alias(request, user_profile, domain=REQ(validator=check_string)): - # type: (HttpRequest, UserProfile, Text) -> (HttpResponse) +def create_alias(request, user_profile, domain=REQ(validator=check_string), allow_subdomains=REQ(validator=check_bool)): + # type: (HttpRequest, UserProfile, Text, bool) -> (HttpResponse) domain = domain.strip().lower() try: 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}) if not can_add_alias(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]}) +@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 @has_request_variables def delete_alias(request, user_profile, domain): diff --git a/zproject/urls.py b/zproject/urls.py index 2148f55d6d..2cbc714d8f 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -165,12 +165,13 @@ v1_api_and_json_patterns = [ # 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'), - # realm/aliases -> zerver.views.realm_aliases + # realm/domains -> zerver.views.realm_aliases url(r'^realm/domains$', rest_dispatch, {'GET': 'zerver.views.realm_aliases.list_aliases', 'POST': 'zerver.views.realm_aliases.create_alias'}), url(r'^realm/domains/(?P\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 url(r'^realm/emoji$', rest_dispatch,