mirror of https://github.com/zulip/zulip.git
Unique link generator for realm creation.
This commit is contained in:
parent
8c62cff1b7
commit
1cbd39b768
|
@ -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}$')
|
||||
|
||||
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():
|
||||
return generate_random_token(40)
|
||||
|
||||
|
@ -39,6 +48,13 @@ def generate_activation_url(key):
|
|||
reverse('confirmation.views.confirm',
|
||||
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):
|
||||
|
||||
|
@ -111,3 +127,7 @@ class Confirmation(models.Model):
|
|||
|
||||
def __unicode__(self):
|
||||
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)
|
||||
|
|
|
@ -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.conf import settings
|
||||
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):
|
||||
COMMAND_NAME = 'send_webhook_fixture_message'
|
||||
|
@ -57,3 +59,53 @@ class TestSendWebhookFixtureMessage(TestCase):
|
|||
self.assertTrue(ujson_mock.loads.called)
|
||||
self.assertTrue(open_mock.called)
|
||||
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 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
|
||||
|
||||
|
@ -117,9 +117,6 @@ 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_failed.html", {'message': _('New organization creation disabled.')})
|
||||
|
||||
if realm and realm.deactivated:
|
||||
# The user is trying to register for a deactivated realm. Advise them to
|
||||
# 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.
|
||||
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
|
||||
def create_realm(request, creation_key=None):
|
||||
# type: (HttpRequest, Optional[text_type]) -> HttpResponse
|
||||
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':
|
||||
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
|
||||
if settings.DEVELOPMENT:
|
||||
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}))
|
||||
try:
|
||||
email = request.POST['email']
|
||||
|
|
|
@ -149,6 +149,7 @@ DEFAULT_SETTINGS = {'TWITTER_CONSUMER_KEY': '',
|
|||
'DBX_APNS_CERT_FILE': None,
|
||||
'EXTRA_INSTALLED_APPS': [],
|
||||
'DEFAULT_NEW_REALM_STREAMS': ["social", "general", "zulip"],
|
||||
'REALM_CREATION_LINK_VALIDITY_DAYS': 7,
|
||||
}
|
||||
|
||||
for setting_name, setting_val in six.iteritems(DEFAULT_SETTINGS):
|
||||
|
|
|
@ -81,6 +81,7 @@ i18n_urls = [
|
|||
|
||||
# Realm Creation
|
||||
url(r'^create_realm/$', 'zerver.views.create_realm'),
|
||||
url(r'^create_realm/(?P<creation_key>[\w]+)$', 'zerver.views.create_realm'),
|
||||
|
||||
# Login/registration
|
||||
url(r'^register/$', 'zerver.views.accounts_home', name='register'),
|
||||
|
|
Loading…
Reference in New Issue