auth.py: Add confirmation handlers for signup.

These handlers will kick into action when is_signup is False. In case
the account exists, the user will be logged in, otherwise, user will
be asked if they want to proceed to registration.
This commit is contained in:
Umair Khan
2017-04-20 11:25:15 +05:00
committed by Tim Abbott
parent 11426a2cec
commit d56db0a3b4
4 changed files with 323 additions and 33 deletions

View File

@@ -0,0 +1,51 @@
{% extends "zerver/portico_signup.html" %}
{% block portico_content %}
<div class="app register-page">
<div class="app-main register-page-container">
<div class="center-container">
<div class="center-block">
<div class="register-form">
<p class="lead">
<div class="register-page-header">{{ _("Account not found!") }}</div>
</p>
{% if invalid_email %}
{# If the email address is invalid, we can't send the user #}
{# to the preregistered user code path. #}
<p>
{% trans %}
Please click the following button if you wish to register.
{% endtrans %}
</p>
<a href='/register/' class="button-new sea-green">Register</a>
{% else %}
<p>
{% trans %}
You attempted to login using {{ email }}, but {{ email }} does
not have an account in this organization. If you'd like, you can
try to register a new account with this email address.
{% endtrans %}
</p>
{# TODO: Ideally, this should use whatever auth #}
{# method the user had used to get here, not just #}
{# send an email. #}
<form class="form-inline" id="send_confirm" name="send_confirm"
action="/register/" method="post">
{{ csrf_input }}
<input type="hidden"
id="email"
name="email"
value="{{ email }}" />
<input type="submit"
class="button btn btn-primary btn-large register-button"
value="{{ _("Continue to registration?") }}"/>
</form>
{% endif %}
</div>
</div>
</div>
</div>
<div class="footer-padder"></div>
</div>
{% endblock %}

View File

@@ -3,6 +3,7 @@ from django.conf import settings
from django.http import HttpResponse
from django.test import TestCase, override_settings
from django_auth_ldap.backend import _LDAPUser
from django.contrib.auth import authenticate
from django.test.client import RequestFactory
from typing import Any, Callable, Dict, List, Optional, Text
from builtins import object
@@ -31,6 +32,7 @@ from zerver.lib.sessions import get_session_dict_user
from zerver.lib.test_classes import (
ZulipTestCase,
)
from zerver.lib.test_helpers import POSTRequestMock
from zerver.models import \
get_realm, get_user_profile_by_email, email_to_username, UserProfile, \
PreregistrationUser, Realm
@@ -43,7 +45,8 @@ from zproject.backends import ZulipDummyBackend, EmailAuthBackend, \
dev_auth_enabled, password_auth_enabled, github_auth_enabled, \
SocialAuthMixin, AUTH_BACKEND_NAME_MAP
from zerver.views.auth import maybe_send_to_registration
from zerver.views.auth import (maybe_send_to_registration,
login_or_register_remote_user)
from version import ZULIP_VERSION
from social_core.exceptions import AuthFailed, AuthStateForbidden
@@ -429,7 +432,7 @@ class GitHubAuthBackendTest(ZulipTestCase):
def do_auth(self, *args, **kwargs):
# type: (*Any, **Any) -> UserProfile
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitHubAuthBackend',)):
return self.backend.authenticate(**kwargs)
return authenticate(**kwargs)
def test_github_auth_enabled(self):
# type: () -> None
@@ -598,6 +601,8 @@ class GitHubAuthBackendTest(ZulipTestCase):
request.session = {}
request.user = self.user_profile
self.backend.strategy.request = request
session_data = {'subdomain': False, 'is_signup': '1'}
self.backend.strategy.session_get = lambda k: session_data.get(k)
def do_auth(*args, **kwargs):
# type: (*Any, **Any) -> UserProfile
@@ -615,6 +620,59 @@ class GitHubAuthBackendTest(ZulipTestCase):
'correspond to any existing '
'organization.'.format(email), result)
def test_github_backend_existing_user(self):
# type: () -> None
rf = RequestFactory()
request = rf.get('/complete')
request.session = {}
request.user = self.user_profile
self.backend.strategy.request = request
session_data = {'subdomain': False, 'is_signup': '1'}
self.backend.strategy.session_get = lambda k: session_data.get(k)
def do_auth(*args, **kwargs):
# type: (*Any, **Any) -> UserProfile
return_data = kwargs['return_data']
return_data['valid_attestation'] = True
return None
with mock.patch('social_core.backends.github.GithubOAuth2.do_auth',
side_effect=do_auth):
email = 'hamlet@zulip.com'
response = dict(email=email, name='Hamlet')
result = self.backend.do_auth(response=response)
self.assert_in_response('action="/register/"', result)
self.assert_in_response('hamlet@zulip.com is already active',
result)
def test_github_backend_new_user_when_is_signup_is_false(self):
# type: () -> None
rf = RequestFactory()
request = rf.get('/complete')
request.session = {}
request.user = self.user_profile
self.backend.strategy.request = request
session_data = {'subdomain': False, 'is_signup': '0'}
self.backend.strategy.session_get = lambda k: session_data.get(k)
def do_auth(*args, **kwargs):
# type: (*Any, **Any) -> UserProfile
return_data = kwargs['return_data']
return_data['valid_attestation'] = True
return None
with mock.patch('social_core.backends.github.GithubOAuth2.do_auth',
side_effect=do_auth):
email = 'nonexisting@phantom.com'
response = dict(email=email, name='Ghost')
result = self.backend.do_auth(response=response)
self.assert_in_response(
'action="/register/"', result)
self.assert_in_response('You attempted to login using '
'nonexisting@phantom.com, but '
'nonexisting@phantom.com does',
result)
def test_login_url(self):
# type: () -> None
result = self.client_get('/accounts/login/social/github')
@@ -661,7 +719,8 @@ class GitHubAuthBackendTest(ZulipTestCase):
result = self.client_get(reverse('social:complete', args=['github']),
info={'state': 'state'})
self.assertEqual(result.status_code, 200)
self.assertIn("Sign up for Zulip", result.content.decode('utf8'))
self.assert_in_response("Please click the following button "
"if you wish to register.", result)
self.assertEqual(mock_get_email_address.call_count, 2)
utils.BACKENDS = settings.AUTHENTICATION_BACKENDS
@@ -719,7 +778,7 @@ class GoogleOAuthTest(ZulipTestCase):
class GoogleSubdomainLoginTest(GoogleOAuthTest):
def get_signed_subdomain_cookie(self, data):
# type: (Dict[str, str]) -> Dict[str, str]
# type: (Dict[str, Any]) -> Dict[str, str]
key = 'subdomain.signature'
salt = key + 'zerver.views.auth'
value = ujson.dumps(data)
@@ -789,7 +848,8 @@ class GoogleSubdomainLoginTest(GoogleOAuthTest):
# type: () -> None
data = {'name': 'Full Name',
'email': 'hamlet@zulip.com',
'subdomain': 'zulip'}
'subdomain': 'zulip',
'is_signup': False}
self.client.cookies = SimpleCookie(self.get_signed_subdomain_cookie(data))
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
@@ -807,18 +867,48 @@ class GoogleSubdomainLoginTest(GoogleOAuthTest):
self.assertEqual(result.status_code, 302)
self.assertTrue(result['Location'].endswith, '?subdomain=1')
def test_log_into_subdomain_when_is_signup_is_true(self):
# type: () -> None
data = {'name': 'Full Name',
'email': 'hamlet@zulip.com',
'subdomain': 'zulip',
'is_signup': True}
self.client.cookies = SimpleCookie(self.get_signed_subdomain_cookie(data))
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
result = self.client_get('/accounts/login/subdomain/')
self.assertEqual(result.status_code, 200)
self.assert_in_response('hamlet@zulip.com is already active', result)
def test_log_into_subdomain_when_is_signup_is_true_and_new_user(self):
# type: () -> None
data = {'name': 'New User Name',
'email': 'new@zulip.com',
'subdomain': 'zulip',
'is_signup': True}
self.client.cookies = SimpleCookie(self.get_signed_subdomain_cookie(data))
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
result = self.client_get('/accounts/login/subdomain/')
self.assertEqual(result.status_code, 302)
confirmation = Confirmation.objects.all().first()
confirmation_key = confirmation.confirmation_key
self.assertIn('do_confirm/' + confirmation_key, result.url)
def test_log_into_subdomain_when_email_is_none(self):
# type: () -> None
data = {'name': None,
'email': None,
'subdomain': 'zulip'}
'subdomain': 'zulip',
'is_signup': False}
self.client.cookies = SimpleCookie(self.get_signed_subdomain_cookie(data))
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'), \
mock.patch('logging.warning'):
result = self.client_get('/accounts/login/subdomain/')
self.assertEqual(result.status_code, 200)
self.assertIn("Sign up for Zulip", result.content.decode('utf8'))
self.assert_in_response("Please click the following button if you "
"wish to register", result)
def test_user_cannot_log_into_nonexisting_realm(self):
# type: () -> None
@@ -881,16 +971,19 @@ class GoogleSubdomainLoginTest(GoogleOAuthTest):
self.assertEqual(uri, 'http://zulip.testserver/accounts/login/subdomain/')
result = self.client_get(result.url)
result = self.client_get(result.url) # Call the confirmation url.
self.assert_in_response(
"You attempted to login using newuser@zulip.com, "
"but newuser@zulip.com does", result)
# Click confirm registraton button.
result = self.client_post('/register/',
{'email': email})
self.assertEqual(result.status_code, 302)
self.client_get(result.url)
assert Confirmation.objects.all().count() == 1
confirmation = Confirmation.objects.all().first()
url = Confirmation.objects.get_activation_url(confirmation.confirmation_key)
result = self.client_get(url)
key_match = re.search('value="(?P<key>[0-9a-f]+)" name="key"', result.content.decode("utf-8"))
name_match = re.search('value="(?P<name>[^"]+)" name="full_name"', result.content.decode("utf-8"))
# This goes through a brief stop on a page that auto-submits via JS
result = self.client_post('/accounts/register/',
{'full_name': name_match.group("name"),
'key': key_match.group("key"),
'from_confirmation': "1"})
self.assertEqual(result.status_code, 200)
result = self.client_post('/accounts/register/',
{'full_name': "New User",
'password': 'test_password',
@@ -924,18 +1017,19 @@ class GoogleLoginTest(GoogleOAuthTest):
value=email)])
account_response = ResponseMock(200, account_data)
result = self.google_oauth2_test(token_response, account_response)
self.assert_in_response(
"You attempted to login using newuser@zulip.com, "
"but newuser@zulip.com does", result)
# Click confirm registraton button.
result = self.client_post('/register/',
{'email': email})
self.assertEqual(result.status_code, 302)
result = self.client_get(result.url)
self.client_get(result.url)
assert Confirmation.objects.all().count() == 1
confirmation = Confirmation.objects.all().first()
url = Confirmation.objects.get_activation_url(confirmation.confirmation_key)
result = self.client_get(url)
key_match = re.search('value="(?P<key>[0-9a-f]+)" name="key"', result.content.decode("utf-8"))
name_match = re.search('value="(?P<name>[^"]+)" name="full_name"', result.content.decode("utf-8"))
# This goes through a brief stop on a page that auto-submits via JS
result = self.client_post('/accounts/register/',
{'full_name': name_match.group("name"),
'key': key_match.group("key"),
'from_confirmation': "1"})
self.assertEqual(result.status_code, 200)
result = self.client_post('/accounts/register/',
{'full_name': "New User",
'password': 'test_password',
@@ -1436,8 +1530,9 @@ class TestZulipRemoteUserBackend(ZulipTestCase):
email = 'nonexisting@zulip.com'
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',)):
result = self.client_post('/accounts/login/sso/', REMOTE_USER=email)
self.assertEqual(result.status_code, 302)
self.assertEqual(result.status_code, 200)
self.assertIs(get_session_dict_user(self.client.session), None)
self.assert_in_response("You attempted to login using", result)
def test_login_failure_due_to_invalid_email(self):
# type: () -> None
@@ -1462,7 +1557,7 @@ class TestZulipRemoteUserBackend(ZulipTestCase):
REMOTE_USER=email)
self.assertEqual(result.status_code, 200)
self.assertIs(get_session_dict_user(self.client.session), None)
self.assertIn(b"Sign up for Zulip", result.content)
self.assert_in_response("You attempted to login using", result)
def test_login_failure_due_to_empty_subdomain(self):
# type: () -> None
@@ -1474,7 +1569,7 @@ class TestZulipRemoteUserBackend(ZulipTestCase):
REMOTE_USER=email)
self.assertEqual(result.status_code, 200)
self.assertIs(get_session_dict_user(self.client.session), None)
self.assertIn(b"Sign up for Zulip", result.content)
self.assert_in_response("You attempted to login using", result)
def test_login_success_under_subdomains(self):
# type: () -> None
@@ -1556,7 +1651,7 @@ class TestJWTLogin(ZulipTestCase):
web_token = jwt.encode(payload, auth_key).decode('utf8')
data = {'json_web_token': web_token}
result = self.client_post('/accounts/login/jwt/', data)
self.assertEqual(result.status_code, 302) # This should ideally be not 200.
self.assertEqual(result.status_code, 200) # This should ideally be not 200.
self.assertIs(get_session_dict_user(self.client.session), None)
# The /accounts/login/jwt/ endpoint should also handle the case
@@ -1565,7 +1660,7 @@ class TestJWTLogin(ZulipTestCase):
'zerver.views.auth.authenticate',
side_effect=UserProfile.DoesNotExist("Do not exist")):
result = self.client_post('/accounts/login/jwt/', data)
self.assertEqual(result.status_code, 302) # This should ideally be not 200.
self.assertEqual(result.status_code, 200) # This should ideally be not 200.
self.assertIs(get_session_dict_user(self.client.session), None)
def test_login_failure_due_to_wrong_subdomain(self):
@@ -1989,3 +2084,19 @@ class LoginEmailValidatorTestCase(TestCase):
# type: () -> None
with self.assertRaises(JsonableError):
validate_login_email(u'hamlet')
class LoginOrRegisterRemoteUserTestCase(ZulipTestCase):
def test_invalid_subdomain(self):
# type: () -> None
email = 'hamlet@zulip.com'
full_name = 'Hamlet'
invalid_subdomain = True
user_profile = get_user_profile_by_email(email)
request = POSTRequestMock({}, user_profile)
response = login_or_register_remote_user(
request,
email,
user_profile,
full_name=full_name,
invalid_subdomain=invalid_subdomain)
self.assertIn('/accounts/login/?subdomain=1', response.url)

