{% endif %}
diff --git a/templates/zerver/realm_creation_disabled.html b/templates/zerver/realm_creation_disabled.html
new file mode 100644
index 0000000000..8e88d65984
--- /dev/null
+++ b/templates/zerver/realm_creation_disabled.html
@@ -0,0 +1,13 @@
+{% extends "zerver/portico.html" %}
+
+{% block for_you %}{{ _('got a bit lost there.') }}{% endblock %}
+
+{% block portico_content %}
+
+
+
{{ _('New organization creation disabled') }}
+
+
{{ _('This server does not allow members of the public to create new organizations.') }}
+
{% trans %}Zulip is open source, so you can install your own Zulip server by following the instructions on www.zulip.org{% endtrans %}
+
+{% endblock %}
diff --git a/templates/zerver/register.html b/templates/zerver/register.html
index 694e6ba7df..f67a847a89 100644
--- a/templates/zerver/register.html
+++ b/templates/zerver/register.html
@@ -46,6 +46,24 @@ Form is validated both client-side using jquery-validate (see signup.js) and ser
{% endif %}
+
+ {% if creating_new_team %}
+
+
+
+
+ {% if form.realm_name.errors %}
+ {% for error in form.realm_name.errors %}
+
{{ error }}
+ {% endfor %}
+ {% endif %}
+ {{ _('You can change this later on the admin page.') }}
+
+
+ {% endif %}
+
{% if password_auth_enabled %}
diff --git a/zerver/context_processors.py b/zerver/context_processors.py
index 1b9285ad38..1dd099ce5d 100644
--- a/zerver/context_processors.py
+++ b/zerver/context_processors.py
@@ -21,6 +21,7 @@ def add_settings(request):
'api_site_required': settings.EXTERNAL_API_PATH != "api.zulip.com",
'email_integration_enabled': settings.EMAIL_GATEWAY_BOT != "",
'email_gateway_example': settings.EMAIL_GATEWAY_EXAMPLE,
+ 'open_realm_creation': settings.OPEN_REALM_CREATION,
'password_auth_enabled': password_auth_enabled(realm),
'dev_auth_enabled': dev_auth_enabled(),
'google_auth_enabled': google_auth_enabled(),
diff --git a/zerver/forms.py b/zerver/forms.py
index 119885093f..14f99aef66 100644
--- a/zerver/forms.py
+++ b/zerver/forms.py
@@ -8,14 +8,18 @@ from django.contrib.auth.forms import SetPasswordForm, AuthenticationForm, \
from django.conf import settings
from django.db.models.query import QuerySet
from jinja2 import Markup as mark_safe
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _
import logging
from zerver.models import Realm, get_user_profile_by_email, UserProfile, \
- completely_open, resolve_email_to_domain, get_realm, get_unique_open_realm
-from zerver.lib.actions import do_change_password, is_inactive
+ completely_open, resolve_email_to_domain, get_realm, \
+ get_unique_open_realm, split_email_to_domain
+from zerver.lib.actions import do_change_password, is_inactive, user_email_is_unique
from zproject.backends import password_auth_enabled
import DNS
+from six import text_type
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,)
@@ -25,6 +29,13 @@ if settings.ZULIP_COM:
u"Zulip is open source, so you can install your own Zulip server " + \
u"by following the instructions on www.zulip.org!"
+
+def get_registration_string(domain):
+ # type: (text_type) -> text_type
+ register_url = reverse('register') + domain
+ register_account_string = _('The organization with the domain already exists. Please register your account here.') % {'url': register_url}
+ return register_account_string
+
def has_valid_realm(value):
# type: (str) -> bool
# Checks if there is a realm without invite_required
@@ -54,10 +65,11 @@ class RegistrationForm(forms.Form):
# actually required for a realm
password = forms.CharField(widget=forms.PasswordInput, max_length=100,
required=False)
+ realm_name = forms.CharField(max_length=100, required=False)
+
if not settings.VOYAGER:
terms = forms.BooleanField(required=True)
-
class ToSForm(forms.Form):
full_name = forms.CharField(max_length=100)
terms = forms.BooleanField(required=True)
@@ -84,6 +96,27 @@ class HomepageForm(forms.Form):
return data
raise ValidationError(mark_safe(SIGNUP_STRING))
+class RealmCreationForm(forms.Form):
+ # This form determines whether users can
+ # create a new realm. Be careful when modifying the
+ # validators.
+ email = forms.EmailField(validators=[user_email_is_unique,])
+
+ def __init__(self, *args, **kwargs):
+ # type: (*Any, **Any) -> None
+ self.domain = kwargs.get("domain")
+ if "domain" in kwargs:
+ del kwargs["domain"]
+ super(RealmCreationForm, self).__init__(*args, **kwargs)
+
+ def clean_email(self):
+ # type: () -> text_type
+ data = self.cleaned_data['email']
+ domain = split_email_to_domain(data)
+ if (get_realm(domain) is not None):
+ raise ValidationError(mark_safe(get_registration_string(domain)))
+ return data
+
class LoggingSetPasswordForm(SetPasswordForm):
def save(self, commit=True):
# type: (bool) -> UserProfile
diff --git a/zerver/migrations/0019_preregistrationuser_realm_creation.py b/zerver/migrations/0019_preregistrationuser_realm_creation.py
new file mode 100644
index 0000000000..3a7f879d7f
--- /dev/null
+++ b/zerver/migrations/0019_preregistrationuser_realm_creation.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('zerver', '0018_realm_emoji_message'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='preregistrationuser',
+ name='realm_creation',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/zerver/models.py b/zerver/models.py
index 1dddd4a30e..2c0f2e03f8 100644
--- a/zerver/models.py
+++ b/zerver/models.py
@@ -502,6 +502,7 @@ class PreregistrationUser(models.Model):
referred_by = models.ForeignKey(UserProfile, null=True) # Optional[UserProfile]
streams = models.ManyToManyField('Stream') # type: Manager
invited_at = models.DateTimeField(auto_now=True) # type: datetime.datetime
+ realm_creation = models.BooleanField(default=False)
# status: whether an object has been confirmed.
# if confirmed, set to confirmation.settings.STATUS_ACTIVE
diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py
index 82e1ed8689..a687f5236c 100644
--- a/zerver/tests/test_signup.py
+++ b/zerver/tests/test_signup.py
@@ -560,3 +560,52 @@ class EmailUnsubscribeTests(AuthedTestCase):
self.assertEqual(0, len(ScheduledJob.objects.filter(
type=ScheduledJob.EMAIL, filter_string__iexact=email)))
+class RealmCreationTest(AuthedTestCase):
+
+ def test_create_realm(self):
+ # type: () -> None
+ username = "user1"
+ password = "test"
+ domain = "test.com"
+ email = "user1@test.com"
+
+ # Make sure the realm does not exist
+ self.assertIsNone(get_realm("test.com"))
+
+ with 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.assertIn("Check your email so we can get started.", result.content)
+
+ # 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)
+ self.assertEquals(result.status_code, 302)
+
+ # Make sure the realm is created
+ realm = get_realm("test.com")
+
+ self.assertIsNotNone(realm)
+ self.assertEqual(realm.domain, domain)
+ self.assertEqual(get_user_profile_by_email(email).realm, realm)
+
+ self.assertTrue(result["Location"].endswith("/invite/"))
+
+ result = self.client.get(result["Location"])
+ self.assertIn("You're the first one here!", result.content)
diff --git a/zerver/views/__init__.py b/zerver/views/__init__.py
index f01f4c065e..bc94fd0463 100644
--- a/zerver/views/__init__.py
+++ b/zerver/views/__init__.py
@@ -26,7 +26,7 @@ from zerver.models import Message, UserProfile, Stream, Subscription, Huddle, \
completely_open, get_unique_open_realm, remote_user_to_email, email_allowed_for_realm, \
get_cross_realm_users
from zerver.lib.actions import do_change_password, do_change_full_name, do_change_is_admin, \
- do_activate_user, do_create_user, \
+ do_activate_user, do_create_user, do_create_realm, set_default_streams, \
internal_send_message, update_user_presence, do_events_register, \
get_status_dict, do_change_enable_offline_email_notifications, \
do_change_enable_digest_emails, do_set_realm_name, do_set_realm_restricted_to_domain, \
@@ -35,7 +35,7 @@ from zerver.lib.actions import do_change_password, do_change_full_name, do_chang
user_email_is_unique, do_invite_users, do_refer_friend, compute_mit_user_fullname, \
do_set_muted_topics, clear_followup_emails_queue, do_update_pointer, realm_user_count
from zerver.lib.push_notifications import num_push_devices_for_user
-from zerver.forms import RegistrationForm, HomepageForm, ToSForm, \
+from zerver.forms import RegistrationForm, HomepageForm, RealmCreationForm, ToSForm, \
CreateUserForm, is_inactive, OurAuthenticationForm
from django.views.decorators.csrf import csrf_exempt
from django_auth_ldap.backend import LDAPBackend, _LDAPUser
@@ -86,6 +86,7 @@ def accounts_register(request):
confirmation = Confirmation.objects.get(confirmation_key=key)
prereg_user = confirmation.content_object
email = prereg_user.email
+ realm_creation = prereg_user.realm_creation
mit_beta_user = isinstance(confirmation.content_object, MitUser)
try:
existing_user_profile = get_user_profile_by_email(email)
@@ -93,9 +94,10 @@ def accounts_register(request):
existing_user_profile = None
validators.validate_email(email)
-
+ # If OPEN_REALM_CREATION is enabled all user sign ups should go through the
+ # special URL with domain name so that REALM can be identified if multiple realms exist
unique_open_realm = get_unique_open_realm()
- if unique_open_realm:
+ if unique_open_realm is not None:
realm = unique_open_realm
domain = realm.domain
elif not mit_beta_user and prereg_user.referred_by:
@@ -117,6 +119,9 @@ def accounts_register(request):
domain = resolve_email_to_domain(email)
realm = get_realm(domain)
+ if realm_creation and not settings.OPEN_REALM_CREATION:
+ return render_to_response("zerver/realm_creation_disabled.html")
+
if realm and realm.deactivated:
# The user is trying to register for a deactivated realm. Advise them to
# contact support.
@@ -196,6 +201,11 @@ def accounts_register(request):
# SSO users don't need no passwords
password = None
+ if realm_creation:
+ domain = split_email_to_domain(email)
+ (realm, _) = do_create_realm(domain, form.cleaned_data['realm_name'])
+ set_default_streams(realm, settings.DEFAULT_NEW_REALM_STREAMS)
+
full_name = form.cleaned_data['full_name']
short_name = email_to_username(email)
first_in_realm = len(UserProfile.objects.filter(realm=realm, is_bot=False)) == 0
@@ -236,6 +246,7 @@ def accounts_register(request):
# password_auth_enabled is normally set via our context processor,
# but for the registration form, there is no logged in user yet, so
# we have to set it here.
+ 'creating_new_team': realm_creation,
'password_auth_enabled': password_auth_enabled(realm),
},
request=request)
@@ -667,8 +678,8 @@ def logout_then_login(request, **kwargs):
# type: (HttpRequest, **Any) -> HttpResponse
return django_logout_then_login(request, kwargs)
-def create_preregistration_user(email, request):
- # type: (text_type, HttpRequest) -> HttpResponse
+def create_preregistration_user(email, request, realm_creation=False):
+ # type: (text_type, HttpRequest, bool) -> HttpResponse
domain = request.session.get("domain")
if completely_open(domain):
# Clear the "domain" from the session object; it's no longer needed
@@ -677,14 +688,15 @@ def create_preregistration_user(email, request):
# The user is trying to sign up for a completely open realm,
# so create them a PreregistrationUser for that realm
return PreregistrationUser.objects.create(email=email,
- realm=get_realm(domain))
+ realm=get_realm(domain),
+ realm_creation=realm_creation)
# MIT users who are not explicitly signing up for an open realm
# require special handling (They may already have an (inactive)
# account, for example)
if split_email_to_domain(email) == "mit.edu":
- return MitUser.objects.get_or_create(email=email)[0]
- return PreregistrationUser.objects.create(email=email)
+ return MitUser.objects.get_or_create(email=email, realm_creation=realm_creation)[0]
+ return PreregistrationUser.objects.create(email=email, realm_creation=realm_creation)
def accounts_home_with_domain(request, domain):
# type: (HttpRequest, str) -> HttpResponse
@@ -699,18 +711,48 @@ def accounts_home_with_domain(request, domain):
else:
return HttpResponseRedirect(reverse('zerver.views.accounts_home'))
-def send_registration_completion_email(email, request):
- # type: (str, HttpRequest) -> HttpResponse
+def send_registration_completion_email(email, request, realm_creation=False):
+ # type: (str, HttpRequest, bool) -> HttpResponse
"""
Send an email with a confirmation link to the provided e-mail so the user
can complete their registration.
"""
- prereg_user = create_preregistration_user(email, request)
+ prereg_user = create_preregistration_user(email, request, realm_creation)
context = {'support_email': settings.ZULIP_ADMINISTRATOR,
'voyager': settings.VOYAGER}
Confirmation.objects.send_confirmation(prereg_user, email,
additional_context=context)
+"""
+When settings.OPEN_REALM_CREATION is enabled public users can create new realm. For creating the realm the user should
+not be the member of any current realm. The realm is created with domain same as the that of the user's email.
+When there is no unique_open_realm user registrations are made by visiting /register/domain_of_the_realm.
+"""
+def create_realm(request):
+ # type: (HttpRequest) -> HttpResponse
+ if not settings.OPEN_REALM_CREATION:
+ return render_to_response("zerver/realm_creation_disabled.html")
+
+ if request.method == 'POST':
+ form = RealmCreationForm(request.POST, domain=request.session.get("domain"))
+ if form.is_valid():
+ email = form.cleaned_data['email']
+ send_registration_completion_email(email, request, realm_creation=True)
+ return HttpResponseRedirect(reverse('send_confirm', kwargs={'email': email}))
+ try:
+ email = request.POST['email']
+ user_email_is_unique(email)
+ except ValidationError:
+ # if the user user is already registered he can't create a new realm as a realm
+ # with the same domain as user's email already exists
+ return HttpResponseRedirect(reverse('django.contrib.auth.views.login') + '?email=' + urllib.parse.quote_plus(email))
+ else:
+ form = RealmCreationForm(domain=request.session.get("domain"))
+ return render_to_response('zerver/create_realm.html',
+ {'form': form, 'current_url': request.get_full_path},
+ request=request)
+
+
def accounts_home(request):
# type: (HttpRequest) -> HttpResponse
if request.method == 'POST':
diff --git a/zproject/dev_settings.py b/zproject/dev_settings.py
index 3e11673244..f126203710 100644
--- a/zproject/dev_settings.py
+++ b/zproject/dev_settings.py
@@ -20,3 +20,5 @@ EMAIL_GATEWAY_BOT = "emailgateway@zulip.com"
EXTRA_INSTALLED_APPS = ["zilencer", "analytics"]
# Disable Camo in development
CAMO_URI = ''
+OPEN_REALM_CREATION = True
+
diff --git a/zproject/settings.py b/zproject/settings.py
index d7efecbc90..7cac880380 100644
--- a/zproject/settings.py
+++ b/zproject/settings.py
@@ -142,6 +142,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
'ZULIP_COM': False,
'ZULIP_COM_STAGING': False,
'STATSD_HOST': '',
+ 'OPEN_REALM_CREATION': False,
'REMOTE_POSTGRES_HOST': '',
'REMOTE_POSTGRES_SSLMODE': '',
'GOOGLE_CLIENT_ID': '',
diff --git a/zproject/urls.py b/zproject/urls.py
index 901904b272..81d6683975 100644
--- a/zproject/urls.py
+++ b/zproject/urls.py
@@ -68,6 +68,9 @@ i18n_urls = [
# Portico-styled page used to provide email confirmation of terms acceptance.
url(r'^accounts/accept_terms/$', 'zerver.views.accounts_accept_terms'),
+ # Realm Creation
+ url(r'^create_realm/$', 'zerver.views.create_realm'),
+
# Login/registration
url(r'^register/$', 'zerver.views.accounts_home', name='register'),
url(r'^login/$', 'zerver.views.login_page', {'template_name': 'zerver/login.html'}),