mirror of
https://github.com/zulip/zulip.git
synced 2025-10-25 09:03:57 +00:00
Unique link generator for realm creation.
This commit is contained in:
23
confirmation/migrations/0002_realmcreationkey.py
Normal file
23
confirmation/migrations/0002_realmcreationkey.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('confirmation', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RealmCreationKey',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||||
|
('creation_key', models.CharField(max_length=40, verbose_name='activation key')),
|
||||||
|
('date_created', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -30,6 +30,15 @@ except ImportError:
|
|||||||
|
|
||||||
B16_RE = re.compile('^[a-f0-9]{40}$')
|
B16_RE = re.compile('^[a-f0-9]{40}$')
|
||||||
|
|
||||||
|
def check_key_is_valid(creation_key):
|
||||||
|
if not RealmCreationKey.objects.filter(creation_key=creation_key).exists():
|
||||||
|
return False
|
||||||
|
days_sofar = (now() - RealmCreationKey.objects.get(creation_key=creation_key).date_created).days
|
||||||
|
# Realm creation link expires after settings.REALM_CREATION_LINK_VALIDITY_DAYS
|
||||||
|
if days_sofar <= settings.REALM_CREATION_LINK_VALIDITY_DAYS:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def generate_key():
|
def generate_key():
|
||||||
return generate_random_token(40)
|
return generate_random_token(40)
|
||||||
|
|
||||||
@@ -39,6 +48,13 @@ def generate_activation_url(key):
|
|||||||
reverse('confirmation.views.confirm',
|
reverse('confirmation.views.confirm',
|
||||||
kwargs={'confirmation_key': key}))
|
kwargs={'confirmation_key': key}))
|
||||||
|
|
||||||
|
def generate_realm_creation_url():
|
||||||
|
key = generate_key()
|
||||||
|
RealmCreationKey.objects.create(creation_key=key, date_created=now())
|
||||||
|
return u'%s%s%s' % (settings.EXTERNAL_URI_SCHEME,
|
||||||
|
settings.EXTERNAL_HOST,
|
||||||
|
reverse('zerver.views.create_realm',
|
||||||
|
kwargs={'creation_key': key}))
|
||||||
|
|
||||||
class ConfirmationManager(models.Manager):
|
class ConfirmationManager(models.Manager):
|
||||||
|
|
||||||
@@ -111,3 +127,7 @@ class Confirmation(models.Model):
|
|||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return _('confirmation email for %s') % (self.content_object,)
|
return _('confirmation email for %s') % (self.content_object,)
|
||||||
|
|
||||||
|
class RealmCreationKey(models.Model):
|
||||||
|
creation_key = models.CharField(_('activation key'), max_length=40)
|
||||||
|
date_created = models.DateTimeField(_('created'), default=now)
|
||||||
|
|||||||
21
zerver/management/commands/generate_realm_creation_link.py
Normal file
21
zerver/management/commands/generate_realm_creation_link.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from confirmation.models import generate_realm_creation_url
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = """Outputs a randomly generated, 1-time-use link for Organization creation.
|
||||||
|
Whoever visits the link can create a new organization on this server, regardless of whether
|
||||||
|
settings.OPEN_REALM_CREATION is enabled. The link would expire automatically after
|
||||||
|
settings.REALM_CREATION_LINK_VALIDITY_DAYS.
|
||||||
|
|
||||||
|
Usage: python manage.py generate_realm_creation_link """
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
# type: (*Any, **Any) -> None
|
||||||
|
url = generate_realm_creation_url()
|
||||||
|
self.stdout.write("\033[1;92mOne time organization creation link generated\033[0m")
|
||||||
|
self.stdout.write("\033[1;92m=> Please visit \033[4m%s\033[0m \033[1;92mto create the organization\033[0m" % (url))
|
||||||
@@ -4,7 +4,9 @@ from mock import patch
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
|
from zerver.models import get_realm
|
||||||
|
from confirmation.models import RealmCreationKey, generate_realm_creation_url
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
class TestSendWebhookFixtureMessage(TestCase):
|
class TestSendWebhookFixtureMessage(TestCase):
|
||||||
COMMAND_NAME = 'send_webhook_fixture_message'
|
COMMAND_NAME = 'send_webhook_fixture_message'
|
||||||
@@ -57,3 +59,53 @@ class TestSendWebhookFixtureMessage(TestCase):
|
|||||||
self.assertTrue(ujson_mock.loads.called)
|
self.assertTrue(ujson_mock.loads.called)
|
||||||
self.assertTrue(open_mock.called)
|
self.assertTrue(open_mock.called)
|
||||||
client.post.assert_called_once_with(self.url, {}, content_type="application/json")
|
client.post.assert_called_once_with(self.url, {}, content_type="application/json")
|
||||||
|
|
||||||
|
class TestGenerateRealmCreationLink(TestCase):
|
||||||
|
COMMAND_NAME = "generate_realm_creation_link"
|
||||||
|
|
||||||
|
def test_generate_link_and_create_realm(self):
|
||||||
|
username = "user1"
|
||||||
|
domain = "test.com"
|
||||||
|
email = "user1@test.com"
|
||||||
|
generated_link = generate_realm_creation_url()
|
||||||
|
|
||||||
|
with self.settings(OPEN_REALM_CREATION=False):
|
||||||
|
# Check realm creation page is accessible
|
||||||
|
result = self.client.get(generated_link)
|
||||||
|
self.assertEquals(result.status_code, 200)
|
||||||
|
self.assertIn("Let's get started…", result.content)
|
||||||
|
|
||||||
|
# Create Realm with generated link
|
||||||
|
self.assertIsNone(get_realm(domain))
|
||||||
|
result = self.client.post(generated_link, {'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)
|
||||||
|
|
||||||
|
# Generated link used for creating realm
|
||||||
|
result = self.client.get(generated_link)
|
||||||
|
self.assertEquals(result.status_code, 200)
|
||||||
|
self.assertIn("The organization creation link has been expired or is not valid.", result.content)
|
||||||
|
|
||||||
|
def test_realm_creation_with_random_link(self):
|
||||||
|
with self.settings(OPEN_REALM_CREATION=False):
|
||||||
|
# Realm creation attempt with an invalid link should fail
|
||||||
|
random_link = "/create_realm/5e89081eb13984e0f3b130bf7a4121d153f1614b"
|
||||||
|
result = self.client.get(random_link)
|
||||||
|
self.assertEquals(result.status_code, 200)
|
||||||
|
self.assertIn("The organization creation link has been expired or is not valid.", result.content)
|
||||||
|
|
||||||
|
def test_realm_creation_with_expired_link(self):
|
||||||
|
with self.settings(OPEN_REALM_CREATION=False):
|
||||||
|
generated_link = generate_realm_creation_url()
|
||||||
|
key = generated_link[-40:]
|
||||||
|
# Manually expire the link by changing the date of creation
|
||||||
|
obj = RealmCreationKey.objects.get(creation_key=key)
|
||||||
|
obj.date_created = obj.date_created - timedelta(days=settings.REALM_CREATION_LINK_VALIDITY_DAYS + 1)
|
||||||
|
obj.save()
|
||||||
|
|
||||||
|
result = self.client.get(generated_link)
|
||||||
|
self.assertEquals(result.status_code, 200)
|
||||||
|
self.assertIn("The organization creation link has been expired or is not valid.", result.content)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ from zerver.lib.response import json_success, json_error
|
|||||||
from zerver.lib.utils import statsd, generate_random_token
|
from zerver.lib.utils import statsd, generate_random_token
|
||||||
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
|
||||||
|
|
||||||
from confirmation.models import Confirmation
|
from confirmation.models import Confirmation, RealmCreationKey, check_key_is_valid
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
@@ -117,9 +117,6 @@ def accounts_register(request):
|
|||||||
domain = resolve_email_to_domain(email)
|
domain = resolve_email_to_domain(email)
|
||||||
realm = get_realm(domain)
|
realm = get_realm(domain)
|
||||||
|
|
||||||
if realm_creation and not settings.OPEN_REALM_CREATION:
|
|
||||||
return render_to_response("zerver/realm_creation_failed.html", {'message': _('New organization creation disabled.')})
|
|
||||||
|
|
||||||
if realm and realm.deactivated:
|
if realm and realm.deactivated:
|
||||||
# The user is trying to register for a deactivated realm. Advise them to
|
# The user is trying to register for a deactivated realm. Advise them to
|
||||||
# contact support.
|
# contact support.
|
||||||
@@ -727,10 +724,13 @@ When settings.OPEN_REALM_CREATION is enabled public users can create new realm.
|
|||||||
not be the member of any current realm. The realm is created with domain same as the that of the user's email.
|
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.
|
When there is no unique_open_realm user registrations are made by visiting /register/domain_of_the_realm.
|
||||||
"""
|
"""
|
||||||
def create_realm(request):
|
def create_realm(request, creation_key=None):
|
||||||
# type: (HttpRequest) -> HttpResponse
|
# type: (HttpRequest, Optional[text_type]) -> HttpResponse
|
||||||
if not settings.OPEN_REALM_CREATION:
|
if not settings.OPEN_REALM_CREATION:
|
||||||
return render_to_response("zerver/realm_creation_failed.html", {'message': _('New organization creation disabled.')})
|
if creation_key is None:
|
||||||
|
return render_to_response("zerver/realm_creation_failed.html", {'message': _('New organization creation disabled.')})
|
||||||
|
elif not check_key_is_valid(creation_key):
|
||||||
|
return render_to_response("zerver/realm_creation_failed.html", {'message': _('The organization creation link has been expired or is not valid.')})
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = RealmCreationForm(request.POST, domain=request.session.get("domain"))
|
form = RealmCreationForm(request.POST, domain=request.session.get("domain"))
|
||||||
@@ -739,6 +739,8 @@ def create_realm(request):
|
|||||||
confirmation_key = send_registration_completion_email(email, request, realm_creation=True).confirmation_key
|
confirmation_key = send_registration_completion_email(email, request, realm_creation=True).confirmation_key
|
||||||
if settings.DEVELOPMENT:
|
if settings.DEVELOPMENT:
|
||||||
request.session['confirmation_key'] = {'confirmation_key': confirmation_key}
|
request.session['confirmation_key'] = {'confirmation_key': confirmation_key}
|
||||||
|
if (creation_key is not None and check_key_is_valid(creation_key)):
|
||||||
|
RealmCreationKey.objects.get(creation_key=creation_key).delete()
|
||||||
return HttpResponseRedirect(reverse('send_confirm', kwargs={'email': email}))
|
return HttpResponseRedirect(reverse('send_confirm', kwargs={'email': email}))
|
||||||
try:
|
try:
|
||||||
email = request.POST['email']
|
email = request.POST['email']
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
|
|||||||
'DBX_APNS_CERT_FILE': None,
|
'DBX_APNS_CERT_FILE': None,
|
||||||
'EXTRA_INSTALLED_APPS': [],
|
'EXTRA_INSTALLED_APPS': [],
|
||||||
'DEFAULT_NEW_REALM_STREAMS': ["social", "general", "zulip"],
|
'DEFAULT_NEW_REALM_STREAMS': ["social", "general", "zulip"],
|
||||||
|
'REALM_CREATION_LINK_VALIDITY_DAYS': 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
for setting_name, setting_val in six.iteritems(DEFAULT_SETTINGS):
|
for setting_name, setting_val in six.iteritems(DEFAULT_SETTINGS):
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ i18n_urls = [
|
|||||||
|
|
||||||
# Realm Creation
|
# Realm Creation
|
||||||
url(r'^create_realm/$', 'zerver.views.create_realm'),
|
url(r'^create_realm/$', 'zerver.views.create_realm'),
|
||||||
|
url(r'^create_realm/(?P<creation_key>[\w]+)$', 'zerver.views.create_realm'),
|
||||||
|
|
||||||
# Login/registration
|
# Login/registration
|
||||||
url(r'^register/$', 'zerver.views.accounts_home', name='register'),
|
url(r'^register/$', 'zerver.views.accounts_home', name='register'),
|
||||||
|
|||||||
Reference in New Issue
Block a user