View File

@@ -17,9 +17,11 @@ from zilencer.models import Deployment
from zerver.forms import HomepageForm, WRONG_SUBDOMAIN_ERROR
from zerver.lib.actions import do_change_password
from zerver.views.auth import login_or_register_remote_user
from zerver.views.invite import get_invitee_emails_set
from zerver.views.registration import confirmation_key, \
redirect_and_log_into_subdomain, send_registration_completion_email
from zerver.models import (
get_realm, get_prereg_user_by_email, get_user_profile_by_email,
get_unique_open_realm, completely_open,
@@ -45,7 +47,7 @@ from zerver.lib.mobile_auth_otp import xor_hex_strings, ascii_to_hex, \
from zerver.lib.notifications import enqueue_welcome_emails, \
one_click_unsubscribe_link
from zerver.lib.test_helpers import find_pattern_in_email, find_key_by_email, queries_captured, \
HostRequestMock, unsign_subdomain_cookie
HostRequestMock, unsign_subdomain_cookie, POSTRequestMock
from zerver.lib.test_classes import (
ZulipTestCase,
)
@@ -1160,6 +1162,39 @@ class UserSignUpTest(ZulipTestCase):
'from_confirmation': '1'})
self.assert_in_success_response(["You're almost there."], result)
def test_signup_with_full_name(self):
# type: () -> None
"""
Check if signing up without a full name redirects to a registration
form.
"""
email = "newguy@zulip.com"
password = "newpassword"
result = self.client_post('/accounts/home/', {'email': email})
self.assertEqual(result.status_code, 302)
self.assertTrue(result["Location"].endswith(
"/accounts/send_confirm/%s" % (email,)))
result = self.client_get(result["Location"])
self.assert_in_response("Check your email so we can get started.", result)
# Visit the confirmation link.
confirmation_url = self.get_confirmation_url_from_outbox(email)
result = self.client_get(confirmation_url)
self.assertEqual(result.status_code, 200)
result = self.client_post(
'/accounts/register/',
{'password': password,
'realm_name': 'Zulip Test',
'realm_subdomain': 'zuliptest',
'key': find_key_by_email(email),
'realm_org_type': Realm.COMMUNITY,
'terms': True,
'full_name': "New Guy",
'from_confirmation': '1'})
self.assert_in_success_response(["You're almost there."], result)
def test_signup_invalid_subdomain(self):
# type: () -> None
"""
@@ -1765,3 +1800,96 @@ class MobileAuthOTPTest(ZulipTestCase):
decryped = otp_decrypt_api_key(result, otp)
self.assertEqual(decryped, hamlet.api_key)
class LoginOrAskForRegistrationTestCase(ZulipTestCase):
def test_confirm(self):
# type: () -> None
request = POSTRequestMock({}, None)
email = 'new@zulip.com'
user_profile = None # type: Optional[UserProfile]
full_name = 'New User'
invalid_subdomain = False
response = login_or_register_remote_user(
request,
email,
user_profile,
full_name=full_name,
invalid_subdomain=invalid_subdomain)
self.assert_in_response('You attempted to login using new@zulip.com, '
'but new@zulip.com does',
response)
def test_invalid_subdomain(self):
# type: () -> None
request = POSTRequestMock({}, None)
email = 'new@zulip.com'
user_profile = None # type: Optional[UserProfile]
full_name = 'New User'
invalid_subdomain = True
response = login_or_register_remote_user(
request,
email,
user_profile,
full_name=full_name,
invalid_subdomain=invalid_subdomain)
self.assertEqual(response.status_code, 302)
self.assertIn('/accounts/login/?subdomain=1', response.url)
def test_invalid_email(self):
# type: () -> None
request = POSTRequestMock({}, None)
email = None # type: Optional[Text]
user_profile = None # type: Optional[UserProfile]
full_name = 'New User'
invalid_subdomain = False
response = login_or_register_remote_user(
request,
email,
user_profile,
full_name=full_name,
invalid_subdomain=invalid_subdomain)
self.assert_in_response('Please click the following button if '
'you wish to register', response)
def test_login_under_subdomains(self):
# type: () -> None
request = POSTRequestMock({}, None)
setattr(request, 'session', self.client.session)
email = 'hamlet@zulip.com'
user_profile = get_user_profile_by_email(email)
user_profile.backend = 'zproject.backends.GitHubAuthBackend'
full_name = 'Hamlet'
invalid_subdomain = False
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
response = login_or_register_remote_user(
request,
email,
user_profile,
full_name=full_name,
invalid_subdomain=invalid_subdomain)
user_id = get_session_dict_user(getattr(request, 'session'))
self.assertEqual(user_id, user_profile.id)
self.assertEqual(response.status_code, 302)
self.assertIn('http://zulip.testserver', response.url)
def test_login_without_subdomains(self):
# type: () -> None
request = POSTRequestMock({}, None)
setattr(request, 'session', self.client.session)
setattr(request, 'get_host', lambda: 'localhost')
email = 'hamlet@zulip.com'
user_profile = get_user_profile_by_email(email)
user_profile.backend = 'zproject.backends.GitHubAuthBackend'
full_name = 'Hamlet'
invalid_subdomain = False
with self.settings(REALMS_HAVE_SUBDOMAINS=False):
response = login_or_register_remote_user(
request,
email,
user_profile,
full_name=full_name,
invalid_subdomain=invalid_subdomain)
user_id = get_session_dict_user(getattr(request, 'session'))
self.assertEqual(user_id, user_profile.id)
self.assertEqual(response.status_code, 302)
self.assertIn('http://localhost', response.url)

View File

@@ -85,7 +85,7 @@ def redirect_to_subdomain_login_url():
def login_or_register_remote_user(request, remote_username, user_profile, full_name='',
invalid_subdomain=False, mobile_flow_otp=None,
is_signup=False):
# type: (HttpRequest, Text, UserProfile, Text, bool, Optional[str]) -> HttpResponse
# type: (HttpRequest, Text, UserProfile, Text, bool, Optional[str], bool) -> HttpResponse
if invalid_subdomain:
# Show login page with an error message
return redirect_to_subdomain_login_url()