mirror of
https://github.com/zulip/zulip.git
synced 2025-11-06 15:03:34 +00:00
Add option for hosting each realm on its own subdomain.
This adds support for running a Zulip production server with each realm on its own unique subdomain, e.g. https://realm_name.example.com. This patch includes a ton of important features: * Configuring the Zulip sesion middleware to issue cookier correctly for the subdomains case. * Throwing an error if the user tries to visit an invalid subdomain. * Runs a portion of the Casper tests with REALMS_HAVE_SUBDOMAINS enabled to test the subdomain signup process. * Updating our integrations documentation to refer to the current subdomain. * Enforces that users can only login to the subdomain of their realm (but does not restrict the API; that will be tightened in a future commit). Note that toggling settings.REALMS_HAVE_SUBDOMAINS on a live server is not supported without manual intervention (the main problem will be adding "subdomain" values for all the existing realms). [substantially modified by tabbott as part of merging]
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
var REALMS_HAVE_SUBDOMAINS = casper.cli.get('subdomains');
|
||||||
var common = (function () {
|
var common = (function () {
|
||||||
|
|
||||||
var exports = {};
|
var exports = {};
|
||||||
@@ -83,7 +84,13 @@ exports.then_log_in = function (credentials) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.start_and_log_in = function (credentials, viewport) {
|
exports.start_and_log_in = function (credentials, viewport) {
|
||||||
casper.start('http://127.0.0.1:9981/accounts/login', function () {
|
var log_in_url = "";
|
||||||
|
if (REALMS_HAVE_SUBDOMAINS) {
|
||||||
|
log_in_url = "http://zulip.zulipdev.com:9981/accounts/login";
|
||||||
|
} else {
|
||||||
|
log_in_url = "http://localhost:9981/accounts/login";
|
||||||
|
}
|
||||||
|
casper.start(log_in_url, function () {
|
||||||
exports.initialize_casper(viewport);
|
exports.initialize_casper(viewport);
|
||||||
log_in(credentials);
|
log_in(credentials);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,9 +2,20 @@ var common = require('../casper_lib/common.js').common;
|
|||||||
|
|
||||||
var email = 'alice@test.example.com';
|
var email = 'alice@test.example.com';
|
||||||
var domain = 'test.example.com';
|
var domain = 'test.example.com';
|
||||||
|
var subdomain = 'testsubdomain';
|
||||||
var organization_name = 'Awesome Organization';
|
var organization_name = 'Awesome Organization';
|
||||||
|
var REALMS_HAVE_SUBDOMAINS = casper.cli.get('subdomains');
|
||||||
|
var host;
|
||||||
|
var realm_host;
|
||||||
|
|
||||||
casper.start('http://127.0.0.1:9981/create_realm/');
|
if (REALMS_HAVE_SUBDOMAINS) {
|
||||||
|
host = 'zulipdev.com:9981';
|
||||||
|
realm_host = subdomain + '.' + host;
|
||||||
|
} else {
|
||||||
|
host = realm_host = 'localhost:9981';
|
||||||
|
}
|
||||||
|
|
||||||
|
casper.start('http://' + host + '/create_realm/');
|
||||||
|
|
||||||
casper.then(function () {
|
casper.then(function () {
|
||||||
// Submit the email for realm creation
|
// Submit the email for realm creation
|
||||||
@@ -21,12 +32,12 @@ casper.then(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Special endpoint enabled only during tests for extracting confirmation key
|
// Special endpoint enabled only during tests for extracting confirmation key
|
||||||
casper.thenOpen('http://127.0.0.1:9981/confirmation_key/');
|
casper.thenOpen('http://' + host + '/confirmation_key/');
|
||||||
|
|
||||||
// Open the confirmation URL
|
// Open the confirmation URL
|
||||||
casper.then(function () {
|
casper.then(function () {
|
||||||
var confirmation_key = JSON.parse(this.getPageContent()).confirmation_key;
|
var confirmation_key = JSON.parse(this.getPageContent()).confirmation_key;
|
||||||
var confirmation_url = 'http://127.0.0.1:9981/accounts/do_confirm/' + confirmation_key;
|
var confirmation_url = 'http://' + host + '/accounts/do_confirm/' + confirmation_key;
|
||||||
this.thenOpen(confirmation_url);
|
this.thenOpen(confirmation_url);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,16 +58,26 @@ casper.then(function () {
|
|||||||
|
|
||||||
casper.then(function () {
|
casper.then(function () {
|
||||||
this.waitForSelector('form[action^="/accounts/register/"]', function () {
|
this.waitForSelector('form[action^="/accounts/register/"]', function () {
|
||||||
this.fill('form[action^="/accounts/register/"]', {
|
if (REALMS_HAVE_SUBDOMAINS) {
|
||||||
full_name: 'Alice',
|
this.fill('form[action^="/accounts/register/"]', {
|
||||||
realm_name: organization_name,
|
full_name: 'Alice',
|
||||||
password: 'password',
|
realm_name: organization_name,
|
||||||
terms: true
|
realm_subdomain: subdomain,
|
||||||
}, true);
|
password: 'password',
|
||||||
|
terms: true
|
||||||
|
}, true);
|
||||||
|
} else {
|
||||||
|
this.fill('form[action^="/accounts/register/"]', {
|
||||||
|
full_name: 'Alice',
|
||||||
|
realm_name: organization_name,
|
||||||
|
password: 'password',
|
||||||
|
terms: true
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.waitWhileSelector('form[action^="/accounts/register/"]', function () {
|
this.waitWhileSelector('form[action^="/accounts/register/"]', function () {
|
||||||
casper.test.assertUrlMatch('http://127.0.0.1:9981/invite/', 'Invite more users page loaded');
|
casper.test.assertUrlMatch(realm_host + '/invite/', 'Invite more users page loaded');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,7 +96,7 @@ casper.then(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.waitWhileSelector('#submit_invitation', function () {
|
this.waitWhileSelector('#submit_invitation', function () {
|
||||||
this.test.assertUrlMatch('http://127.0.0.1:9981/', 'Realm created and logged in');
|
this.test.assertUrlMatch(realm_host, 'Realm created and logged in');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
var common = require('../casper_lib/common.js').common;
|
var common = require('../casper_lib/common.js').common;
|
||||||
|
var REALMS_HAVE_SUBDOMAINS = casper.cli.get('subdomains');
|
||||||
|
|
||||||
|
var realm_url = "";
|
||||||
|
if (REALMS_HAVE_SUBDOMAINS) {
|
||||||
|
realm_url = "http://zulip.zulipdev.com:9981/";
|
||||||
|
} else {
|
||||||
|
realm_url = "http://localhost:9981/";
|
||||||
|
}
|
||||||
// Start of test script.
|
// Start of test script.
|
||||||
casper.start('http://127.0.0.1:9981/', common.initialize_casper);
|
casper.start(realm_url, common.initialize_casper);
|
||||||
|
|
||||||
casper.then(function () {
|
casper.then(function () {
|
||||||
casper.test.assertHttpStatus(302);
|
casper.test.assertHttpStatus(302);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
var common = require('../casper_lib/common.js').common;
|
var common = require('../casper_lib/common.js').common;
|
||||||
var test_credentials = require('../../var/casper/test_credentials.js').test_credentials;
|
var test_credentials = require('../../var/casper/test_credentials.js').test_credentials;
|
||||||
|
var REALMS_HAVE_SUBDOMAINS = casper.cli.get('subdomains');
|
||||||
|
|
||||||
common.start_and_log_in();
|
common.start_and_log_in();
|
||||||
|
|
||||||
@@ -166,7 +167,14 @@ casper.waitForSelector("#default_language", function () {
|
|||||||
casper.test.info("Opening German page through i18n url.");
|
casper.test.info("Opening German page through i18n url.");
|
||||||
});
|
});
|
||||||
|
|
||||||
casper.thenOpen('http://127.0.0.1:9981/de/#settings');
|
var settings_url = "";
|
||||||
|
if (REALMS_HAVE_SUBDOMAINS) {
|
||||||
|
settings_url = 'http://zulip.zulipdev.com:9981/de/#settings';
|
||||||
|
} else {
|
||||||
|
settings_url = 'http://localhost:9981/de/#settings';
|
||||||
|
}
|
||||||
|
|
||||||
|
casper.thenOpen(settings_url);
|
||||||
|
|
||||||
casper.waitForSelector("#settings-change-box", function check_url_preference() {
|
casper.waitForSelector("#settings-change-box", function check_url_preference() {
|
||||||
casper.test.info("Checking the i18n url language precedence.");
|
casper.test.info("Checking the i18n url language precedence.");
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ casper.then(function () {
|
|||||||
casper.waitForSelector('.admin-emoji-form', function () {
|
casper.waitForSelector('.admin-emoji-form', function () {
|
||||||
casper.fill('form.admin-emoji-form', {
|
casper.fill('form.admin-emoji-form', {
|
||||||
'name': 'MouseFace',
|
'name': 'MouseFace',
|
||||||
'url': 'http://127.0.0.1:9991/static/images/integrations/logos/jenkins.png'
|
'url': 'http://localhost:9991/static/images/integrations/logos/jenkins.png'
|
||||||
});
|
});
|
||||||
casper.click('form.admin-emoji-form input.button');
|
casper.click('form.admin-emoji-form input.button');
|
||||||
});
|
});
|
||||||
@@ -159,7 +159,7 @@ casper.then(function () {
|
|||||||
casper.then(function () {
|
casper.then(function () {
|
||||||
casper.waitForSelector('.emoji_row', function () {
|
casper.waitForSelector('.emoji_row', function () {
|
||||||
casper.test.assertSelectorHasText('.emoji_row .emoji_name', 'MouseFace');
|
casper.test.assertSelectorHasText('.emoji_row .emoji_name', 'MouseFace');
|
||||||
casper.test.assertExists('.emoji_row img[src="http://127.0.0.1:9991/static/images/integrations/logos/jenkins.png"]');
|
casper.test.assertExists('.emoji_row img[src="http://localhost:9991/static/images/integrations/logos/jenkins.png"]');
|
||||||
casper.click('.emoji_row button.delete');
|
casper.click('.emoji_row button.delete');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ def server_is_up(server):
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def run_tests(files):
|
def run_tests(realms_have_subdomains, files):
|
||||||
# type: (Iterable[str]) -> None
|
# type: (bool, Iterable[str]) -> None
|
||||||
test_files = []
|
test_files = []
|
||||||
for file in files:
|
for file in files:
|
||||||
if not os.path.exists(file):
|
if not os.path.exists(file):
|
||||||
@@ -78,7 +78,7 @@ def run_tests(files):
|
|||||||
if options.remote_debug:
|
if options.remote_debug:
|
||||||
remote_debug = "--remote-debugger-port=7777 --remote-debugger-autorun=yes"
|
remote_debug = "--remote-debugger-port=7777 --remote-debugger-autorun=yes"
|
||||||
|
|
||||||
cmd = "frontend_tests/casperjs/bin/casperjs %s test " % (remote_debug,)
|
cmd = "frontend_tests/casperjs/bin/casperjs %s test --subdomains=%s " % (remote_debug, realms_have_subdomains,)
|
||||||
if test_files:
|
if test_files:
|
||||||
cmd += ' '.join(test_files)
|
cmd += ' '.join(test_files)
|
||||||
else:
|
else:
|
||||||
@@ -115,5 +115,13 @@ Oops, the frontend tests failed. Tips for debugging:
|
|||||||
|
|
||||||
sys.exit(ret)
|
sys.exit(ret)
|
||||||
|
|
||||||
run_tests(args)
|
# Run tests with REALMS_HAVE_SUBDOMAINS set to True
|
||||||
|
run_tests(False, args)
|
||||||
|
os.environ["EXTERNAL_HOST"] = "zulipdev.com:9981"
|
||||||
|
os.environ["REALMS_HAVE_SUBDOMAINS"] = "True"
|
||||||
|
# Run tests with REALMS_HAVE_SUBDOMAINS set to True
|
||||||
|
if len(args) == 0:
|
||||||
|
run_tests(True, ["00-realm-creation.js", "01-login.js", "02-site.js"])
|
||||||
|
else:
|
||||||
|
run_tests(True, args)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|||||||
9
templates/zerver/invalid_realm.html
Normal file
9
templates/zerver/invalid_realm.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{% extends "zerver/portico.html" %}
|
||||||
|
{% block portico_content %}
|
||||||
|
|
||||||
|
<h3>{{ _('Organization Does Not Exist') }}</h3>
|
||||||
|
|
||||||
|
<p>{{ _('Hi there! Thank you for your interest in Zulip') }}.</p>
|
||||||
|
<p>{% trans %}There is no Zulip organization hosted at this subdomain{% endtrans %}.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -33,6 +33,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
|
|||||||
<input type='text' disabled="true" placeholder="{{ email }}" />
|
<input type='text' disabled="true" placeholder="{{ email }}" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="id_full_name" class="control-label">{{ _('Full name') }}</label>
|
<label for="id_full_name" class="control-label">{{ _('Full name') }}</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
@@ -51,7 +52,7 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if creating_new_team %}
|
{% if creating_new_team %}
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label for="id_team_name" class="control-label">{{ _('Organization name') }}</label>
|
<label for="id_team_name" class="control-label">{{ _('Organization name') }}</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
@@ -65,7 +66,24 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<br /><span class="small">{{ _('You can change this later on the admin page.') }}</span>
|
<br /><span class="small">{{ _('You can change this later on the admin page.') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if realms_have_subdomains %}
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="id_team_subdomain" class="control-label">{{ _('Subdomain') }}</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input id="id_team_subdomain" class="required" type="text"
|
||||||
|
placeholder="{{ _("E.g. acme") }}"
|
||||||
|
name="realm_subdomain" maxlength="40" /><b> .{{ external_host }}</b>
|
||||||
|
{% if form.realm_subdomain.errors %}
|
||||||
|
{% for error in form.realm_subdomain.errors %}
|
||||||
|
<div class="alert alert-error">{{ error }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<br /><span class="small"><p>{% trans %}The address you'll use to sign in to your organization.{% endtrans %}.</p></span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if password_auth_enabled %}
|
{% if password_auth_enabled %}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from django.utils.timezone import now
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from zerver.lib.queue import queue_json_publish
|
from zerver.lib.queue import queue_json_publish
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.utils import statsd
|
from zerver.lib.utils import statsd, get_subdomain, check_subdomain
|
||||||
from zerver.exceptions import RateLimited
|
from zerver.exceptions import RateLimited
|
||||||
from zerver.lib.rate_limiter import incr_ratelimit, is_ratelimited, \
|
from zerver.lib.rate_limiter import incr_ratelimit, is_ratelimited, \
|
||||||
api_calls_left
|
api_calls_left
|
||||||
@@ -279,7 +279,7 @@ def logged_in_and_active(request):
|
|||||||
return False
|
return False
|
||||||
if request.user.realm.deactivated:
|
if request.user.realm.deactivated:
|
||||||
return False
|
return False
|
||||||
return True
|
return check_subdomain(get_subdomain(request), request.user.realm.subdomain)
|
||||||
|
|
||||||
# Based on Django 1.8's @login_required
|
# Based on Django 1.8's @login_required
|
||||||
def zulip_login_required(function=None,
|
def zulip_login_required(function=None,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ from django.db.models.query import QuerySet
|
|||||||
from jinja2 import Markup as mark_safe
|
from jinja2 import Markup as mark_safe
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from zerver.models import resolve_subdomain_to_realm
|
||||||
|
from zerver.lib.utils import get_subdomain, check_subdomain
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -23,6 +25,11 @@ from six import text_type
|
|||||||
|
|
||||||
SIGNUP_STRING = u'Your e-mail does not match any existing open organization. ' + \
|
SIGNUP_STRING = u'Your e-mail does not match any existing open organization. ' + \
|
||||||
u'Use a different e-mail address, or contact %s with questions.' % (settings.ZULIP_ADMINISTRATOR,)
|
u'Use a different e-mail address, or contact %s with questions.' % (settings.ZULIP_ADMINISTRATOR,)
|
||||||
|
|
||||||
|
def subdomain_unavailable(subdomain):
|
||||||
|
# type: (text_type) -> text_type
|
||||||
|
return u"The subdomain '%s' is not available. Please choose another one." % (subdomain)
|
||||||
|
|
||||||
if settings.SHOW_OSS_ANNOUNCEMENT:
|
if settings.SHOW_OSS_ANNOUNCEMENT:
|
||||||
SIGNUP_STRING = u'Your e-mail does not match any existing organization. <br />' + \
|
SIGNUP_STRING = u'Your e-mail does not match any existing organization. <br />' + \
|
||||||
u"The zulip.com service is not taking new customer teams. <br /> " + \
|
u"The zulip.com service is not taking new customer teams. <br /> " + \
|
||||||
@@ -32,7 +39,10 @@ if settings.SHOW_OSS_ANNOUNCEMENT:
|
|||||||
MIT_VALIDATION_ERROR = u'That user does not exist at MIT or is a ' + \
|
MIT_VALIDATION_ERROR = u'That user does not exist at MIT or is a ' + \
|
||||||
u'<a href="https://ist.mit.edu/email-lists">mailing list</a>. ' + \
|
u'<a href="https://ist.mit.edu/email-lists">mailing list</a>. ' + \
|
||||||
u'If you want to sign up an alias for Zulip, ' + \
|
u'If you want to sign up an alias for Zulip, ' + \
|
||||||
u'<a href="mailto:"' + settings.ZULIP_ADMINISTRATOR + '">contact us</a>.'
|
u'<a href="mailto:support@zulipchat.com">contact us</a>.'
|
||||||
|
WRONG_SUBDOMAIN_ERROR = "Your Zulip account is not a member of the " + \
|
||||||
|
"organization associated with this subdomain. " + \
|
||||||
|
"Please contact %s with any questions!" % (settings.ZULIP_ADMINISTRATOR,)
|
||||||
|
|
||||||
def get_registration_string(domain):
|
def get_registration_string(domain):
|
||||||
# type: (text_type) -> text_type
|
# type: (text_type) -> text_type
|
||||||
@@ -73,10 +83,18 @@ class RegistrationForm(forms.Form):
|
|||||||
password = forms.CharField(widget=forms.PasswordInput, max_length=100,
|
password = forms.CharField(widget=forms.PasswordInput, max_length=100,
|
||||||
required=False)
|
required=False)
|
||||||
realm_name = forms.CharField(max_length=100, required=False)
|
realm_name = forms.CharField(max_length=100, required=False)
|
||||||
|
realm_subdomain = forms.CharField(max_length=40, required=False)
|
||||||
if settings.TERMS_OF_SERVICE:
|
if settings.TERMS_OF_SERVICE:
|
||||||
terms = forms.BooleanField(required=True)
|
terms = forms.BooleanField(required=True)
|
||||||
|
|
||||||
|
def clean_realm_subdomain(self):
|
||||||
|
# type: () -> str
|
||||||
|
data = self.cleaned_data['realm_subdomain']
|
||||||
|
realm = resolve_subdomain_to_realm(data)
|
||||||
|
if realm is not None:
|
||||||
|
raise ValidationError(subdomain_unavailable(data))
|
||||||
|
return data
|
||||||
|
|
||||||
class ToSForm(forms.Form):
|
class ToSForm(forms.Form):
|
||||||
terms = forms.BooleanField(required=True)
|
terms = forms.BooleanField(required=True)
|
||||||
|
|
||||||
@@ -89,8 +107,11 @@ class HomepageForm(forms.Form):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# type: (*Any, **Any) -> None
|
# type: (*Any, **Any) -> None
|
||||||
self.domain = kwargs.get("domain")
|
self.domain = kwargs.get("domain")
|
||||||
|
self.subdomain = kwargs.get("subdomain")
|
||||||
if "domain" in kwargs:
|
if "domain" in kwargs:
|
||||||
del kwargs["domain"]
|
del kwargs["domain"]
|
||||||
|
if "subdomain" in kwargs:
|
||||||
|
del kwargs["subdomain"]
|
||||||
super(HomepageForm, self).__init__(*args, **kwargs)
|
super(HomepageForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def clean_email(self):
|
def clean_email(self):
|
||||||
@@ -106,6 +127,12 @@ class HomepageForm(forms.Form):
|
|||||||
if completely_open(self.domain):
|
if completely_open(self.domain):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
# If the subdomain encodes a complete open realm, pass
|
||||||
|
subdomain_realm = resolve_subdomain_to_realm(self.subdomain)
|
||||||
|
if (subdomain_realm is not None and
|
||||||
|
completely_open(subdomain_realm.domain)):
|
||||||
|
return data
|
||||||
|
|
||||||
# If no realm is specified, fail
|
# If no realm is specified, fail
|
||||||
realm = get_valid_realm(data)
|
realm = get_valid_realm(data)
|
||||||
if realm is None:
|
if realm is None:
|
||||||
@@ -191,4 +218,8 @@ Please contact %s to reactivate this group.""" % (
|
|||||||
settings.ZULIP_ADMINISTRATOR)
|
settings.ZULIP_ADMINISTRATOR)
|
||||||
raise ValidationError(mark_safe(error_msg))
|
raise ValidationError(mark_safe(error_msg))
|
||||||
|
|
||||||
|
if not check_subdomain(get_subdomain(self.request), user_profile.realm.subdomain):
|
||||||
|
logging.warning("User %s attempted to password login to wrong subdomain %s" %
|
||||||
|
(user_profile.email, get_subdomain(self.request)))
|
||||||
|
raise ValidationError(mark_safe(WRONG_SUBDOMAIN_ERROR))
|
||||||
return email
|
return email
|
||||||
|
|||||||
@@ -1929,12 +1929,12 @@ def do_change_stream_description(realm, stream_name, new_description):
|
|||||||
value=new_description)
|
value=new_description)
|
||||||
send_event(event, stream_user_ids(stream))
|
send_event(event, stream_user_ids(stream))
|
||||||
|
|
||||||
def do_create_realm(domain, name, restricted_to_domain=True):
|
def do_create_realm(domain, name, restricted_to_domain=True, subdomain=None):
|
||||||
# type: (text_type, text_type, bool) -> Tuple[Realm, bool]
|
# type: (text_type, text_type, bool, Optional[text_type]) -> Tuple[Realm, bool]
|
||||||
realm = get_realm(domain)
|
realm = get_realm(domain)
|
||||||
created = not realm
|
created = not realm
|
||||||
if created:
|
if created:
|
||||||
realm = Realm(domain=domain, name=name,
|
realm = Realm(domain=domain, name=name, subdomain=subdomain,
|
||||||
restricted_to_domain=restricted_to_domain)
|
restricted_to_domain=restricted_to_domain)
|
||||||
realm.save()
|
realm.save()
|
||||||
|
|
||||||
|
|||||||
@@ -384,18 +384,24 @@ class ZulipTestCase(TestCase):
|
|||||||
{'email': username + "@" + domain})
|
{'email': username + "@" + domain})
|
||||||
return self.submit_reg_form_for_user(username, password, domain=domain)
|
return self.submit_reg_form_for_user(username, password, domain=domain)
|
||||||
|
|
||||||
def submit_reg_form_for_user(self, username, password, domain="zulip.com"):
|
def submit_reg_form_for_user(self, username, password, domain="zulip.com",
|
||||||
# type: (text_type, text_type, text_type) -> HttpResponse
|
realm_name=None, realm_subdomain=None, **kwargs):
|
||||||
|
# type: (text_type, text_type, text_type, Optional[text_type], Optional[text_type], **Any) -> HttpResponse
|
||||||
"""
|
"""
|
||||||
Stage two of the two-step registration process.
|
Stage two of the two-step registration process.
|
||||||
|
|
||||||
If things are working correctly the account should be fully
|
If things are working correctly the account should be fully
|
||||||
registered after this call.
|
registered after this call.
|
||||||
|
|
||||||
|
You can pass the HTTP_HOST variable for subdomains via kwargs.
|
||||||
"""
|
"""
|
||||||
return self.client_post('/accounts/register/',
|
return self.client_post('/accounts/register/',
|
||||||
{'full_name': username, 'password': password,
|
{'full_name': username, 'password': password,
|
||||||
|
'realm_name': realm_name,
|
||||||
|
'realm_subdomain': realm_subdomain,
|
||||||
'key': find_key_by_email(username + '@' + domain),
|
'key': find_key_by_email(username + '@' + domain),
|
||||||
'terms': True})
|
'terms': True},
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
def get_confirmation_url_from_outbox(self, email_address, path_pattern="(\S+)>"):
|
def get_confirmation_url_from_outbox(self, email_address, path_pattern="(\S+)>"):
|
||||||
# type: (text_type, text_type) -> text_type
|
# type: (text_type, text_type) -> text_type
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import os
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.http import HttpRequest
|
||||||
from six.moves import range
|
from six.moves import range
|
||||||
from zerver.lib.str_utils import force_text
|
from zerver.lib.str_utils import force_text
|
||||||
|
|
||||||
@@ -184,3 +185,21 @@ def query_chunker(queries, id_collector=None, chunk_size=1000, db_chunk_size=Non
|
|||||||
id_collector.update(tup_ids)
|
id_collector.update(tup_ids)
|
||||||
|
|
||||||
yield [row for row_id, i, row in tup_chunk]
|
yield [row for row_id, i, row in tup_chunk]
|
||||||
|
|
||||||
|
def get_subdomain(request):
|
||||||
|
# type: (HttpRequest) -> text_type
|
||||||
|
domain = request.get_host().lower()
|
||||||
|
index = domain.find("." + settings.EXTERNAL_HOST)
|
||||||
|
if index == -1:
|
||||||
|
return ""
|
||||||
|
subdomain = domain[0:index]
|
||||||
|
return subdomain
|
||||||
|
|
||||||
|
def check_subdomain(realm_subdomain, user_subdomain):
|
||||||
|
# type: (text_type, text_type) -> bool
|
||||||
|
if settings.REALMS_HAVE_SUBDOMAINS and realm_subdomain is not None:
|
||||||
|
if (realm_subdomain == "" and user_subdomain is None):
|
||||||
|
return True
|
||||||
|
if realm_subdomain != user_subdomain:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|||||||
@@ -10,16 +10,18 @@ from zerver.lib.response import json_error
|
|||||||
from zerver.lib.request import JsonableError
|
from zerver.lib.request import JsonableError
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from zerver.lib.utils import statsd
|
from zerver.lib.utils import statsd, get_subdomain
|
||||||
from zerver.lib.queue import queue_json_publish
|
from zerver.lib.queue import queue_json_publish
|
||||||
from zerver.lib.cache import get_remote_cache_time, get_remote_cache_requests
|
from zerver.lib.cache import get_remote_cache_time, get_remote_cache_requests
|
||||||
from zerver.lib.bugdown import get_bugdown_time, get_bugdown_requests
|
from zerver.lib.bugdown import get_bugdown_time, get_bugdown_requests
|
||||||
from zerver.models import flush_per_request_caches
|
from zerver.models import flush_per_request_caches, resolve_subdomain_to_realm
|
||||||
from zerver.exceptions import RateLimited
|
from zerver.exceptions import RateLimited
|
||||||
from django.contrib.sessions.middleware import SessionMiddleware
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.views.csrf import csrf_failure as html_csrf_failure
|
from django.views.csrf import csrf_failure as html_csrf_failure
|
||||||
from django.utils.cache import patch_vary_headers
|
from django.utils.cache import patch_vary_headers
|
||||||
from django.utils.http import cookie_date
|
from django.utils.http import cookie_date
|
||||||
|
from zproject.jinja2 import render_to_response
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
@@ -346,6 +348,17 @@ class FlushDisplayRecipientCache(object):
|
|||||||
class SessionHostDomainMiddleware(SessionMiddleware):
|
class SessionHostDomainMiddleware(SessionMiddleware):
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
# type: (HttpRequest, HttpResponse) -> HttpResponse
|
# type: (HttpRequest, HttpResponse) -> HttpResponse
|
||||||
|
if settings.REALMS_HAVE_SUBDOMAINS:
|
||||||
|
if (not request.path.startswith("/static/") and not request.path.startswith("/api/")
|
||||||
|
and not request.path.startswith("/json/")):
|
||||||
|
subdomain = get_subdomain(request)
|
||||||
|
if (request.get_host() == "127.0.0.1:9991" or request.get_host() == "localhost:9991"):
|
||||||
|
return redirect("%s%s" % (settings.EXTERNAL_URI_SCHEME,
|
||||||
|
settings.EXTERNAL_HOST))
|
||||||
|
if subdomain != "":
|
||||||
|
realm = resolve_subdomain_to_realm(subdomain)
|
||||||
|
if (realm is None):
|
||||||
|
return render_to_response("zerver/invalid_realm.html")
|
||||||
"""
|
"""
|
||||||
If request.session was modified, or if the configuration is to save the
|
If request.session was modified, or if the configuration is to save the
|
||||||
session every time, save the changes and set a session cookie.
|
session every time, save the changes and set a session cookie.
|
||||||
@@ -371,9 +384,15 @@ class SessionHostDomainMiddleware(SessionMiddleware):
|
|||||||
if response.status_code != 500:
|
if response.status_code != 500:
|
||||||
request.session.save()
|
request.session.save()
|
||||||
host = request.get_host().split(':')[0]
|
host = request.get_host().split(':')[0]
|
||||||
|
|
||||||
session_cookie_domain = settings.SESSION_COOKIE_DOMAIN
|
session_cookie_domain = settings.SESSION_COOKIE_DOMAIN
|
||||||
if host.endswith(".e.zulip.com"):
|
# The subdomains feature overrides the
|
||||||
session_cookie_domain = ".e.zulip.com"
|
# SESSION_COOKIE_DOMAIN setting, since the setting
|
||||||
|
# is a fixed value and with subdomains enabled,
|
||||||
|
# the session cookie domain has to vary with the
|
||||||
|
# subdomain.
|
||||||
|
if settings.REALMS_HAVE_SUBDOMAINS:
|
||||||
|
session_cookie_domain = host
|
||||||
response.set_cookie(settings.SESSION_COOKIE_NAME,
|
response.set_cookie(settings.SESSION_COOKIE_NAME,
|
||||||
request.session.session_key, max_age=max_age,
|
request.session.session_key, max_age=max_age,
|
||||||
expires=expires, domain=session_cookie_domain,
|
expires=expires, domain=session_cookie_domain,
|
||||||
|
|||||||
36
zerver/migrations/0029_realm_subdomain.py
Normal file
36
zerver/migrations/0029_realm_subdomain.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
|
def set_subdomain_of_default_realm(apps, schema_editor):
|
||||||
|
# type: (StateApps, DatabaseSchemaEditor) -> None
|
||||||
|
if settings.DEVELOPMENT:
|
||||||
|
Realm = apps.get_model('zerver', 'Realm')
|
||||||
|
try:
|
||||||
|
default_realm = Realm.objects.get(domain="zulip.com")
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
default_realm = None
|
||||||
|
|
||||||
|
if default_realm is not None:
|
||||||
|
default_realm.subdomain = "zulip"
|
||||||
|
default_realm.save()
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0028_userprofile_tos_version'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='realm',
|
||||||
|
name='subdomain',
|
||||||
|
field=models.CharField(max_length=40, unique=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.RunPython(set_subdomain_of_default_realm)
|
||||||
|
]
|
||||||
@@ -140,6 +140,7 @@ class Realm(ModelReprMixin, models.Model):
|
|||||||
# name is the user-visible identifier for the realm. It has no required
|
# name is the user-visible identifier for the realm. It has no required
|
||||||
# structure.
|
# structure.
|
||||||
name = models.CharField(max_length=40, null=True) # type: Optional[text_type]
|
name = models.CharField(max_length=40, null=True) # type: Optional[text_type]
|
||||||
|
subdomain = models.CharField(max_length=40, null=True, unique=True) # type: Optional[text_type]
|
||||||
restricted_to_domain = models.BooleanField(default=True) # type: bool
|
restricted_to_domain = models.BooleanField(default=True) # type: bool
|
||||||
invite_required = models.BooleanField(default=False) # type: bool
|
invite_required = models.BooleanField(default=False) # type: bool
|
||||||
invite_by_admins_only = models.BooleanField(default=False) # type: bool
|
invite_by_admins_only = models.BooleanField(default=False) # type: bool
|
||||||
@@ -199,11 +200,16 @@ class Realm(ModelReprMixin, models.Model):
|
|||||||
@property
|
@property
|
||||||
def uri(self):
|
def uri(self):
|
||||||
# type: () -> str
|
# type: () -> str
|
||||||
|
if settings.REALMS_HAVE_SUBDOMAINS and self.subdomain is not None:
|
||||||
|
return '%s%s.%s' % (settings.EXTERNAL_URI_SCHEME,
|
||||||
|
self.subdomain, settings.EXTERNAL_HOST)
|
||||||
return settings.SERVER_URI
|
return settings.SERVER_URI
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self):
|
def host(self):
|
||||||
# type: () -> str
|
# type: () -> str
|
||||||
|
if settings.REALMS_HAVE_SUBDOMAINS and self.subdomain is not None:
|
||||||
|
return "%s.%s" % (self.subdomain, settings.EXTERNAL_HOST)
|
||||||
return settings.EXTERNAL_HOST
|
return settings.EXTERNAL_HOST
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -258,6 +264,13 @@ def resolve_email_to_domain(email):
|
|||||||
domain = alias.realm.domain
|
domain = alias.realm.domain
|
||||||
return domain
|
return domain
|
||||||
|
|
||||||
|
def resolve_subdomain_to_realm(subdomain):
|
||||||
|
# type: (text_type) -> Optional[Realm]
|
||||||
|
try:
|
||||||
|
return Realm.objects.get(subdomain=subdomain)
|
||||||
|
except Realm.DoesNotExist:
|
||||||
|
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?
|
||||||
# (This function does not check whether the user has been invited to the realm.
|
# (This function does not check whether the user has been invited to the realm.
|
||||||
# So for invite-only realms, this is the test for whether a user can be invited,
|
# So for invite-only realms, this is the test for whether a user can be invited,
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from __future__ import absolute_import
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.conf import settings
|
||||||
|
from django.test import TestCase, override_settings
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from zproject.settings import DEPLOY_ROOT
|
from zproject.settings import DEPLOY_ROOT
|
||||||
@@ -17,9 +18,27 @@ class IntegrationTest(TestCase):
|
|||||||
for integration in INTEGRATIONS.values():
|
for integration in INTEGRATIONS.values():
|
||||||
self.assertTrue(os.path.isfile(os.path.join(DEPLOY_ROOT, integration.logo)))
|
self.assertTrue(os.path.isfile(os.path.join(DEPLOY_ROOT, integration.logo)))
|
||||||
|
|
||||||
|
@override_settings(REALMS_HAVE_SUBDOMAINS=False)
|
||||||
def test_api_url_view_base(self):
|
def test_api_url_view_base(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
context = dict() # type: Dict[str, Any]
|
context = dict() # type: Dict[str, Any]
|
||||||
add_api_uri_context(context, HostRequestMock())
|
add_api_uri_context(context, HostRequestMock())
|
||||||
self.assertEqual(context["external_api_path_subdomain"], "localhost:9991/api")
|
self.assertEqual(context["external_api_path_subdomain"], "zulipdev.com:9991/api")
|
||||||
self.assertEqual(context["external_api_uri_subdomain"], "http://localhost:9991/api")
|
self.assertEqual(context["external_api_uri_subdomain"], "http://zulipdev.com:9991/api")
|
||||||
|
|
||||||
|
@override_settings(REALMS_HAVE_SUBDOMAINS=True)
|
||||||
|
def test_api_url_view_subdomains_base(self):
|
||||||
|
# type: () -> None
|
||||||
|
context = dict() # type: Dict[str, Any]
|
||||||
|
add_api_uri_context(context, HostRequestMock())
|
||||||
|
self.assertEqual(context["external_api_path_subdomain"], "yourZulipDomain.zulipdev.com:9991/api")
|
||||||
|
self.assertEqual(context["external_api_uri_subdomain"], "http://yourZulipDomain.zulipdev.com:9991/api")
|
||||||
|
|
||||||
|
@override_settings(REALMS_HAVE_SUBDOMAINS=True, EXTERNAL_HOST="zulipdev.com")
|
||||||
|
def test_api_url_view_subdomains_full(self):
|
||||||
|
# type: () -> None
|
||||||
|
context = dict() # type: Dict[str, Any]
|
||||||
|
request = HostRequestMock(host="mysubdomain.zulipdev.com")
|
||||||
|
add_api_uri_context(context, request)
|
||||||
|
self.assertEqual(context["external_api_path_subdomain"], "mysubdomain.zulipdev.com:9991/api")
|
||||||
|
self.assertEqual(context["external_api_uri_subdomain"], "http://mysubdomain.zulipdev.com:9991/api")
|
||||||
|
|||||||
@@ -734,3 +734,54 @@ class UserSignUpTest(ZulipTestCase):
|
|||||||
self.assertEqual(user_profile.default_language, realm.default_language)
|
self.assertEqual(user_profile.default_language, realm.default_language)
|
||||||
from django.core.mail import outbox
|
from django.core.mail import outbox
|
||||||
outbox.pop()
|
outbox.pop()
|
||||||
|
|
||||||
|
def test_create_realm_with_subdomain(self):
|
||||||
|
# type: () -> None
|
||||||
|
username = "user1"
|
||||||
|
password = "test"
|
||||||
|
domain = "test.com"
|
||||||
|
email = "user1@test.com"
|
||||||
|
subdomain = "test"
|
||||||
|
realm_name = "Test"
|
||||||
|
|
||||||
|
# Make sure the realm does not exist
|
||||||
|
self.assertIsNone(get_realm(domain))
|
||||||
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True), self.settings(OPEN_REALM_CREATION=True):
|
||||||
|
# Create new realm with the email
|
||||||
|
result = self.client_post('/create_realm/', {'email': email})
|
||||||
|
self.assertEquals(result.status_code, 302)
|
||||||
|
self.assertTrue(result["Location"].endswith(
|
||||||
|
"/accounts/send_confirm/%s@%s" % (username, domain)))
|
||||||
|
result = self.client_get(result["Location"])
|
||||||
|
self.assert_in_response("Check your email so we can get started.", result)
|
||||||
|
# Visit the confirmation link.
|
||||||
|
from django.core.mail import outbox
|
||||||
|
for message in reversed(outbox):
|
||||||
|
if email in message.to:
|
||||||
|
confirmation_link_pattern = re.compile(settings.EXTERNAL_HOST + "(\S+)>")
|
||||||
|
confirmation_url = confirmation_link_pattern.search(
|
||||||
|
message.body).groups()[0]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError("Couldn't find a confirmation email.")
|
||||||
|
|
||||||
|
result = self.client_get(confirmation_url)
|
||||||
|
self.assertEquals(result.status_code, 200)
|
||||||
|
|
||||||
|
result = self.submit_reg_form_for_user(username,
|
||||||
|
password,
|
||||||
|
domain=domain,
|
||||||
|
realm_name=realm_name,
|
||||||
|
realm_subdomain=subdomain,
|
||||||
|
# Pass HTTP_HOST for the target subdomain
|
||||||
|
HTTP_HOST=subdomain + ".testserver")
|
||||||
|
self.assertEquals(result.status_code, 302)
|
||||||
|
|
||||||
|
# Make sure the realm is created
|
||||||
|
realm = get_realm(domain)
|
||||||
|
|
||||||
|
self.assertIsNotNone(realm)
|
||||||
|
self.assertEqual(realm.domain, domain)
|
||||||
|
self.assertEqual(realm.name, realm_name)
|
||||||
|
self.assertEqual(realm.subdomain, subdomain)
|
||||||
|
self.assertEqual(get_user_profile_by_email(email).realm, realm)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from zerver.models import Message, UserProfile, Stream, Subscription, Huddle, \
|
|||||||
get_stream, UserPresence, get_recipient, \
|
get_stream, UserPresence, get_recipient, \
|
||||||
split_email_to_domain, resolve_email_to_domain, email_to_username, get_realm, \
|
split_email_to_domain, resolve_email_to_domain, email_to_username, get_realm, \
|
||||||
completely_open, get_unique_open_realm, remote_user_to_email, email_allowed_for_realm, \
|
completely_open, get_unique_open_realm, remote_user_to_email, email_allowed_for_realm, \
|
||||||
get_cross_realm_users
|
get_cross_realm_users, resolve_subdomain_to_realm
|
||||||
from zerver.lib.actions import do_change_password, do_change_full_name, do_change_is_admin, \
|
from zerver.lib.actions import do_change_password, do_change_full_name, do_change_is_admin, \
|
||||||
do_activate_user, do_create_user, do_create_realm, set_default_streams, \
|
do_activate_user, do_create_user, do_create_realm, set_default_streams, \
|
||||||
internal_send_message, update_user_presence, do_events_register, \
|
internal_send_message, update_user_presence, do_events_register, \
|
||||||
@@ -49,7 +49,7 @@ from zerver.lib.avatar import avatar_url
|
|||||||
from zerver.lib.i18n import get_language_list, get_language_name, \
|
from zerver.lib.i18n import get_language_list, get_language_name, \
|
||||||
get_language_list_for_templates
|
get_language_list_for_templates
|
||||||
from zerver.lib.response import json_success, json_error
|
from zerver.lib.response import json_success, json_error
|
||||||
from zerver.lib.utils import statsd
|
from zerver.lib.utils import statsd, get_subdomain
|
||||||
from version import ZULIP_VERSION
|
from version import ZULIP_VERSION
|
||||||
from zproject.backends import password_auth_enabled, dev_auth_enabled, google_auth_enabled
|
from zproject.backends import password_auth_enabled, dev_auth_enabled, google_auth_enabled
|
||||||
|
|
||||||
@@ -198,7 +198,12 @@ def accounts_register(request):
|
|||||||
|
|
||||||
if realm_creation:
|
if realm_creation:
|
||||||
domain = split_email_to_domain(email)
|
domain = split_email_to_domain(email)
|
||||||
realm = do_create_realm(domain, form.cleaned_data['realm_name'])[0]
|
if settings.REALMS_HAVE_SUBDOMAINS:
|
||||||
|
realm = do_create_realm(domain, form.cleaned_data['realm_name'],
|
||||||
|
subdomain=form.cleaned_data['realm_subdomain'])[0]
|
||||||
|
else:
|
||||||
|
realm = do_create_realm(domain, form.cleaned_data['realm_name'])[0]
|
||||||
|
|
||||||
set_default_streams(realm, settings.DEFAULT_NEW_REALM_STREAMS)
|
set_default_streams(realm, settings.DEFAULT_NEW_REALM_STREAMS)
|
||||||
|
|
||||||
full_name = form.cleaned_data['full_name']
|
full_name = form.cleaned_data['full_name']
|
||||||
@@ -225,11 +230,21 @@ def accounts_register(request):
|
|||||||
|
|
||||||
# This logs you in using the ZulipDummyBackend, since honestly nothing
|
# This logs you in using the ZulipDummyBackend, since honestly nothing
|
||||||
# more fancy than this is required.
|
# more fancy than this is required.
|
||||||
login(request, authenticate(username=user_profile.email, use_dummy_backend=True))
|
login(request, authenticate(username=user_profile.email,
|
||||||
|
realm_subdomain=realm.subdomain,
|
||||||
|
use_dummy_backend=True))
|
||||||
|
|
||||||
if first_in_realm:
|
if first_in_realm:
|
||||||
do_change_is_admin(user_profile, True)
|
do_change_is_admin(user_profile, True)
|
||||||
return HttpResponseRedirect(reverse('zerver.views.initial_invite_page'))
|
invite_url = reverse('zerver.views.initial_invite_page')
|
||||||
|
if (realm_creation and settings.REALMS_HAVE_SUBDOMAINS):
|
||||||
|
invite_url = "%s%s.%s%s" % (
|
||||||
|
settings.EXTERNAL_URI_SCHEME,
|
||||||
|
form.cleaned_data['realm_subdomain'],
|
||||||
|
settings.EXTERNAL_HOST,
|
||||||
|
reverse('zerver.views.initial_invite_page')
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(invite_url)
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect(reverse('zerver.views.home'))
|
return HttpResponseRedirect(reverse('zerver.views.home'))
|
||||||
|
|
||||||
@@ -244,6 +259,7 @@ def accounts_register(request):
|
|||||||
# but for the registration form, there is no logged in user yet, so
|
# but for the registration form, there is no logged in user yet, so
|
||||||
# we have to set it here.
|
# we have to set it here.
|
||||||
'creating_new_team': realm_creation,
|
'creating_new_team': realm_creation,
|
||||||
|
'realms_have_subdomains': settings.REALMS_HAVE_SUBDOMAINS,
|
||||||
'password_auth_enabled': password_auth_enabled(realm),
|
'password_auth_enabled': password_auth_enabled(realm),
|
||||||
},
|
},
|
||||||
request=request)
|
request=request)
|
||||||
@@ -316,10 +332,11 @@ def get_invitee_emails_set(invitee_emails_raw):
|
|||||||
def create_homepage_form(request, user_info=None):
|
def create_homepage_form(request, user_info=None):
|
||||||
# type: (HttpRequest, Optional[Dict[str, Any]]) -> HomepageForm
|
# type: (HttpRequest, Optional[Dict[str, Any]]) -> HomepageForm
|
||||||
if user_info:
|
if user_info:
|
||||||
return HomepageForm(user_info, domain=request.session.get("domain"))
|
return HomepageForm(user_info, domain=request.session.get("domain"),
|
||||||
|
subdomain=get_subdomain(request))
|
||||||
# An empty fields dict is not treated the same way as not
|
# An empty fields dict is not treated the same way as not
|
||||||
# providing it.
|
# providing it.
|
||||||
return HomepageForm(domain=request.session.get("domain"))
|
return HomepageForm(domain=request.session.get("domain"), subdomain=get_subdomain(request))
|
||||||
|
|
||||||
def maybe_send_to_registration(request, email, full_name=''):
|
def maybe_send_to_registration(request, email, full_name=''):
|
||||||
# type: (HttpRequest, text_type, text_type) -> HttpResponse
|
# type: (HttpRequest, text_type, text_type) -> HttpResponse
|
||||||
@@ -362,6 +379,11 @@ def login_or_register_remote_user(request, remote_username, user_profile, full_n
|
|||||||
return maybe_send_to_registration(request, remote_user_to_email(remote_username), full_name)
|
return maybe_send_to_registration(request, remote_user_to_email(remote_username), full_name)
|
||||||
else:
|
else:
|
||||||
login(request, user_profile)
|
login(request, user_profile)
|
||||||
|
if settings.OPEN_REALM_CREATION and user_profile.realm.subdomain is not None:
|
||||||
|
return HttpResponseRedirect("%s%s.%s" % (settings.EXTERNAL_URI_SCHEME,
|
||||||
|
user_profile.realm.subdomain,
|
||||||
|
settings.EXTERNAL_HOST))
|
||||||
|
|
||||||
return HttpResponseRedirect("%s%s" % (settings.EXTERNAL_URI_SCHEME,
|
return HttpResponseRedirect("%s%s" % (settings.EXTERNAL_URI_SCHEME,
|
||||||
request.get_host()))
|
request.get_host()))
|
||||||
|
|
||||||
@@ -372,7 +394,7 @@ def remote_user_sso(request):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
raise JsonableError(_("No REMOTE_USER set."))
|
raise JsonableError(_("No REMOTE_USER set."))
|
||||||
|
|
||||||
user_profile = authenticate(remote_user=remote_user)
|
user_profile = authenticate(remote_user=remote_user, realm_subdomain=get_subdomain(request))
|
||||||
return login_or_register_remote_user(request, remote_user, user_profile)
|
return login_or_register_remote_user(request, remote_user, user_profile)
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@@ -401,7 +423,9 @@ def remote_user_jwt(request):
|
|||||||
# We do all the authentication we need here (otherwise we'd have to
|
# We do all the authentication we need here (otherwise we'd have to
|
||||||
# duplicate work), but we need to call authenticate with some backend so
|
# duplicate work), but we need to call authenticate with some backend so
|
||||||
# that the request.backend attribute gets set.
|
# that the request.backend attribute gets set.
|
||||||
user_profile = authenticate(username=email, use_dummy_backend=True)
|
user_profile = authenticate(username=email,
|
||||||
|
realm_subdomain=get_subdomain(request),
|
||||||
|
use_dummy_backend=True)
|
||||||
except (jwt.DecodeError, jwt.ExpiredSignature):
|
except (jwt.DecodeError, jwt.ExpiredSignature):
|
||||||
raise JsonableError(_("Bad JSON web token signature"))
|
raise JsonableError(_("Bad JSON web token signature"))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -503,7 +527,9 @@ def finish_google_oauth2(request):
|
|||||||
logging.error('Google oauth2 account email not found: %s' % (body,))
|
logging.error('Google oauth2 account email not found: %s' % (body,))
|
||||||
return HttpResponse(status=400)
|
return HttpResponse(status=400)
|
||||||
email_address = email['value']
|
email_address = email['value']
|
||||||
user_profile = authenticate(username=email_address, use_dummy_backend=True)
|
user_profile = authenticate(username=email_address,
|
||||||
|
realm_subdomain=get_subdomain(request),
|
||||||
|
use_dummy_backend=True)
|
||||||
return login_or_register_remote_user(request, email_address, user_profile, full_name)
|
return login_or_register_remote_user(request, email_address, user_profile, full_name)
|
||||||
|
|
||||||
def login_page(request, **kwargs):
|
def login_page(request, **kwargs):
|
||||||
@@ -536,10 +562,16 @@ def dev_direct_login(request, **kwargs):
|
|||||||
# This check is probably not required, since authenticate would fail without an enabled DevAuthBackend.
|
# This check is probably not required, since authenticate would fail without an enabled DevAuthBackend.
|
||||||
raise Exception('Direct login not supported.')
|
raise Exception('Direct login not supported.')
|
||||||
email = request.POST['direct_email']
|
email = request.POST['direct_email']
|
||||||
user_profile = authenticate(username=email)
|
user_profile = authenticate(username=email, realm_subdomain=get_subdomain(request))
|
||||||
if user_profile is None:
|
if user_profile is None:
|
||||||
raise Exception("User cannot login")
|
raise Exception("User cannot login")
|
||||||
login(request, user_profile)
|
login(request, user_profile)
|
||||||
|
if settings.OPEN_REALM_CREATION and settings.DEVELOPMENT:
|
||||||
|
if user_profile.realm.subdomain is not None:
|
||||||
|
return HttpResponseRedirect("%s%s.%s" % (settings.EXTERNAL_URI_SCHEME,
|
||||||
|
user_profile.realm.subdomain,
|
||||||
|
settings.EXTERNAL_HOST))
|
||||||
|
|
||||||
return HttpResponseRedirect("%s%s" % (settings.EXTERNAL_URI_SCHEME,
|
return HttpResponseRedirect("%s%s" % (settings.EXTERNAL_URI_SCHEME,
|
||||||
request.get_host()))
|
request.get_host()))
|
||||||
|
|
||||||
@@ -555,7 +587,9 @@ def api_dev_fetch_api_key(request, username=REQ()):
|
|||||||
if not dev_auth_enabled() or settings.PRODUCTION:
|
if not dev_auth_enabled() or settings.PRODUCTION:
|
||||||
return json_error(_("Dev environment not enabled."))
|
return json_error(_("Dev environment not enabled."))
|
||||||
return_data = {} # type: Dict[str, bool]
|
return_data = {} # type: Dict[str, bool]
|
||||||
user_profile = authenticate(username=username, return_data=return_data)
|
user_profile = authenticate(username=username,
|
||||||
|
realm_subdomain=get_subdomain(request),
|
||||||
|
return_data=return_data)
|
||||||
if return_data.get("inactive_realm") == True:
|
if return_data.get("inactive_realm") == True:
|
||||||
return json_error(_("Your realm has been deactivated."),
|
return json_error(_("Your realm has been deactivated."),
|
||||||
data={"reason": "realm deactivated"}, status=403)
|
data={"reason": "realm deactivated"}, status=403)
|
||||||
@@ -660,7 +694,8 @@ def send_registration_completion_email(email, request, realm_creation=False):
|
|||||||
context = {'support_email': settings.ZULIP_ADMINISTRATOR,
|
context = {'support_email': settings.ZULIP_ADMINISTRATOR,
|
||||||
'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS}
|
'verbose_support_offers': settings.VERBOSE_SUPPORT_OFFERS}
|
||||||
return Confirmation.objects.send_confirmation(prereg_user, email,
|
return Confirmation.objects.send_confirmation(prereg_user, email,
|
||||||
additional_context=context)
|
additional_context=context,
|
||||||
|
host=request.get_host())
|
||||||
|
|
||||||
def redirect_to_email_login_url(email):
|
def redirect_to_email_login_url(email):
|
||||||
# type: (str) -> HttpResponseRedirect
|
# type: (str) -> HttpResponseRedirect
|
||||||
@@ -1003,9 +1038,14 @@ def api_fetch_api_key(request, username=REQ(), password=REQ()):
|
|||||||
# type: (HttpRequest, str, str) -> HttpResponse
|
# type: (HttpRequest, str, str) -> HttpResponse
|
||||||
return_data = {} # type: Dict[str, bool]
|
return_data = {} # type: Dict[str, bool]
|
||||||
if username == "google-oauth2-token":
|
if username == "google-oauth2-token":
|
||||||
user_profile = authenticate(google_oauth2_token=password, return_data=return_data)
|
user_profile = authenticate(google_oauth2_token=password,
|
||||||
|
realm_subdomain=get_subdomain(request),
|
||||||
|
return_data=return_data)
|
||||||
else:
|
else:
|
||||||
user_profile = authenticate(username=username, password=password, return_data=return_data)
|
user_profile = authenticate(username=username,
|
||||||
|
password=password,
|
||||||
|
realm_subdomain=get_subdomain(request),
|
||||||
|
return_data=return_data)
|
||||||
if return_data.get("inactive_user") == True:
|
if return_data.get("inactive_user") == True:
|
||||||
return json_error(_("Your account has been disabled."),
|
return json_error(_("Your account has been disabled."),
|
||||||
data={"reason": "user disable"}, status=403)
|
data={"reason": "user disable"}, status=403)
|
||||||
@@ -1039,7 +1079,8 @@ def api_get_auth_backends(request):
|
|||||||
def json_fetch_api_key(request, user_profile, password=REQ(default='')):
|
def json_fetch_api_key(request, user_profile, password=REQ(default='')):
|
||||||
# type: (HttpRequest, UserProfile, str) -> HttpResponse
|
# type: (HttpRequest, UserProfile, str) -> HttpResponse
|
||||||
if password_auth_enabled(user_profile.realm):
|
if password_auth_enabled(user_profile.realm):
|
||||||
if not authenticate(username=user_profile.email, password=password):
|
if not authenticate(username=user_profile.email, password=password,
|
||||||
|
realm_subdomain=get_subdomain(request)):
|
||||||
return json_error(_("Your username or password is incorrect."))
|
return json_error(_("Your username or password is incorrect."))
|
||||||
return json_success({"api_key": user_profile.api_key})
|
return json_success({"api_key": user_profile.api_key})
|
||||||
|
|
||||||
|
|||||||
@@ -9,15 +9,31 @@ import ujson
|
|||||||
|
|
||||||
from zerver.lib import bugdown
|
from zerver.lib import bugdown
|
||||||
from zerver.lib.integrations import INTEGRATIONS
|
from zerver.lib.integrations import INTEGRATIONS
|
||||||
|
from zerver.lib.utils import get_subdomain
|
||||||
from zproject.jinja2 import render_to_response
|
from zproject.jinja2 import render_to_response
|
||||||
|
|
||||||
def add_api_uri_context(context, request):
|
def add_api_uri_context(context, request):
|
||||||
# type: (Dict[str, Any], HttpRequest) -> None
|
# type: (Dict[str, Any], HttpRequest) -> None
|
||||||
external_api_path_subdomain = settings.EXTERNAL_API_PATH
|
if settings.REALMS_HAVE_SUBDOMAINS:
|
||||||
external_api_uri_subdomain = settings.EXTERNAL_API_URI
|
subdomain = get_subdomain(request)
|
||||||
|
if subdomain:
|
||||||
|
display_subdomain = subdomain
|
||||||
|
html_settings_links = True
|
||||||
|
else:
|
||||||
|
display_subdomain = 'yourZulipDomain'
|
||||||
|
html_settings_links = False
|
||||||
|
external_api_path_subdomain = '%s.%s' % (display_subdomain,
|
||||||
|
settings.EXTERNAL_API_PATH)
|
||||||
|
else:
|
||||||
|
external_api_path_subdomain = settings.EXTERNAL_API_PATH
|
||||||
|
html_settings_links = True
|
||||||
|
|
||||||
|
external_api_uri_subdomain = '%s%s' % (settings.EXTERNAL_URI_SCHEME,
|
||||||
|
external_api_path_subdomain)
|
||||||
|
|
||||||
context['external_api_path_subdomain'] = external_api_path_subdomain
|
context['external_api_path_subdomain'] = external_api_path_subdomain
|
||||||
context['external_api_uri_subdomain'] = external_api_uri_subdomain
|
context['external_api_uri_subdomain'] = external_api_uri_subdomain
|
||||||
|
context["html_settings_links"] = html_settings_links
|
||||||
|
|
||||||
class ApiURLView(TemplateView):
|
class ApiURLView(TemplateView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@@ -26,7 +42,6 @@ class ApiURLView(TemplateView):
|
|||||||
add_api_uri_context(context, self.request)
|
add_api_uri_context(context, self.request)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class APIView(ApiURLView):
|
class APIView(ApiURLView):
|
||||||
template_name = 'zerver/api.html'
|
template_name = 'zerver/api.html'
|
||||||
|
|
||||||
@@ -40,8 +55,12 @@ class IntegrationView(ApiURLView):
|
|||||||
alphabetical_sorted_integration = OrderedDict(sorted(INTEGRATIONS.items()))
|
alphabetical_sorted_integration = OrderedDict(sorted(INTEGRATIONS.items()))
|
||||||
context['integrations_dict'] = alphabetical_sorted_integration
|
context['integrations_dict'] = alphabetical_sorted_integration
|
||||||
|
|
||||||
settings_html = '<a href="../#settings">Zulip settings page</a>'
|
if context["html_settings_links"]:
|
||||||
subscriptions_html = '<a target="_blank" href="../#subscriptions">subscriptions page</a>'
|
settings_html = '<a href="../#settings">Zulip settings page</a>'
|
||||||
|
subscriptions_html = '<a target="_blank" href="../#subscriptions">subscriptions page</a>'
|
||||||
|
else:
|
||||||
|
settings_html = 'Zulip settings page'
|
||||||
|
subscriptions_html = 'subscriptions page'
|
||||||
|
|
||||||
context['settings_html'] = settings_html
|
context['settings_html'] = settings_html
|
||||||
context['subscriptions_html'] = subscriptions_html
|
context['subscriptions_html'] = subscriptions_html
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class Command(BaseCommand):
|
|||||||
clear_database()
|
clear_database()
|
||||||
|
|
||||||
# Create our two default realms
|
# Create our two default realms
|
||||||
zulip_realm = Realm.objects.create(domain="zulip.com", name="Zulip Dev")
|
zulip_realm = Realm.objects.create(domain="zulip.com", name="Zulip Dev", subdomain="zulip")
|
||||||
if options["test_suite"]:
|
if options["test_suite"]:
|
||||||
Realm.objects.create(domain="mit.edu")
|
Realm.objects.create(domain="mit.edu")
|
||||||
realms = {} # type: Dict[text_type, Realm]
|
realms = {} # type: Dict[text_type, Realm]
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from social.backends.github import GithubOAuth2, GithubOrganizationOAuth2, \
|
|||||||
GithubTeamOAuth2
|
GithubTeamOAuth2
|
||||||
from social.exceptions import AuthFailed
|
from social.exceptions import AuthFailed
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
|
from zerver.lib.utils import check_subdomain
|
||||||
|
|
||||||
def password_auth_enabled(realm):
|
def password_auth_enabled(realm):
|
||||||
# type: (Realm) -> bool
|
# type: (Realm) -> bool
|
||||||
@@ -115,6 +116,11 @@ class SocialAuthMixin(ZulipAuthMixin):
|
|||||||
return_data["inactive_realm"] = True
|
return_data["inactive_realm"] = True
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if not check_subdomain(kwargs.get("realm_subdomain"),
|
||||||
|
user_profile.realm.subdomain):
|
||||||
|
return_data["invalid_subdomain"] = True
|
||||||
|
return None
|
||||||
|
|
||||||
return user_profile
|
return user_profile
|
||||||
|
|
||||||
def process_do_auth(self, user_profile, *args, **kwargs):
|
def process_do_auth(self, user_profile, *args, **kwargs):
|
||||||
@@ -142,10 +148,14 @@ class ZulipDummyBackend(ZulipAuthMixin):
|
|||||||
"""
|
"""
|
||||||
Used when we want to log you in but we don't know which backend to use.
|
Used when we want to log you in but we don't know which backend to use.
|
||||||
"""
|
"""
|
||||||
def authenticate(self, username=None, use_dummy_backend=False):
|
def authenticate(self, username=None, realm_subdomain=None, use_dummy_backend=False):
|
||||||
# type: (Optional[str], bool) -> Optional[UserProfile]
|
# type: (Optional[text_type], Optional[text_type], bool) -> Optional[UserProfile]
|
||||||
if use_dummy_backend:
|
if use_dummy_backend:
|
||||||
return common_get_active_user_by_email(username)
|
user_profile = common_get_active_user_by_email(username)
|
||||||
|
if user_profile is None:
|
||||||
|
return None
|
||||||
|
if check_subdomain(realm_subdomain, user_profile.realm.subdomain):
|
||||||
|
return user_profile
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class EmailAuthBackend(ZulipAuthMixin):
|
class EmailAuthBackend(ZulipAuthMixin):
|
||||||
@@ -155,9 +165,8 @@ class EmailAuthBackend(ZulipAuthMixin):
|
|||||||
Allows a user to sign in using an email/password pair rather than
|
Allows a user to sign in using an email/password pair rather than
|
||||||
a username/password pair.
|
a username/password pair.
|
||||||
"""
|
"""
|
||||||
|
def authenticate(self, username=None, password=None, realm_subdomain=None, return_data=None):
|
||||||
def authenticate(self, username=None, password=None, return_data=None):
|
# type: (Optional[text_type], Optional[str], Optional[text_type], Optional[Dict[str, Any]]) -> Optional[UserProfile]
|
||||||
# type: (Optional[text_type], Optional[str], Optional[Dict[str, Any]]) -> Optional[UserProfile]
|
|
||||||
""" Authenticate a user based on email address as the user name. """
|
""" Authenticate a user based on email address as the user name. """
|
||||||
if username is None or password is None:
|
if username is None or password is None:
|
||||||
# Return immediately. Otherwise we will look for a SQL row with
|
# Return immediately. Otherwise we will look for a SQL row with
|
||||||
@@ -173,7 +182,11 @@ class EmailAuthBackend(ZulipAuthMixin):
|
|||||||
return_data['password_auth_disabled'] = True
|
return_data['password_auth_disabled'] = True
|
||||||
return None
|
return None
|
||||||
if user_profile.check_password(password):
|
if user_profile.check_password(password):
|
||||||
|
if not check_subdomain(realm_subdomain, user_profile.realm.subdomain):
|
||||||
|
return_data["invalid_subdomain"] = True
|
||||||
|
return None
|
||||||
return user_profile
|
return user_profile
|
||||||
|
return None
|
||||||
|
|
||||||
class GoogleMobileOauth2Backend(ZulipAuthMixin):
|
class GoogleMobileOauth2Backend(ZulipAuthMixin):
|
||||||
"""
|
"""
|
||||||
@@ -186,8 +199,8 @@ class GoogleMobileOauth2Backend(ZulipAuthMixin):
|
|||||||
https://developers.google.com/accounts/docs/CrossClientAuth#offlineAccess
|
https://developers.google.com/accounts/docs/CrossClientAuth#offlineAccess
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def authenticate(self, google_oauth2_token=None, return_data=dict()):
|
def authenticate(self, google_oauth2_token=None, realm_subdomain=None, return_data={}):
|
||||||
# type: (Optional[str], Dict[str, Any]) -> Optional[UserProfile]
|
# type: (Optional[str], Optional[text_type], Dict[str, Any]) -> Optional[UserProfile]
|
||||||
try:
|
try:
|
||||||
token_payload = googleapiclient.verify_id_token(google_oauth2_token, settings.GOOGLE_CLIENT_ID)
|
token_payload = googleapiclient.verify_id_token(google_oauth2_token, settings.GOOGLE_CLIENT_ID)
|
||||||
except AppIdentityError:
|
except AppIdentityError:
|
||||||
@@ -204,20 +217,25 @@ class GoogleMobileOauth2Backend(ZulipAuthMixin):
|
|||||||
if user_profile.realm.deactivated:
|
if user_profile.realm.deactivated:
|
||||||
return_data["inactive_realm"] = True
|
return_data["inactive_realm"] = True
|
||||||
return None
|
return None
|
||||||
|
if not check_subdomain(realm_subdomain, user_profile.realm.subdomain):
|
||||||
|
return_data["invalid_subdomain"] = True
|
||||||
|
return None
|
||||||
return user_profile
|
return user_profile
|
||||||
else:
|
else:
|
||||||
return_data["valid_attestation"] = False
|
return_data["valid_attestation"] = False
|
||||||
|
|
||||||
class ZulipRemoteUserBackend(RemoteUserBackend):
|
class ZulipRemoteUserBackend(RemoteUserBackend):
|
||||||
create_unknown_user = False
|
create_unknown_user = False
|
||||||
|
def authenticate(self, remote_user, realm_subdomain=None):
|
||||||
def authenticate(self, remote_user):
|
# type: (str, Optional[text_type]) -> Optional[UserProfile]
|
||||||
# type: (str) -> Optional[UserProfile]
|
|
||||||
if not remote_user:
|
if not remote_user:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
email = remote_user_to_email(remote_user)
|
email = remote_user_to_email(remote_user)
|
||||||
return common_get_active_user_by_email(email)
|
user = common_get_active_user_by_email(email)
|
||||||
|
if user is not None and check_subdomain(realm_subdomain, user.realm.subdomain):
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
class ZulipLDAPException(Exception):
|
class ZulipLDAPException(Exception):
|
||||||
pass
|
pass
|
||||||
@@ -257,11 +275,16 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend):
|
|||||||
return username
|
return username
|
||||||
|
|
||||||
class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
|
class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
|
||||||
def authenticate(self, username, password, return_data=None):
|
def authenticate(self, username, password, realm_subdomain=None, return_data=None):
|
||||||
# type: (text_type, str, Optional[Dict[str, Any]]) -> Optional[str]
|
# type: (text_type, str, Optional[text_type], Optional[Dict[str, Any]]) -> Optional[str]
|
||||||
try:
|
try:
|
||||||
username = self.django_to_ldap_username(username)
|
username = self.django_to_ldap_username(username)
|
||||||
return ZulipLDAPAuthBackendBase.authenticate(self, username, password)
|
user_profile = ZulipLDAPAuthBackendBase.authenticate(self, username, password)
|
||||||
|
if user_profile is None:
|
||||||
|
return None
|
||||||
|
if not check_subdomain(realm_subdomain, user_profile.realm.subdomain):
|
||||||
|
return None
|
||||||
|
return user_profile
|
||||||
except Realm.DoesNotExist:
|
except Realm.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
except ZulipLDAPException:
|
except ZulipLDAPException:
|
||||||
@@ -292,16 +315,15 @@ class ZulipLDAPAuthBackend(ZulipLDAPAuthBackendBase):
|
|||||||
|
|
||||||
# Just like ZulipLDAPAuthBackend, but doesn't let you log in.
|
# Just like ZulipLDAPAuthBackend, but doesn't let you log in.
|
||||||
class ZulipLDAPUserPopulator(ZulipLDAPAuthBackendBase):
|
class ZulipLDAPUserPopulator(ZulipLDAPAuthBackendBase):
|
||||||
def authenticate(self, username, password):
|
def authenticate(self, username, password, realm_subdomain=None):
|
||||||
# type: (text_type, str) -> None
|
# type: (text_type, str, Optional[text_type]) -> None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class DevAuthBackend(ZulipAuthMixin):
|
class DevAuthBackend(ZulipAuthMixin):
|
||||||
# Allow logging in as any user without a password.
|
# Allow logging in as any user without a password.
|
||||||
# This is used for convenience when developing Zulip.
|
# This is used for convenience when developing Zulip.
|
||||||
|
def authenticate(self, username, realm_subdomain=None, return_data=None):
|
||||||
def authenticate(self, username, return_data=None):
|
# type: (text_type, Optional[text_type], Optional[Dict[str, Any]]) -> UserProfile
|
||||||
# type: (text_type, Optional[Dict[str, Any]]) -> UserProfile
|
|
||||||
return common_get_active_user_by_email(username, return_data=return_data)
|
return common_get_active_user_by_email(username, return_data=return_data)
|
||||||
|
|
||||||
class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2):
|
class GitHubAuthBackend(SocialAuthMixin, GithubOAuth2):
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
from .prod_settings_template import *
|
from .prod_settings_template import *
|
||||||
|
|
||||||
LOCAL_UPLOADS_DIR = 'var/uploads'
|
LOCAL_UPLOADS_DIR = 'var/uploads'
|
||||||
EXTERNAL_HOST = 'localhost:9991'
|
EXTERNAL_HOST = 'zulipdev.com:9991'
|
||||||
ALLOWED_HOSTS = ['localhost']
|
ALLOWED_HOSTS = ['*']
|
||||||
AUTHENTICATION_BACKENDS = ('zproject.backends.DevAuthBackend',)
|
AUTHENTICATION_BACKENDS = ('zproject.backends.DevAuthBackend',)
|
||||||
# Add some of the below if you're testing other backends
|
# Add some of the below if you're testing other backends
|
||||||
# AUTHENTICATION_BACKENDS = ('zproject.backends.EmailAuthBackend',
|
# AUTHENTICATION_BACKENDS = ('zproject.backends.EmailAuthBackend',
|
||||||
@@ -20,6 +20,9 @@ EXTRA_INSTALLED_APPS = ["zilencer", "analytics"]
|
|||||||
# Disable Camo in development
|
# Disable Camo in development
|
||||||
CAMO_URI = ''
|
CAMO_URI = ''
|
||||||
OPEN_REALM_CREATION = True
|
OPEN_REALM_CREATION = True
|
||||||
|
# Default to subdomains disabled in development until we can update
|
||||||
|
# the development documentation to make sense with subdomains.
|
||||||
|
REALMS_HAVE_SUBDOMAINS = False
|
||||||
TERMS_OF_SERVICE = 'zproject/terms.md.template'
|
TERMS_OF_SERVICE = 'zproject/terms.md.template'
|
||||||
|
|
||||||
SAVE_FRONTEND_STACKTRACES = True
|
SAVE_FRONTEND_STACKTRACES = True
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
|
|||||||
'VERBOSE_SUPPORT_OFFERS': False,
|
'VERBOSE_SUPPORT_OFFERS': False,
|
||||||
'STATSD_HOST': '',
|
'STATSD_HOST': '',
|
||||||
'OPEN_REALM_CREATION': False,
|
'OPEN_REALM_CREATION': False,
|
||||||
|
'REALMS_HAVE_SUBDOMAINS': False,
|
||||||
'REMOTE_POSTGRES_HOST': '',
|
'REMOTE_POSTGRES_HOST': '',
|
||||||
'REMOTE_POSTGRES_SSLMODE': '',
|
'REMOTE_POSTGRES_SSLMODE': '',
|
||||||
# Default GOOGLE_CLIENT_ID to the value needed for Android auth to work
|
# Default GOOGLE_CLIENT_ID to the value needed for Android auth to work
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ LOCAL_UPLOADS_DIR = 'var/test_uploads'
|
|||||||
S3_KEY = 'test-key'
|
S3_KEY = 'test-key'
|
||||||
S3_SECRET_KEY = 'test-secret-key'
|
S3_SECRET_KEY = 'test-secret-key'
|
||||||
S3_AUTH_UPLOADS_BUCKET = 'test-authed-bucket'
|
S3_AUTH_UPLOADS_BUCKET = 'test-authed-bucket'
|
||||||
|
EXTERNAL_HOST = os.getenv('EXTERNAL_HOST', "testserver")
|
||||||
|
REALMS_HAVE_SUBDOMAINS = bool(os.getenv('REALMS_HAVE_SUBDOMAINS', False))
|
||||||
|
|
||||||
# Test Custom TOS template rendering
|
# Test Custom TOS template rendering
|
||||||
TERMS_OF_SERVICE = 'corporate/terms.md'
|
TERMS_OF_SERVICE = 'corporate/terms.md'
|
||||||
|
|||||||
Reference in New Issue
Block a user