Unique link generator for realm creation.

This commit is contained in:
Vishnu Ks 2016-06-23 00:46:02 +05:30 committed by Tim Abbott
parent 8c62cff1b7
commit 1cbd39b768
7 changed files with 128 additions and 8 deletions

View 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')),
],
),
]

View File

@ -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)

View 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))

View File

@ -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)

View File

@ -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']

View File

@ -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):

View File

@ -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'),