2016-04-21 18:34:54 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from django.conf import settings
|
2017-06-15 07:15:57 +02:00
|
|
|
from django.core import mail
|
2016-09-13 21:30:18 +02:00
|
|
|
from django.http import HttpResponse
|
2017-05-25 02:29:42 +02:00
|
|
|
from django.test import override_settings
|
2016-04-21 18:34:54 +02:00
|
|
|
from django_auth_ldap.backend import _LDAPUser
|
2017-04-20 08:25:15 +02:00
|
|
|
from django.contrib.auth import authenticate
|
2016-07-25 11:24:36 +02:00
|
|
|
from django.test.client import RequestFactory
|
2018-02-12 23:12:47 +01:00
|
|
|
from typing import Any, Callable, Dict, List, Optional, Text, Tuple
|
2016-10-24 14:41:45 +02:00
|
|
|
from builtins import object
|
2016-10-26 12:35:57 +02:00
|
|
|
from oauth2client.crypt import AppIdentityError
|
2016-10-17 14:28:23 +02:00
|
|
|
from django.core import signing
|
2018-01-30 06:05:25 +01:00
|
|
|
from django.urls import reverse
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2016-10-24 11:38:38 +02:00
|
|
|
import jwt
|
2016-04-21 18:34:54 +02:00
|
|
|
import mock
|
2016-09-13 21:30:18 +02:00
|
|
|
import re
|
2017-10-27 02:45:38 +02:00
|
|
|
import time
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2016-12-24 04:35:58 +01:00
|
|
|
from zerver.forms import HomepageForm
|
2017-03-21 18:08:40 +01:00
|
|
|
from zerver.lib.actions import (
|
|
|
|
do_deactivate_realm,
|
|
|
|
do_deactivate_user,
|
|
|
|
do_reactivate_realm,
|
|
|
|
do_reactivate_user,
|
|
|
|
do_set_realm_authentication_methods,
|
2017-09-27 03:34:58 +02:00
|
|
|
create_stream_if_needed,
|
2017-03-21 18:08:40 +01:00
|
|
|
)
|
2017-03-19 20:01:01 +01:00
|
|
|
from zerver.lib.mobile_auth_otp import otp_decrypt_api_key
|
2017-05-04 01:13:56 +02:00
|
|
|
from zerver.lib.validator import validate_login_email, \
|
2018-02-12 23:12:47 +01:00
|
|
|
check_bool, check_dict_only, check_string, Validator
|
2017-04-10 08:06:10 +02:00
|
|
|
from zerver.lib.request import JsonableError
|
2016-04-21 21:07:43 +02:00
|
|
|
from zerver.lib.initial_password import initial_password
|
2017-03-08 11:43:35 +01:00
|
|
|
from zerver.lib.sessions import get_session_dict_user
|
2016-11-10 19:30:09 +01:00
|
|
|
from zerver.lib.test_classes import (
|
|
|
|
ZulipTestCase,
|
2016-04-21 18:34:54 +02:00
|
|
|
)
|
2017-04-20 08:25:15 +02:00
|
|
|
from zerver.lib.test_helpers import POSTRequestMock
|
2016-04-21 18:34:54 +02:00
|
|
|
from zerver.models import \
|
2017-05-23 20:57:59 +02:00
|
|
|
get_realm, email_to_username, UserProfile, \
|
2017-09-27 03:34:58 +02:00
|
|
|
PreregistrationUser, Realm, get_user, MultiuseInvite
|
2016-10-26 14:40:14 +02:00
|
|
|
|
2017-09-27 03:34:58 +02:00
|
|
|
from confirmation.models import Confirmation, confirmation_url, create_confirmation_link
|
2016-04-21 18:34:54 +02:00
|
|
|
|
|
|
|
from zproject.backends import ZulipDummyBackend, EmailAuthBackend, \
|
|
|
|
GoogleMobileOauth2Backend, ZulipRemoteUserBackend, ZulipLDAPAuthBackend, \
|
2016-10-26 13:50:00 +02:00
|
|
|
ZulipLDAPUserPopulator, DevAuthBackend, GitHubAuthBackend, ZulipAuthMixin, \
|
2016-11-07 01:44:15 +01:00
|
|
|
dev_auth_enabled, password_auth_enabled, github_auth_enabled, \
|
2017-09-22 10:58:12 +02:00
|
|
|
require_email_format_usernames, SocialAuthMixin, AUTH_BACKEND_NAME_MAP, \
|
|
|
|
ZulipLDAPConfigurationError
|
2016-07-25 11:24:36 +02:00
|
|
|
|
2017-04-20 08:25:15 +02:00
|
|
|
from zerver.views.auth import (maybe_send_to_registration,
|
2017-10-27 02:45:38 +02:00
|
|
|
login_or_register_remote_user,
|
|
|
|
_subdomain_token_salt)
|
2017-02-27 08:30:26 +01:00
|
|
|
from version import ZULIP_VERSION
|
2016-10-26 14:40:14 +02:00
|
|
|
|
2017-03-07 08:32:40 +01:00
|
|
|
from social_core.exceptions import AuthFailed, AuthStateForbidden
|
2017-04-19 19:05:04 +02:00
|
|
|
from social_django.strategy import DjangoStrategy
|
2017-01-21 16:52:59 +01:00
|
|
|
from social_django.storage import BaseDjangoStorage
|
|
|
|
from social_core.backends.github import GithubOrganizationOAuth2, GithubTeamOAuth2, \
|
2016-08-02 11:07:45 +02:00
|
|
|
GithubOAuth2
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2017-11-05 05:30:31 +01:00
|
|
|
import urllib
|
2017-11-06 03:07:49 +01:00
|
|
|
from http.cookies import SimpleCookie
|
2016-04-21 18:34:54 +02:00
|
|
|
import ujson
|
2017-10-27 02:45:38 +02:00
|
|
|
from zerver.lib.test_helpers import MockLDAP, load_subdomain_token
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2017-04-27 06:46:43 +02:00
|
|
|
class AuthBackendTest(ZulipTestCase):
|
2017-11-19 04:02:03 +01:00
|
|
|
def get_username(self, email_to_username: Optional[Callable[[Text], Text]]=None) -> Text:
|
2017-05-23 20:57:59 +02:00
|
|
|
username = self.example_email('hamlet')
|
2017-03-28 11:11:29 +02:00
|
|
|
if email_to_username is not None:
|
2017-05-23 20:57:59 +02:00
|
|
|
username = email_to_username(self.example_email('hamlet'))
|
2017-03-28 11:11:29 +02:00
|
|
|
|
|
|
|
return username
|
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def verify_backend(self, backend: Any, good_kwargs: Optional[Dict[str, Any]]=None, bad_kwargs: Optional[Dict[str, Any]]=None) -> None:
|
2017-03-28 11:16:36 +02:00
|
|
|
|
2017-05-23 20:57:59 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2017-03-28 11:16:36 +02:00
|
|
|
|
2017-09-15 21:51:45 +02:00
|
|
|
assert good_kwargs is not None
|
2016-04-21 18:34:54 +02:00
|
|
|
|
|
|
|
# If bad_kwargs was specified, verify auth fails in that case
|
|
|
|
if bad_kwargs is not None:
|
2017-03-28 11:16:36 +02:00
|
|
|
self.assertIsNone(backend.authenticate(**bad_kwargs))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
|
|
|
# Verify auth works
|
2017-03-28 11:16:36 +02:00
|
|
|
result = backend.authenticate(**good_kwargs)
|
2016-04-21 18:34:54 +02:00
|
|
|
self.assertEqual(user_profile, result)
|
|
|
|
|
|
|
|
# Verify auth fails with a deactivated user
|
|
|
|
do_deactivate_user(user_profile)
|
2017-03-28 11:16:36 +02:00
|
|
|
self.assertIsNone(backend.authenticate(**good_kwargs))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
|
|
|
# Reactivate the user and verify auth works again
|
|
|
|
do_reactivate_user(user_profile)
|
2017-03-28 11:16:36 +02:00
|
|
|
result = backend.authenticate(**good_kwargs)
|
2016-04-21 18:34:54 +02:00
|
|
|
self.assertEqual(user_profile, result)
|
|
|
|
|
|
|
|
# Verify auth fails with a deactivated realm
|
|
|
|
do_deactivate_realm(user_profile.realm)
|
2017-03-28 11:16:36 +02:00
|
|
|
self.assertIsNone(backend.authenticate(**good_kwargs))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
|
|
|
# Verify auth works again after reactivating the realm
|
|
|
|
do_reactivate_realm(user_profile.realm)
|
2017-03-28 11:16:36 +02:00
|
|
|
result = backend.authenticate(**good_kwargs)
|
2016-04-21 18:34:54 +02:00
|
|
|
self.assertEqual(user_profile, result)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
# ZulipDummyBackend isn't a real backend so the remainder
|
|
|
|
# doesn't make sense for it
|
|
|
|
if isinstance(backend, ZulipDummyBackend):
|
|
|
|
return
|
|
|
|
|
|
|
|
# Verify auth fails if the auth backend is disabled on server
|
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipDummyBackend',)):
|
2017-03-28 11:16:36 +02:00
|
|
|
self.assertIsNone(backend.authenticate(**good_kwargs))
|
2016-11-02 21:41:10 +01:00
|
|
|
|
|
|
|
# Verify auth fails if the auth backend is disabled for the realm
|
|
|
|
for backend_name in AUTH_BACKEND_NAME_MAP.keys():
|
|
|
|
if isinstance(backend, AUTH_BACKEND_NAME_MAP[backend_name]):
|
|
|
|
break
|
|
|
|
|
|
|
|
index = getattr(user_profile.realm.authentication_methods, backend_name).number
|
|
|
|
user_profile.realm.authentication_methods.set_bit(index, False)
|
|
|
|
user_profile.realm.save()
|
2017-11-17 23:14:08 +01:00
|
|
|
if 'realm' in good_kwargs:
|
|
|
|
# Because this test is a little unfaithful to the ordering
|
|
|
|
# (i.e. we fetched the realm object before this function
|
|
|
|
# was called, when in fact it should be fetched after we
|
|
|
|
# changed the allowed authentication methods), we need to
|
|
|
|
# propagate the changes we just made to the actual realm
|
|
|
|
# object in good_kwargs.
|
|
|
|
good_kwargs['realm'] = user_profile.realm
|
2017-03-28 11:16:36 +02:00
|
|
|
self.assertIsNone(backend.authenticate(**good_kwargs))
|
2016-11-02 21:41:10 +01:00
|
|
|
user_profile.realm.authentication_methods.set_bit(index, True)
|
|
|
|
user_profile.realm.save()
|
2016-11-07 00:09:21 +01:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_dummy_backend(self) -> None:
|
2017-10-03 02:29:20 +02:00
|
|
|
realm = get_realm("zulip")
|
2017-03-28 11:16:36 +02:00
|
|
|
username = self.get_username()
|
2016-04-21 18:34:54 +02:00
|
|
|
self.verify_backend(ZulipDummyBackend(),
|
2017-03-28 11:16:36 +02:00
|
|
|
good_kwargs=dict(username=username,
|
2017-10-03 02:29:20 +02:00
|
|
|
realm=realm,
|
2017-03-28 11:16:36 +02:00
|
|
|
use_dummy_backend=True),
|
|
|
|
bad_kwargs=dict(username=username,
|
2017-10-03 02:29:20 +02:00
|
|
|
realm=realm,
|
2017-03-28 11:16:36 +02:00
|
|
|
use_dummy_backend=False))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def setup_subdomain(self, user_profile: UserProfile) -> None:
|
2016-10-07 13:38:01 +02:00
|
|
|
realm = user_profile.realm
|
2016-10-26 18:13:43 +02:00
|
|
|
realm.string_id = 'zulip'
|
2016-10-07 13:38:01 +02:00
|
|
|
realm.save()
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_email_auth_backend(self) -> None:
|
2017-03-28 11:16:36 +02:00
|
|
|
username = self.get_username()
|
2017-05-23 20:57:59 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2016-04-21 18:34:54 +02:00
|
|
|
password = "testpassword"
|
|
|
|
user_profile.set_password(password)
|
|
|
|
user_profile.save()
|
|
|
|
|
2017-03-23 07:49:42 +01:00
|
|
|
with mock.patch('zproject.backends.email_auth_enabled',
|
|
|
|
return_value=False), \
|
|
|
|
mock.patch('zproject.backends.password_auth_enabled',
|
|
|
|
return_value=True):
|
|
|
|
return_data = {} # type: Dict[str, bool]
|
2017-05-23 20:57:59 +02:00
|
|
|
user = EmailAuthBackend().authenticate(self.example_email('hamlet'),
|
2017-11-17 23:56:45 +01:00
|
|
|
realm=get_realm("zulip"),
|
2017-03-23 07:49:42 +01:00
|
|
|
password=password,
|
|
|
|
return_data=return_data)
|
|
|
|
self.assertEqual(user, None)
|
|
|
|
self.assertTrue(return_data['email_auth_disabled'])
|
|
|
|
|
2016-10-07 13:38:01 +02:00
|
|
|
self.verify_backend(EmailAuthBackend(),
|
|
|
|
good_kwargs=dict(password=password,
|
2017-03-28 11:16:36 +02:00
|
|
|
username=username,
|
2017-11-17 23:56:45 +01:00
|
|
|
realm=get_realm('zulip'),
|
2017-09-15 21:51:45 +02:00
|
|
|
return_data=dict()),
|
|
|
|
bad_kwargs=dict(password=password,
|
|
|
|
username=username,
|
2017-11-17 23:56:45 +01:00
|
|
|
realm=get_realm('zephyr'),
|
|
|
|
return_data=dict()))
|
|
|
|
self.verify_backend(EmailAuthBackend(),
|
|
|
|
good_kwargs=dict(password=password,
|
|
|
|
username=username,
|
|
|
|
realm=get_realm('zulip'),
|
|
|
|
return_data=dict()),
|
|
|
|
bad_kwargs=dict(password=password,
|
|
|
|
username=username,
|
|
|
|
realm=None,
|
2017-09-15 21:51:45 +02:00
|
|
|
return_data=dict()))
|
2016-10-07 13:38:01 +02:00
|
|
|
|
2017-11-20 03:22:57 +01:00
|
|
|
def test_email_auth_backend_disabled_password_auth(self) -> None:
|
2017-05-23 20:57:59 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2016-04-21 18:34:54 +02:00
|
|
|
password = "testpassword"
|
|
|
|
user_profile.set_password(password)
|
|
|
|
user_profile.save()
|
|
|
|
# Verify if a realm has password auth disabled, correct password is rejected
|
|
|
|
with mock.patch('zproject.backends.password_auth_enabled', return_value=False):
|
2017-11-17 23:56:45 +01:00
|
|
|
self.assertIsNone(EmailAuthBackend().authenticate(self.example_email('hamlet'),
|
|
|
|
password,
|
|
|
|
realm=get_realm("zulip")))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2017-04-27 06:46:43 +02:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipDummyBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_no_backend_enabled(self) -> None:
|
2017-04-27 06:46:43 +02:00
|
|
|
result = self.client_get('/login/')
|
|
|
|
self.assert_in_success_response(["No authentication backends are enabled"], result)
|
|
|
|
|
|
|
|
result = self.client_get('/register/')
|
|
|
|
self.assert_in_success_response(["No authentication backends are enabled"], result)
|
|
|
|
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GoogleMobileOauth2Backend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_any_backend_enabled(self) -> None:
|
2017-04-27 06:46:43 +02:00
|
|
|
|
|
|
|
# testing to avoid false error messages.
|
|
|
|
result = self.client_get('/login/')
|
|
|
|
self.assert_not_in_success_response(["No Authentication Backend is enabled."], result)
|
|
|
|
|
|
|
|
result = self.client_get('/register/')
|
|
|
|
self.assert_not_in_success_response(["No Authentication Backend is enabled."], result)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GoogleMobileOauth2Backend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_backend(self) -> None:
|
2017-05-07 19:39:30 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
email = user_profile.email
|
2016-04-21 18:34:54 +02:00
|
|
|
backend = GoogleMobileOauth2Backend()
|
|
|
|
payload = dict(email_verified=True,
|
|
|
|
email=email)
|
|
|
|
|
2016-10-07 13:38:01 +02:00
|
|
|
with mock.patch('apiclient.sample_tools.client.verify_id_token', return_value=payload):
|
|
|
|
self.verify_backend(backend,
|
2017-11-21 21:23:07 +01:00
|
|
|
good_kwargs=dict(realm=get_realm("zulip")),
|
|
|
|
bad_kwargs=dict(realm=get_realm('invalid')))
|
2016-10-07 13:38:01 +02:00
|
|
|
|
2016-04-21 18:34:54 +02:00
|
|
|
# Verify valid_attestation parameter is set correctly
|
|
|
|
unverified_payload = dict(email_verified=False)
|
2017-11-21 21:23:07 +01:00
|
|
|
with mock.patch('apiclient.sample_tools.client.verify_id_token',
|
|
|
|
return_value=unverified_payload):
|
2017-06-04 11:36:52 +02:00
|
|
|
ret = dict() # type: Dict[str, str]
|
2017-11-21 21:23:07 +01:00
|
|
|
result = backend.authenticate(realm=get_realm("zulip"), return_data=ret)
|
2016-04-21 18:34:54 +02:00
|
|
|
self.assertIsNone(result)
|
|
|
|
self.assertFalse(ret["valid_attestation"])
|
|
|
|
|
|
|
|
nonexistent_user_payload = dict(email_verified=True, email="invalid@zulip.com")
|
|
|
|
with mock.patch('apiclient.sample_tools.client.verify_id_token',
|
|
|
|
return_value=nonexistent_user_payload):
|
|
|
|
ret = dict()
|
2017-11-21 21:23:07 +01:00
|
|
|
result = backend.authenticate(realm=get_realm("zulip"), return_data=ret)
|
2016-04-21 18:34:54 +02:00
|
|
|
self.assertIsNone(result)
|
|
|
|
self.assertTrue(ret["valid_attestation"])
|
2016-10-26 12:35:57 +02:00
|
|
|
with mock.patch('apiclient.sample_tools.client.verify_id_token',
|
|
|
|
side_effect=AppIdentityError):
|
|
|
|
ret = dict()
|
2017-11-21 21:23:07 +01:00
|
|
|
result = backend.authenticate(realm=get_realm("zulip"), return_data=ret)
|
2016-10-26 12:35:57 +02:00
|
|
|
self.assertIsNone(result)
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_ldap_backend(self) -> None:
|
2017-05-07 19:39:30 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
email = user_profile.email
|
2016-04-21 18:34:54 +02:00
|
|
|
password = "test_password"
|
2016-10-07 13:38:01 +02:00
|
|
|
self.setup_subdomain(user_profile)
|
|
|
|
|
2017-03-28 11:16:36 +02:00
|
|
|
username = self.get_username()
|
2016-04-21 18:34:54 +02:00
|
|
|
backend = ZulipLDAPAuthBackend()
|
|
|
|
|
|
|
|
# Test LDAP auth fails when LDAP server rejects password
|
2016-11-30 14:17:35 +01:00
|
|
|
with mock.patch('django_auth_ldap.backend._LDAPUser._authenticate_user_dn',
|
|
|
|
side_effect=_LDAPUser.AuthenticationFailed("Failed")), (
|
2017-01-24 07:06:13 +01:00
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser._check_requirements')), (
|
2018-02-22 08:32:21 +01:00
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser.attrs',
|
2017-01-24 07:06:13 +01:00
|
|
|
return_value=dict(full_name=['Hamlet']))):
|
2017-11-17 23:56:45 +01:00
|
|
|
self.assertIsNone(backend.authenticate(email, password, realm=get_realm("zulip")))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2016-11-30 14:17:35 +01:00
|
|
|
with mock.patch('django_auth_ldap.backend._LDAPUser._authenticate_user_dn'), (
|
2017-01-24 07:06:13 +01:00
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser._check_requirements')), (
|
2018-02-22 08:32:21 +01:00
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser.attrs',
|
2017-01-24 07:06:13 +01:00
|
|
|
return_value=dict(full_name=['Hamlet']))):
|
2017-03-28 11:16:36 +02:00
|
|
|
self.verify_backend(backend,
|
2017-09-15 21:51:45 +02:00
|
|
|
bad_kwargs=dict(username=username,
|
|
|
|
password=password,
|
2017-11-17 23:56:45 +01:00
|
|
|
realm=get_realm('zephyr')),
|
2017-03-28 11:16:36 +02:00
|
|
|
good_kwargs=dict(username=username,
|
|
|
|
password=password,
|
2017-11-17 23:56:45 +01:00
|
|
|
realm=get_realm('zulip')))
|
|
|
|
self.verify_backend(backend,
|
|
|
|
bad_kwargs=dict(username=username,
|
|
|
|
password=password,
|
|
|
|
realm=get_realm('acme')),
|
|
|
|
good_kwargs=dict(username=username,
|
|
|
|
password=password,
|
|
|
|
realm=get_realm('zulip')))
|
2017-03-23 07:49:42 +01:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_devauth_backend(self) -> None:
|
2017-03-28 11:16:36 +02:00
|
|
|
self.verify_backend(DevAuthBackend(),
|
2017-11-21 21:19:20 +01:00
|
|
|
good_kwargs=dict(dev_auth_username=self.get_username(),
|
|
|
|
realm=get_realm("zulip")),
|
|
|
|
bad_kwargs=dict(dev_auth_username=self.get_username(),
|
|
|
|
realm=get_realm("invalid")))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_remote_user_backend(self) -> None:
|
2017-03-28 11:16:36 +02:00
|
|
|
username = self.get_username()
|
2016-10-07 13:38:01 +02:00
|
|
|
self.verify_backend(ZulipRemoteUserBackend(),
|
2017-03-28 11:16:36 +02:00
|
|
|
good_kwargs=dict(remote_user=username,
|
2017-11-17 23:14:08 +01:00
|
|
|
realm=get_realm('zulip')),
|
2017-09-15 21:51:45 +02:00
|
|
|
bad_kwargs=dict(remote_user=username,
|
2017-11-17 23:14:08 +01:00
|
|
|
realm=get_realm('zephyr')))
|
|
|
|
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',))
|
2017-12-09 06:57:59 +01:00
|
|
|
def test_remote_user_backend_invalid_realm(self) -> None:
|
2017-11-17 23:14:08 +01:00
|
|
|
username = self.get_username()
|
|
|
|
self.verify_backend(ZulipRemoteUserBackend(),
|
|
|
|
good_kwargs=dict(remote_user=username,
|
|
|
|
realm=get_realm('zulip')),
|
|
|
|
bad_kwargs=dict(remote_user=username,
|
|
|
|
realm=None))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',))
|
2017-09-15 21:51:45 +02:00
|
|
|
@override_settings(SSO_APPEND_DOMAIN='zulip.com')
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_remote_user_backend_sso_append_domain(self) -> None:
|
2017-03-28 11:16:36 +02:00
|
|
|
username = self.get_username(email_to_username)
|
2017-09-15 21:51:45 +02:00
|
|
|
self.verify_backend(ZulipRemoteUserBackend(),
|
|
|
|
good_kwargs=dict(remote_user=username,
|
2017-11-17 23:14:08 +01:00
|
|
|
realm=get_realm("zulip")),
|
2017-09-15 21:51:45 +02:00
|
|
|
bad_kwargs=dict(remote_user=username,
|
2017-11-17 23:14:08 +01:00
|
|
|
realm=get_realm('zephyr')))
|
2016-04-21 21:07:43 +02:00
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitHubAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend(self) -> None:
|
2017-05-08 16:23:43 +02:00
|
|
|
user = self.example_user('hamlet')
|
|
|
|
email = user.email
|
2016-10-07 13:38:01 +02:00
|
|
|
good_kwargs = dict(response=dict(email=email), return_data=dict(),
|
2017-11-21 23:48:40 +01:00
|
|
|
realm=get_realm('zulip'))
|
2017-09-15 21:51:45 +02:00
|
|
|
bad_kwargs = dict(response=dict(email=email), return_data=dict(),
|
2017-11-21 23:48:40 +01:00
|
|
|
realm=None)
|
|
|
|
self.verify_backend(GitHubAuthBackend(),
|
|
|
|
good_kwargs=good_kwargs,
|
|
|
|
bad_kwargs=bad_kwargs)
|
|
|
|
bad_kwargs['realm'] = get_realm("zephyr")
|
2016-07-29 21:47:14 +02:00
|
|
|
self.verify_backend(GitHubAuthBackend(),
|
2016-07-25 11:24:36 +02:00
|
|
|
good_kwargs=good_kwargs,
|
2017-09-15 21:51:45 +02:00
|
|
|
bad_kwargs=bad_kwargs)
|
2016-07-25 11:24:36 +02:00
|
|
|
|
2016-11-07 01:44:15 +01:00
|
|
|
class SocialAuthMixinTest(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_social_auth_mixing(self) -> None:
|
2016-11-07 01:44:15 +01:00
|
|
|
mixin = SocialAuthMixin()
|
|
|
|
with self.assertRaises(NotImplementedError):
|
|
|
|
mixin.get_email_address()
|
|
|
|
with self.assertRaises(NotImplementedError):
|
|
|
|
mixin.get_full_name()
|
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class GitHubAuthBackendTest(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def setUp(self) -> None:
|
2017-05-07 21:25:59 +02:00
|
|
|
self.user_profile = self.example_user('hamlet')
|
|
|
|
self.email = self.user_profile.email
|
2016-07-25 11:24:36 +02:00
|
|
|
self.name = 'Hamlet'
|
2016-07-29 21:47:14 +02:00
|
|
|
self.backend = GitHubAuthBackend()
|
2017-04-19 19:05:04 +02:00
|
|
|
self.backend.strategy = DjangoStrategy(storage=BaseDjangoStorage())
|
2016-07-25 11:24:36 +02:00
|
|
|
self.user_profile.backend = self.backend
|
|
|
|
|
2016-10-07 11:10:21 +02:00
|
|
|
rf = RequestFactory()
|
|
|
|
request = rf.get('/complete')
|
|
|
|
request.session = {}
|
2017-09-15 21:39:21 +02:00
|
|
|
request.get_host = lambda: 'zulip.testserver'
|
2016-10-07 11:10:21 +02:00
|
|
|
request.user = self.user_profile
|
|
|
|
self.backend.strategy.request = request
|
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def do_auth(self, *args: Any, **kwargs: Any) -> UserProfile:
|
2016-11-01 23:50:35 +01:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitHubAuthBackend',)):
|
2017-04-20 08:25:15 +02:00
|
|
|
return authenticate(**kwargs)
|
2016-11-01 23:50:35 +01:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_auth_enabled(self) -> None:
|
2016-10-26 13:57:17 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitHubAuthBackend',)):
|
|
|
|
self.assertTrue(github_auth_enabled())
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_full_name_with_missing_key(self) -> None:
|
2016-10-26 13:57:17 +02:00
|
|
|
self.assertEqual(self.backend.get_full_name(), '')
|
2017-03-23 07:49:42 +01:00
|
|
|
self.assertEqual(self.backend.get_full_name(response={'name': None}), '')
|
2016-10-26 13:57:17 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_full_name_with_none(self) -> None:
|
2017-03-09 09:45:21 +01:00
|
|
|
self.assertEqual(self.backend.get_full_name(response={'email': None}), '')
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_do_auth_with_non_existing_subdomain(self) -> None:
|
2017-01-21 16:52:59 +01:00
|
|
|
with mock.patch('social_core.backends.github.GithubOAuth2.do_auth',
|
2016-11-01 23:50:35 +01:00
|
|
|
side_effect=self.do_auth):
|
2017-10-03 01:31:20 +02:00
|
|
|
self.backend.strategy.session_set('subdomain', 'test')
|
|
|
|
response = dict(email=self.email, name=self.name)
|
|
|
|
result = self.backend.do_auth(response=response)
|
|
|
|
assert(result is not None)
|
|
|
|
self.assertIn('subdomain=1', result.url)
|
2016-07-25 11:24:36 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_do_auth_with_subdomains(self) -> None:
|
2017-01-21 16:52:59 +01:00
|
|
|
with mock.patch('social_core.backends.github.GithubOAuth2.do_auth',
|
2016-12-01 13:10:59 +01:00
|
|
|
side_effect=self.do_auth):
|
2017-09-15 21:39:21 +02:00
|
|
|
self.backend.strategy.session_set('subdomain', 'zulip')
|
|
|
|
response = dict(email=self.email, name=self.name)
|
|
|
|
result = self.backend.do_auth(response=response)
|
|
|
|
assert(result is not None)
|
2017-10-27 02:45:38 +02:00
|
|
|
self.assertTrue(result.url.startswith('http://zulip.testserver/accounts/login/subdomain/'))
|
2016-12-01 13:10:59 +01:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_do_auth_for_default(self) -> None:
|
2017-01-21 16:52:59 +01:00
|
|
|
with mock.patch('social_core.backends.github.GithubOAuth2.do_auth',
|
2016-11-01 23:50:35 +01:00
|
|
|
side_effect=self.do_auth), \
|
2016-10-11 15:14:50 +02:00
|
|
|
mock.patch('zproject.backends.SocialAuthMixin.process_do_auth') as result:
|
2017-11-22 00:46:34 +01:00
|
|
|
self.backend.strategy.session_set('subdomain', 'zulip')
|
2016-11-28 23:29:01 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
2016-08-02 11:07:45 +02:00
|
|
|
self.backend.do_auth('fake-access-token', response=response)
|
|
|
|
|
2017-11-21 23:48:40 +01:00
|
|
|
kwargs = {'realm': get_realm('zulip'),
|
2016-10-11 15:14:50 +02:00
|
|
|
'response': response,
|
2017-11-22 00:41:18 +01:00
|
|
|
'return_data': {'valid_attestation': True}}
|
2016-10-11 15:14:50 +02:00
|
|
|
result.assert_called_with(self.user_profile, 'fake-access-token', **kwargs)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_do_auth_for_default_auth_failed(self) -> None:
|
2017-03-23 07:49:42 +01:00
|
|
|
with mock.patch('social_core.backends.github.GithubOAuth2.do_auth',
|
|
|
|
side_effect=AuthFailed('Not found')), \
|
|
|
|
mock.patch('logging.info'), \
|
|
|
|
mock.patch('zproject.backends.SocialAuthMixin.process_do_auth') as result:
|
2017-11-22 00:46:34 +01:00
|
|
|
self.backend.strategy.session_set('subdomain', 'zulip')
|
2017-03-23 07:49:42 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
|
|
|
|
|
|
|
self.backend.do_auth('fake-access-token', response=response)
|
2017-11-21 23:48:40 +01:00
|
|
|
kwargs = {'realm': get_realm('zulip'),
|
2017-03-23 07:49:42 +01:00
|
|
|
'response': response,
|
|
|
|
'return_data': {}}
|
|
|
|
result.assert_called_with(None, 'fake-access-token', **kwargs)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_do_auth_for_team(self) -> None:
|
2017-01-21 16:52:59 +01:00
|
|
|
with mock.patch('social_core.backends.github.GithubTeamOAuth2.do_auth',
|
2016-11-01 23:50:35 +01:00
|
|
|
side_effect=self.do_auth), \
|
2016-10-11 15:14:50 +02:00
|
|
|
mock.patch('zproject.backends.SocialAuthMixin.process_do_auth') as result:
|
2017-09-15 21:39:21 +02:00
|
|
|
self.backend.strategy.session_set('subdomain', 'zulip')
|
2016-11-28 23:29:01 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
2016-08-02 11:07:45 +02:00
|
|
|
with self.settings(SOCIAL_AUTH_GITHUB_TEAM_ID='zulip-webapp'):
|
|
|
|
self.backend.do_auth('fake-access-token', response=response)
|
|
|
|
|
2017-11-21 23:48:40 +01:00
|
|
|
kwargs = {'realm': get_realm('zulip'),
|
2016-10-11 15:14:50 +02:00
|
|
|
'response': response,
|
2017-11-22 00:41:18 +01:00
|
|
|
'return_data': {'valid_attestation': True}}
|
2016-10-11 15:14:50 +02:00
|
|
|
result.assert_called_with(self.user_profile, 'fake-access-token', **kwargs)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_do_auth_for_team_auth_failed(self) -> None:
|
2017-01-21 16:52:59 +01:00
|
|
|
with mock.patch('social_core.backends.github.GithubTeamOAuth2.do_auth',
|
2016-10-11 14:35:06 +02:00
|
|
|
side_effect=AuthFailed('Not found')), \
|
|
|
|
mock.patch('logging.info'), \
|
|
|
|
mock.patch('zproject.backends.SocialAuthMixin.process_do_auth') as result:
|
2017-09-15 21:39:21 +02:00
|
|
|
self.backend.strategy.session_set('subdomain', 'zulip')
|
2016-11-28 23:29:01 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
2016-10-11 14:35:06 +02:00
|
|
|
with self.settings(SOCIAL_AUTH_GITHUB_TEAM_ID='zulip-webapp'):
|
|
|
|
self.backend.do_auth('fake-access-token', response=response)
|
2017-11-21 23:48:40 +01:00
|
|
|
kwargs = {'realm': get_realm('zulip'),
|
2016-10-11 14:35:06 +02:00
|
|
|
'response': response,
|
|
|
|
'return_data': {}}
|
|
|
|
result.assert_called_with(None, 'fake-access-token', **kwargs)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_do_auth_for_org(self) -> None:
|
2017-01-21 16:52:59 +01:00
|
|
|
with mock.patch('social_core.backends.github.GithubOrganizationOAuth2.do_auth',
|
2016-11-01 23:50:35 +01:00
|
|
|
side_effect=self.do_auth), \
|
2016-10-11 15:14:50 +02:00
|
|
|
mock.patch('zproject.backends.SocialAuthMixin.process_do_auth') as result:
|
2017-09-15 21:39:21 +02:00
|
|
|
self.backend.strategy.session_set('subdomain', 'zulip')
|
2016-11-28 23:29:01 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
2016-08-02 11:07:45 +02:00
|
|
|
with self.settings(SOCIAL_AUTH_GITHUB_ORG_NAME='Zulip'):
|
|
|
|
self.backend.do_auth('fake-access-token', response=response)
|
|
|
|
|
2017-11-21 23:48:40 +01:00
|
|
|
kwargs = {'realm': get_realm('zulip'),
|
2016-10-11 15:14:50 +02:00
|
|
|
'response': response,
|
2017-11-22 00:41:18 +01:00
|
|
|
'return_data': {'valid_attestation': True}}
|
2016-10-11 15:14:50 +02:00
|
|
|
result.assert_called_with(self.user_profile, 'fake-access-token', **kwargs)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_do_auth_for_org_auth_failed(self) -> None:
|
2017-01-21 16:52:59 +01:00
|
|
|
with mock.patch('social_core.backends.github.GithubOrganizationOAuth2.do_auth',
|
2016-10-11 14:35:06 +02:00
|
|
|
side_effect=AuthFailed('Not found')), \
|
|
|
|
mock.patch('logging.info'), \
|
|
|
|
mock.patch('zproject.backends.SocialAuthMixin.process_do_auth') as result:
|
2017-09-15 21:39:21 +02:00
|
|
|
self.backend.strategy.session_set('subdomain', 'zulip')
|
2016-11-28 23:29:01 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
2016-10-11 14:35:06 +02:00
|
|
|
with self.settings(SOCIAL_AUTH_GITHUB_ORG_NAME='Zulip'):
|
|
|
|
self.backend.do_auth('fake-access-token', response=response)
|
2017-11-21 23:48:40 +01:00
|
|
|
kwargs = {'realm': get_realm('zulip'),
|
2016-10-11 14:35:06 +02:00
|
|
|
'response': response,
|
|
|
|
'return_data': {}}
|
|
|
|
result.assert_called_with(None, 'fake-access-token', **kwargs)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_authenticate_nonexisting_user(self) -> None:
|
2017-11-22 00:45:17 +01:00
|
|
|
self.backend.strategy.session_set('subdomain', 'zulip')
|
|
|
|
response = dict(email="invalid@zulip.com", name=self.name)
|
|
|
|
return_data = dict() # type: Dict[str, Any]
|
2017-11-21 23:48:40 +01:00
|
|
|
user = self.backend.authenticate(return_data=return_data, response=response,
|
|
|
|
realm=get_realm("zulip"))
|
2017-11-22 00:45:17 +01:00
|
|
|
self.assertIs(user, None)
|
|
|
|
self.assertTrue(return_data['valid_attestation'])
|
2016-10-11 14:35:06 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_authenticate_invalid_email(self) -> None:
|
2017-03-23 07:49:42 +01:00
|
|
|
response = dict(email=None, name=self.name)
|
2017-06-04 11:36:52 +02:00
|
|
|
return_data = dict() # type: Dict[str, Any]
|
2017-11-21 23:48:40 +01:00
|
|
|
user = self.backend.authenticate(return_data=return_data, response=response,
|
|
|
|
realm=get_realm("zulip"))
|
2017-03-23 07:49:42 +01:00
|
|
|
self.assertIs(user, None)
|
|
|
|
self.assertTrue(return_data['invalid_email'])
|
2017-11-22 01:14:55 +01:00
|
|
|
result = self.backend.process_do_auth(user, return_data=return_data, response=response)
|
|
|
|
self.assertIs(result, None)
|
2017-03-23 07:49:42 +01:00
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def test_github_backend_inactive_user(self) -> None:
|
|
|
|
def do_auth_inactive(*args: Any, **kwargs: Any) -> UserProfile:
|
2016-08-02 11:22:55 +02:00
|
|
|
return_data = kwargs['return_data']
|
2016-07-25 11:24:36 +02:00
|
|
|
return_data['inactive_user'] = True
|
|
|
|
return self.user_profile
|
|
|
|
|
2016-10-12 04:50:38 +02:00
|
|
|
with mock.patch('zerver.views.auth.login_or_register_remote_user') as result, \
|
2017-01-21 16:52:59 +01:00
|
|
|
mock.patch('social_core.backends.github.GithubOAuth2.do_auth',
|
2016-07-25 11:24:36 +02:00
|
|
|
side_effect=do_auth_inactive):
|
2016-11-28 23:29:01 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
2016-07-25 11:24:36 +02:00
|
|
|
user = self.backend.do_auth(response=response)
|
|
|
|
result.assert_not_called()
|
|
|
|
self.assertIs(user, None)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_new_user_wrong_domain(self) -> None:
|
2016-07-25 11:24:36 +02:00
|
|
|
rf = RequestFactory()
|
|
|
|
request = rf.get('/complete')
|
|
|
|
request.session = {}
|
|
|
|
request.user = self.user_profile
|
|
|
|
self.backend.strategy.request = request
|
2017-10-26 02:48:21 +02:00
|
|
|
session_data = {'subdomain': Realm.SUBDOMAIN_FOR_ROOT_DOMAIN, 'is_signup': '1'}
|
2017-04-20 08:25:15 +02:00
|
|
|
self.backend.strategy.session_get = lambda k: session_data.get(k)
|
2016-07-25 11:24:36 +02:00
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def do_auth(*args: Any, **kwargs: Any) -> None:
|
2016-08-02 11:22:55 +02:00
|
|
|
return_data = kwargs['return_data']
|
2016-07-25 11:24:36 +02:00
|
|
|
return_data['valid_attestation'] = True
|
|
|
|
return None
|
|
|
|
|
2017-01-21 16:52:59 +01:00
|
|
|
with mock.patch('social_core.backends.github.GithubOAuth2.do_auth',
|
2016-07-25 11:24:36 +02:00
|
|
|
side_effect=do_auth):
|
2017-04-14 11:01:24 +02:00
|
|
|
email = 'nonexisting@phantom.com'
|
|
|
|
response = dict(email=email, name='Ghost')
|
2016-07-25 11:24:36 +02:00
|
|
|
result = self.backend.do_auth(response=response)
|
|
|
|
self.assert_in_response('action="/register/"', result)
|
2017-10-02 08:32:09 +02:00
|
|
|
self.assert_in_response('Your email address, {}, is not '
|
|
|
|
'in one of the domains that are allowed to register '
|
|
|
|
'for accounts in this organization.'.format(email), result)
|
2016-07-25 11:24:36 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_new_user(self) -> None:
|
2017-08-08 11:21:31 +02:00
|
|
|
rf = RequestFactory()
|
2017-11-08 23:02:50 +01:00
|
|
|
request = rf.get('/complete', HTTP_HOST=self.user_profile.realm.host)
|
2017-08-08 11:21:31 +02:00
|
|
|
request.session = {}
|
|
|
|
request.user = self.user_profile
|
|
|
|
self.backend.strategy.request = request
|
2017-10-26 02:48:21 +02:00
|
|
|
session_data = {'subdomain': Realm.SUBDOMAIN_FOR_ROOT_DOMAIN, 'is_signup': '1'}
|
2017-08-08 11:21:31 +02:00
|
|
|
self.backend.strategy.session_get = lambda k: session_data.get(k)
|
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def do_auth(*args: Any, **kwargs: Any) -> None:
|
2017-08-08 11:21:31 +02:00
|
|
|
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 = self.nonreg_email('newuser')
|
|
|
|
name = "Ghost"
|
|
|
|
response = dict(email=email, name=name)
|
|
|
|
result = self.backend.do_auth(response=response)
|
|
|
|
confirmation = Confirmation.objects.all().first()
|
|
|
|
confirmation_key = confirmation.confirmation_key
|
|
|
|
self.assertIn('do_confirm/' + confirmation_key, result.url)
|
|
|
|
result = self.client_get(result.url)
|
|
|
|
self.assert_in_response('action="/accounts/register/"', result)
|
|
|
|
data = {"from_confirmation": "1",
|
|
|
|
"full_name": name,
|
|
|
|
"key": confirmation_key}
|
|
|
|
result = self.client_post('/accounts/register/', data)
|
|
|
|
self.assert_in_response("You're almost there", result)
|
2017-08-09 22:09:38 +02:00
|
|
|
# Verify that the user is asked for name but not password
|
|
|
|
self.assert_not_in_success_response(['id_password'], result)
|
|
|
|
self.assert_in_success_response(['id_full_name'], result)
|
2017-08-08 11:21:31 +02:00
|
|
|
|
|
|
|
result = self.client_post(
|
|
|
|
'/accounts/register/',
|
|
|
|
{'full_name': name,
|
|
|
|
'key': confirmation_key,
|
|
|
|
'terms': True})
|
|
|
|
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
user_profile = self.nonreg_user('newuser')
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_existing_user(self) -> None:
|
2017-04-20 08:25:15 +02:00
|
|
|
rf = RequestFactory()
|
|
|
|
request = rf.get('/complete')
|
|
|
|
request.session = {}
|
|
|
|
request.user = self.user_profile
|
|
|
|
self.backend.strategy.request = request
|
2017-10-26 02:48:21 +02:00
|
|
|
session_data = {'subdomain': Realm.SUBDOMAIN_FOR_ROOT_DOMAIN, 'is_signup': '1'}
|
2017-04-20 08:25:15 +02:00
|
|
|
self.backend.strategy.session_get = lambda k: session_data.get(k)
|
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def do_auth(*args: Any, **kwargs: Any) -> None:
|
2017-04-20 08:25:15 +02:00
|
|
|
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):
|
2017-05-25 01:40:26 +02:00
|
|
|
email = self.example_email("hamlet")
|
2017-04-20 08:25:15 +02:00
|
|
|
response = dict(email=email, name='Hamlet')
|
|
|
|
result = self.backend.do_auth(response=response)
|
|
|
|
self.assert_in_response('action="/register/"', result)
|
2017-08-25 07:12:26 +02:00
|
|
|
self.assert_in_response('hamlet@zulip.com already has an account',
|
2017-04-20 08:25:15 +02:00
|
|
|
result)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_backend_new_user_when_is_signup_is_false(self) -> None:
|
2017-04-20 08:25:15 +02:00
|
|
|
rf = RequestFactory()
|
|
|
|
request = rf.get('/complete')
|
|
|
|
request.session = {}
|
|
|
|
request.user = self.user_profile
|
|
|
|
self.backend.strategy.request = request
|
2017-10-26 02:48:21 +02:00
|
|
|
session_data = {'subdomain': Realm.SUBDOMAIN_FOR_ROOT_DOMAIN, 'is_signup': '0'}
|
2017-04-20 08:25:15 +02:00
|
|
|
self.backend.strategy.session_get = lambda k: session_data.get(k)
|
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def do_auth(*args: Any, **kwargs: Any) -> None:
|
2017-04-20 08:25:15 +02:00
|
|
|
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)
|
2017-07-17 17:31:05 +02:00
|
|
|
self.assert_in_response('No account found for',
|
2017-05-13 21:40:23 +02:00
|
|
|
result)
|
2017-07-17 17:31:05 +02:00
|
|
|
self.assert_in_response('nonexisting@phantom.com. Would you like to register instead?',
|
2017-04-20 08:25:15 +02:00
|
|
|
result)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_url(self) -> None:
|
2016-12-01 13:10:59 +01:00
|
|
|
result = self.client_get('/accounts/login/social/github')
|
|
|
|
self.assertIn(reverse('social:begin', args=['github']), result.url)
|
2017-04-18 11:50:44 +02:00
|
|
|
self.assertIn('is_signup=0', result.url)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_signup_url(self) -> None:
|
2017-04-18 11:50:44 +02:00
|
|
|
result = self.client_get('/accounts/register/social/github')
|
|
|
|
self.assertIn(reverse('social:begin', args=['github']), result.url)
|
|
|
|
self.assertIn('is_signup=1', result.url)
|
2016-12-01 13:10:59 +01:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_complete(self) -> None:
|
2017-02-28 11:58:03 +01:00
|
|
|
from social_django import utils
|
|
|
|
utils.BACKENDS = ('zproject.backends.GitHubAuthBackend',)
|
|
|
|
with mock.patch('social_core.backends.oauth.BaseOAuth2.process_error',
|
|
|
|
side_effect=AuthFailed('Not found')):
|
|
|
|
result = self.client_get(reverse('social:complete', args=['github']))
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertIn('login', result.url)
|
|
|
|
|
|
|
|
utils.BACKENDS = settings.AUTHENTICATION_BACKENDS
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_github_complete_when_base_exc_is_raised(self) -> None:
|
2017-03-07 08:32:40 +01:00
|
|
|
from social_django import utils
|
|
|
|
utils.BACKENDS = ('zproject.backends.GitHubAuthBackend',)
|
|
|
|
with mock.patch('social_core.backends.oauth.BaseOAuth2.auth_complete',
|
|
|
|
side_effect=AuthStateForbidden('State forbidden')), \
|
2017-10-13 08:26:50 +02:00
|
|
|
mock.patch('zproject.backends.logging.warning'):
|
2017-03-07 08:32:40 +01:00
|
|
|
result = self.client_get(reverse('social:complete', args=['github']))
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertIn('login', result.url)
|
|
|
|
|
|
|
|
utils.BACKENDS = settings.AUTHENTICATION_BACKENDS
|
|
|
|
|
2017-11-05 11:49:43 +01:00
|
|
|
class ResponseMock:
|
2017-11-19 04:02:03 +01:00
|
|
|
def __init__(self, status_code: int, data: Any) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
self.status_code = status_code
|
|
|
|
self.data = data
|
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def json(self) -> str:
|
2016-09-13 21:30:18 +02:00
|
|
|
return self.data
|
|
|
|
|
|
|
|
@property
|
2017-11-19 04:02:03 +01:00
|
|
|
def text(self) -> str:
|
2016-09-13 21:30:18 +02:00
|
|
|
return "Response text"
|
|
|
|
|
2016-10-17 14:28:23 +02:00
|
|
|
class GoogleOAuthTest(ZulipTestCase):
|
2017-11-20 03:22:57 +01:00
|
|
|
def google_oauth2_test(self, token_response: ResponseMock, account_response: ResponseMock,
|
|
|
|
*, subdomain: Optional[str]=None,
|
|
|
|
mobile_flow_otp: Optional[str]=None,
|
|
|
|
is_signup: Optional[str]=None) -> HttpResponse:
|
2017-04-28 01:18:57 +02:00
|
|
|
url = "/accounts/login/google/"
|
2017-04-28 00:22:58 +02:00
|
|
|
params = {}
|
2017-04-28 01:18:57 +02:00
|
|
|
headers = {}
|
2016-10-17 14:28:23 +02:00
|
|
|
if subdomain is not None:
|
2017-04-28 01:18:57 +02:00
|
|
|
headers['HTTP_HOST'] = subdomain + ".testserver"
|
2017-03-19 20:01:01 +01:00
|
|
|
if mobile_flow_otp is not None:
|
|
|
|
params['mobile_flow_otp'] = mobile_flow_otp
|
2017-06-16 06:50:48 +02:00
|
|
|
headers['HTTP_USER_AGENT'] = "ZulipAndroid"
|
2017-08-04 09:19:32 +02:00
|
|
|
if is_signup is not None:
|
|
|
|
params['is_signup'] = is_signup
|
2017-04-28 00:22:58 +02:00
|
|
|
if len(params) > 0:
|
|
|
|
url += "?%s" % (urllib.parse.urlencode(params))
|
2016-10-17 14:28:23 +02:00
|
|
|
|
2017-04-28 01:18:57 +02:00
|
|
|
result = self.client_get(url, **headers)
|
2017-03-19 20:01:01 +01:00
|
|
|
if result.status_code != 302 or '/accounts/login/google/send/' not in result.url:
|
2017-04-28 01:18:57 +02:00
|
|
|
return result
|
|
|
|
|
|
|
|
# Now do the /google/send/ request
|
2017-06-16 06:49:25 +02:00
|
|
|
result = self.client_get(result.url, **headers)
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 302)
|
2016-10-17 14:28:23 +02:00
|
|
|
if 'google' not in result.url:
|
|
|
|
return result
|
|
|
|
|
2016-11-07 11:16:40 +01:00
|
|
|
self.client.cookies = result.cookies
|
2016-09-13 21:30:18 +02:00
|
|
|
# Now extract the CSRF token from the redirect URL
|
2016-09-14 03:12:39 +02:00
|
|
|
parsed_url = urllib.parse.urlparse(result.url)
|
|
|
|
csrf_state = urllib.parse.parse_qs(parsed_url.query)['state']
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2016-11-30 14:17:35 +01:00
|
|
|
with mock.patch("requests.post", return_value=token_response), (
|
2017-01-24 07:06:13 +01:00
|
|
|
mock.patch("requests.get", return_value=account_response)):
|
2016-09-13 21:30:18 +02:00
|
|
|
result = self.client_get("/accounts/login/google/done/",
|
2017-06-16 06:49:25 +02:00
|
|
|
dict(state=csrf_state), **headers)
|
2016-09-13 21:30:18 +02:00
|
|
|
return result
|
|
|
|
|
2016-10-17 14:28:23 +02:00
|
|
|
class GoogleSubdomainLoginTest(GoogleOAuthTest):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_start(self) -> None:
|
2017-09-27 07:01:41 +02:00
|
|
|
result = self.client_get('/accounts/login/google/', subdomain="zulip")
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
parsed_url = urllib.parse.urlparse(result.url)
|
|
|
|
subdomain = urllib.parse.parse_qs(parsed_url.query)['subdomain']
|
|
|
|
self.assertEqual(subdomain, ['zulip'])
|
2016-10-17 14:28:23 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_success(self) -> None:
|
2016-10-17 14:28:23 +02:00
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
2017-05-25 01:40:26 +02:00
|
|
|
value=self.example_email("hamlet"))])
|
2016-10-17 14:28:23 +02:00
|
|
|
account_response = ResponseMock(200, account_data)
|
2017-09-27 07:01:41 +02:00
|
|
|
result = self.google_oauth2_test(token_response, account_response, subdomain='zulip')
|
2016-10-17 14:28:23 +02:00
|
|
|
|
2017-10-27 02:45:38 +02:00
|
|
|
data = load_subdomain_token(result)
|
2017-05-25 01:40:26 +02:00
|
|
|
self.assertEqual(data['email'], self.example_email("hamlet"))
|
2016-10-17 14:28:23 +02:00
|
|
|
self.assertEqual(data['name'], 'Full Name')
|
|
|
|
self.assertEqual(data['subdomain'], 'zulip')
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 302)
|
2016-10-17 14:28:23 +02:00
|
|
|
parsed_url = urllib.parse.urlparse(result.url)
|
|
|
|
uri = "{}://{}{}".format(parsed_url.scheme, parsed_url.netloc,
|
|
|
|
parsed_url.path)
|
2017-10-27 02:45:38 +02:00
|
|
|
self.assertTrue(uri.startswith('http://zulip.testserver/accounts/login/subdomain/'))
|
2016-10-17 14:28:23 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_no_fullname(self) -> None:
|
2017-09-16 19:34:59 +02:00
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(givenName="Test", familyName="User"),
|
|
|
|
emails=[dict(type="account",
|
|
|
|
value=self.example_email("hamlet"))])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
result = self.google_oauth2_test(token_response, account_response, subdomain='zulip')
|
|
|
|
|
2017-10-27 02:45:38 +02:00
|
|
|
data = load_subdomain_token(result)
|
2017-09-16 19:34:59 +02:00
|
|
|
self.assertEqual(data['email'], self.example_email("hamlet"))
|
|
|
|
self.assertEqual(data['name'], 'Test User')
|
|
|
|
self.assertEqual(data['subdomain'], 'zulip')
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
parsed_url = urllib.parse.urlparse(result.url)
|
|
|
|
uri = "{}://{}{}".format(parsed_url.scheme, parsed_url.netloc,
|
|
|
|
parsed_url.path)
|
2017-10-27 02:45:38 +02:00
|
|
|
self.assertTrue(uri.startswith('http://zulip.testserver/accounts/login/subdomain/'))
|
2017-09-16 19:34:59 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_mobile_success(self) -> None:
|
2017-03-19 20:01:01 +01:00
|
|
|
mobile_flow_otp = '1234abcd' * 8
|
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
2017-05-25 01:40:26 +02:00
|
|
|
value=self.example_email("hamlet"))])
|
2017-03-19 20:01:01 +01:00
|
|
|
account_response = ResponseMock(200, account_data)
|
2017-06-16 06:50:48 +02:00
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
2017-09-27 07:01:41 +02:00
|
|
|
with self.settings(SEND_LOGIN_EMAILS=True):
|
2017-03-19 20:01:01 +01:00
|
|
|
# Verify that the right thing happens with an invalid-format OTP
|
2017-08-24 05:44:51 +02:00
|
|
|
result = self.google_oauth2_test(token_response, account_response, subdomain='zulip',
|
2017-03-19 20:01:01 +01:00
|
|
|
mobile_flow_otp="1234")
|
|
|
|
self.assert_json_error(result, "Invalid OTP")
|
2017-08-24 05:44:51 +02:00
|
|
|
result = self.google_oauth2_test(token_response, account_response, subdomain='zulip',
|
2017-03-19 20:01:01 +01:00
|
|
|
mobile_flow_otp="invalido" * 8)
|
|
|
|
self.assert_json_error(result, "Invalid OTP")
|
|
|
|
|
|
|
|
# Now do it correctly
|
2017-08-24 05:44:51 +02:00
|
|
|
result = self.google_oauth2_test(token_response, account_response, subdomain='zulip',
|
2017-03-19 20:01:01 +01:00
|
|
|
mobile_flow_otp=mobile_flow_otp)
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
redirect_url = result['Location']
|
|
|
|
parsed_url = urllib.parse.urlparse(redirect_url)
|
|
|
|
query_params = urllib.parse.parse_qs(parsed_url.query)
|
|
|
|
self.assertEqual(parsed_url.scheme, 'zulip')
|
|
|
|
self.assertEqual(query_params["realm"], ['http://zulip.testserver'])
|
2017-05-25 01:40:26 +02:00
|
|
|
self.assertEqual(query_params["email"], [self.example_email("hamlet")])
|
2017-03-19 20:01:01 +01:00
|
|
|
encrypted_api_key = query_params["otp_encrypted_api_key"][0]
|
2017-05-07 17:21:26 +02:00
|
|
|
self.assertEqual(self.example_user('hamlet').api_key,
|
2017-03-19 20:01:01 +01:00
|
|
|
otp_decrypt_api_key(encrypted_api_key, mobile_flow_otp))
|
2017-06-16 06:50:48 +02:00
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
self.assertIn('Zulip on Android', mail.outbox[0].body)
|
2017-03-19 20:01:01 +01:00
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def get_log_into_subdomain(self, data: Dict[str, Any], *, key: Optional[str]=None, subdomain: str='zulip') -> HttpResponse:
|
2017-10-27 02:45:38 +02:00
|
|
|
token = signing.dumps(data, salt=_subdomain_token_salt, key=key)
|
|
|
|
url_path = reverse('zerver.views.auth.log_into_subdomain', args=[token])
|
|
|
|
return self.client_get(url_path, subdomain=subdomain)
|
2017-10-27 02:41:54 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_log_into_subdomain(self) -> None:
|
2016-10-17 14:28:23 +02:00
|
|
|
data = {'name': 'Full Name',
|
2017-05-25 01:40:26 +02:00
|
|
|
'email': self.example_email("hamlet"),
|
2017-04-20 08:25:15 +02:00
|
|
|
'subdomain': 'zulip',
|
|
|
|
'is_signup': False}
|
2017-10-27 02:41:54 +02:00
|
|
|
result = self.get_log_into_subdomain(data)
|
2017-09-27 07:01:41 +02:00
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
|
|
|
with mock.patch(
|
|
|
|
'zerver.views.auth.authenticate_remote_user',
|
|
|
|
return_value=(None, {'invalid_subdomain': True})):
|
2017-10-27 02:41:54 +02:00
|
|
|
result = self.get_log_into_subdomain(data)
|
auth: Try switching to register even if user exists on another realm.
For example, this means that if a user already has an account on one
realm and they try to make an account on another by hitting "Sign in
with Google" (rather than following the little "Register" link to a
"Sign up with Google" button instead), they'll get to make an account
instead of getting an error.
Until very recently, if the user existed on another realm, any attempt
to register with that email address had to fail in the end, so this
logic gave the user a useful error message early. We introduced it in
c23aaa178 "GitHub: Show error on login page for wrong subdomain"
back in 2016-10 for that purpose. No longer! We now support reusing
an email on multiple realms, so we let the user proceed instead.
This function's interface is kind of confusing, but I believe when its
callers use it properly, `invalid_subdomain` should only ever be true
when `user_profile` is None -- in which case the revised
`invalid_subdomain` condition in this commit can never actually fire,
and the `invalid_subdomain` parameter no longer has any effect. (At
least some unit tests call this function improperly in that respect.)
I've kept this commit to a minimal change, but it would be a good
followup to go through the call sites, verify that, eliminate the use
of `invalid_subdomain`, then remove it from the function entirely.
2017-11-28 06:26:41 +01:00
|
|
|
self.assert_in_success_response(['Would you like to register instead?'],
|
|
|
|
result)
|
2017-03-25 20:44:14 +01:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_log_into_subdomain_when_signature_is_bad(self) -> None:
|
2017-10-27 02:41:54 +02:00
|
|
|
data = {'name': 'Full Name',
|
|
|
|
'email': self.example_email("hamlet"),
|
|
|
|
'subdomain': 'zulip',
|
|
|
|
'is_signup': False}
|
2017-10-28 00:54:46 +02:00
|
|
|
with mock.patch('logging.warning') as mock_warning:
|
|
|
|
result = self.get_log_into_subdomain(data, key='nonsense')
|
|
|
|
mock_warning.assert_called_with("Subdomain cookie: Bad signature.")
|
2017-10-27 02:45:38 +02:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_log_into_subdomain_when_signature_is_expired(self) -> None:
|
2017-10-27 02:45:38 +02:00
|
|
|
data = {'name': 'Full Name',
|
|
|
|
'email': self.example_email("hamlet"),
|
|
|
|
'subdomain': 'zulip',
|
|
|
|
'is_signup': False}
|
|
|
|
with mock.patch('django.core.signing.time.time', return_value=time.time() - 45):
|
|
|
|
token = signing.dumps(data, salt=_subdomain_token_salt)
|
|
|
|
url_path = reverse('zerver.views.auth.log_into_subdomain', args=[token])
|
2017-10-28 00:54:46 +02:00
|
|
|
with mock.patch('logging.warning') as mock_warning:
|
|
|
|
result = self.client_get(url_path, subdomain='zulip')
|
|
|
|
mock_warning.assert_called_once()
|
2017-10-27 02:41:54 +02:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_log_into_subdomain_when_is_signup_is_true(self) -> None:
|
2017-04-20 08:25:15 +02:00
|
|
|
data = {'name': 'Full Name',
|
2017-05-25 01:40:26 +02:00
|
|
|
'email': self.example_email("hamlet"),
|
2017-04-20 08:25:15 +02:00
|
|
|
'subdomain': 'zulip',
|
|
|
|
'is_signup': True}
|
2017-10-27 02:41:54 +02:00
|
|
|
result = self.get_log_into_subdomain(data)
|
2017-09-27 07:01:41 +02:00
|
|
|
self.assertEqual(result.status_code, 200)
|
|
|
|
self.assert_in_response('hamlet@zulip.com already has an account', result)
|
2017-04-20 08:25:15 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_log_into_subdomain_when_is_signup_is_true_and_new_user(
|
|
|
|
self) -> None:
|
2017-04-20 08:25:15 +02:00
|
|
|
data = {'name': 'New User Name',
|
|
|
|
'email': 'new@zulip.com',
|
|
|
|
'subdomain': 'zulip',
|
|
|
|
'is_signup': True}
|
2017-10-27 02:41:54 +02:00
|
|
|
result = self.get_log_into_subdomain(data)
|
2017-09-27 07:01:41 +02:00
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
confirmation = Confirmation.objects.all().first()
|
|
|
|
confirmation_key = confirmation.confirmation_key
|
|
|
|
self.assertIn('do_confirm/' + confirmation_key, result.url)
|
|
|
|
result = self.client_get(result.url)
|
|
|
|
self.assert_in_response('action="/accounts/register/"', result)
|
|
|
|
data = {"from_confirmation": "1",
|
|
|
|
"full_name": data['name'],
|
|
|
|
"key": confirmation_key}
|
|
|
|
result = self.client_post('/accounts/register/', data, subdomain="zulip")
|
|
|
|
self.assert_in_response("You're almost there", result)
|
|
|
|
|
|
|
|
# Verify that the user is asked for name but not password
|
|
|
|
self.assert_not_in_success_response(['id_password'], result)
|
|
|
|
self.assert_in_success_response(['id_full_name'], result)
|
2017-08-09 22:09:38 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_log_into_subdomain_when_using_invite_link(self) -> None:
|
2017-09-27 03:34:58 +02:00
|
|
|
data = {'name': 'New User Name',
|
|
|
|
'email': 'new@zulip.com',
|
|
|
|
'subdomain': 'zulip',
|
|
|
|
'is_signup': True}
|
|
|
|
|
|
|
|
realm = get_realm("zulip")
|
|
|
|
realm.invite_required = True
|
|
|
|
realm.save()
|
|
|
|
|
|
|
|
stream_names = ["new_stream_1", "new_stream_2"]
|
|
|
|
streams = []
|
|
|
|
for stream_name in set(stream_names):
|
|
|
|
stream, _ = create_stream_if_needed(realm, stream_name)
|
|
|
|
streams.append(stream)
|
|
|
|
|
|
|
|
# Without the invite link, we can't create an account due to invite_required
|
2017-10-27 02:41:54 +02:00
|
|
|
result = self.get_log_into_subdomain(data)
|
2017-09-27 03:34:58 +02:00
|
|
|
self.assertEqual(result.status_code, 200)
|
|
|
|
self.assert_in_success_response(['Sign up for Zulip'], result)
|
|
|
|
|
|
|
|
# Now confirm an invitation link works
|
|
|
|
referrer = self.example_user("hamlet")
|
|
|
|
multiuse_obj = MultiuseInvite.objects.create(realm=realm, referred_by=referrer)
|
2018-01-31 08:22:07 +01:00
|
|
|
multiuse_obj.streams.set(streams)
|
2017-09-27 03:34:58 +02:00
|
|
|
invite_link = create_confirmation_link(multiuse_obj, realm.host,
|
|
|
|
Confirmation.MULTIUSE_INVITE)
|
|
|
|
|
|
|
|
result = self.client_get(invite_link, subdomain="zulip")
|
|
|
|
self.assert_in_success_response(['Sign up for Zulip'], result)
|
|
|
|
|
2017-10-27 02:45:38 +02:00
|
|
|
result = self.get_log_into_subdomain(data)
|
2017-09-27 03:34:58 +02:00
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
|
|
|
|
confirmation = Confirmation.objects.all().last()
|
|
|
|
confirmation_key = confirmation.confirmation_key
|
|
|
|
self.assertIn('do_confirm/' + confirmation_key, result.url)
|
|
|
|
result = self.client_get(result.url)
|
|
|
|
self.assert_in_response('action="/accounts/register/"', result)
|
|
|
|
data2 = {"from_confirmation": "1",
|
|
|
|
"full_name": data['name'],
|
|
|
|
"key": confirmation_key}
|
|
|
|
result = self.client_post('/accounts/register/', data2, subdomain="zulip")
|
|
|
|
self.assert_in_response("You're almost there", result)
|
|
|
|
|
|
|
|
# Verify that the user is asked for name but not password
|
|
|
|
self.assert_not_in_success_response(['id_password'], result)
|
|
|
|
self.assert_in_success_response(['id_full_name'], result)
|
|
|
|
|
|
|
|
# Click confirm registration button.
|
|
|
|
result = self.client_post(
|
|
|
|
'/accounts/register/',
|
|
|
|
{'full_name': 'New User Name',
|
|
|
|
'key': confirmation_key,
|
|
|
|
'terms': True})
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertEqual(sorted(self.get_streams('new@zulip.com', realm)), stream_names)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_log_into_subdomain_when_email_is_none(self) -> None:
|
2017-04-18 08:34:29 +02:00
|
|
|
data = {'name': None,
|
|
|
|
'email': None,
|
2017-04-20 08:25:15 +02:00
|
|
|
'subdomain': 'zulip',
|
|
|
|
'is_signup': False}
|
2017-04-18 08:34:29 +02:00
|
|
|
|
2017-09-27 07:01:41 +02:00
|
|
|
with mock.patch('logging.warning'):
|
2017-10-27 02:41:54 +02:00
|
|
|
result = self.get_log_into_subdomain(data)
|
2017-04-18 08:34:29 +02:00
|
|
|
self.assertEqual(result.status_code, 200)
|
2017-04-20 08:25:15 +02:00
|
|
|
self.assert_in_response("Please click the following button if you "
|
|
|
|
"wish to register", result)
|
2017-04-18 08:34:29 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_user_cannot_log_into_nonexisting_realm(self) -> None:
|
2016-10-17 14:28:23 +02:00
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
2017-05-25 01:40:26 +02:00
|
|
|
value=self.example_email("hamlet"))])
|
2016-10-17 14:28:23 +02:00
|
|
|
account_response = ResponseMock(200, account_data)
|
2017-09-16 19:34:59 +02:00
|
|
|
result = self.google_oauth2_test(token_response, account_response,
|
|
|
|
subdomain='nonexistent')
|
|
|
|
self.assert_in_success_response(["There is no Zulip organization hosted at this subdomain."],
|
|
|
|
result)
|
2016-10-17 14:28:23 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_user_cannot_log_into_wrong_subdomain(self) -> None:
|
2017-09-16 19:34:59 +02:00
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
|
|
|
value=self.example_email("hamlet"))])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
result = self.google_oauth2_test(token_response, account_response,
|
|
|
|
subdomain='zephyr')
|
|
|
|
self.assertEqual(result.status_code, 302)
|
2017-10-27 02:45:38 +02:00
|
|
|
self.assertTrue(result.url.startswith("http://zephyr.testserver/accounts/login/subdomain/"))
|
|
|
|
result = self.client_get(result.url.replace('http://zephyr.testserver', ''),
|
|
|
|
subdomain="zephyr")
|
auth: Try switching to register even if user exists on another realm.
For example, this means that if a user already has an account on one
realm and they try to make an account on another by hitting "Sign in
with Google" (rather than following the little "Register" link to a
"Sign up with Google" button instead), they'll get to make an account
instead of getting an error.
Until very recently, if the user existed on another realm, any attempt
to register with that email address had to fail in the end, so this
logic gave the user a useful error message early. We introduced it in
c23aaa178 "GitHub: Show error on login page for wrong subdomain"
back in 2016-10 for that purpose. No longer! We now support reusing
an email on multiple realms, so we let the user proceed instead.
This function's interface is kind of confusing, but I believe when its
callers use it properly, `invalid_subdomain` should only ever be true
when `user_profile` is None -- in which case the revised
`invalid_subdomain` condition in this commit can never actually fire,
and the `invalid_subdomain` parameter no longer has any effect. (At
least some unit tests call this function improperly in that respect.)
I've kept this commit to a minimal change, but it would be a good
followup to go through the call sites, verify that, eliminate the use
of `invalid_subdomain`, then remove it from the function entirely.
2017-11-28 06:26:41 +01:00
|
|
|
self.assert_in_success_response(['Would you like to register instead?'], result)
|
2017-09-16 19:34:59 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_user_cannot_log_into_wrong_subdomain_with_cookie(self) -> None:
|
2016-10-17 14:28:23 +02:00
|
|
|
data = {'name': 'Full Name',
|
2017-05-25 01:40:26 +02:00
|
|
|
'email': self.example_email("hamlet"),
|
2017-09-16 19:34:59 +02:00
|
|
|
'subdomain': 'zephyr'}
|
2017-10-28 00:54:46 +02:00
|
|
|
with mock.patch('logging.warning') as mock_warning:
|
|
|
|
result = self.get_log_into_subdomain(data)
|
|
|
|
mock_warning.assert_called_with("Login attempt on invalid subdomain")
|
2017-09-27 07:01:41 +02:00
|
|
|
self.assertEqual(result.status_code, 400)
|
2016-10-17 14:28:23 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_registration(self) -> None:
|
2016-11-01 08:03:10 +01:00
|
|
|
"""If the user doesn't exist yet, Google auth can be used to register an account"""
|
2017-09-27 07:01:41 +02:00
|
|
|
email = "newuser@zulip.com"
|
|
|
|
realm = get_realm("zulip")
|
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
|
|
|
value=email)])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
result = self.google_oauth2_test(token_response, account_response, subdomain='zulip',
|
|
|
|
is_signup='1')
|
2017-08-04 09:19:32 +02:00
|
|
|
|
2017-10-27 02:45:38 +02:00
|
|
|
data = load_subdomain_token(result)
|
2017-09-27 07:01:41 +02:00
|
|
|
name = 'Full Name'
|
|
|
|
self.assertEqual(data['email'], email)
|
|
|
|
self.assertEqual(data['name'], name)
|
|
|
|
self.assertEqual(data['subdomain'], 'zulip')
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
parsed_url = urllib.parse.urlparse(result.url)
|
|
|
|
uri = "{}://{}{}".format(parsed_url.scheme, parsed_url.netloc,
|
|
|
|
parsed_url.path)
|
2017-10-27 02:45:38 +02:00
|
|
|
self.assertTrue(uri.startswith('http://zulip.testserver/accounts/login/subdomain/'))
|
2017-08-09 22:09:38 +02:00
|
|
|
|
2017-09-27 07:01:41 +02:00
|
|
|
result = self.client_get(result.url)
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
confirmation = Confirmation.objects.all().first()
|
|
|
|
confirmation_key = confirmation.confirmation_key
|
|
|
|
self.assertIn('do_confirm/' + confirmation_key, result.url)
|
|
|
|
result = self.client_get(result.url)
|
|
|
|
self.assert_in_response('action="/accounts/register/"', result)
|
|
|
|
data = {"from_confirmation": "1",
|
|
|
|
"full_name": name,
|
|
|
|
"key": confirmation_key}
|
|
|
|
result = self.client_post('/accounts/register/', data)
|
|
|
|
self.assert_in_response("You're almost there", result)
|
|
|
|
|
|
|
|
# Verify that the user is asked for name but not password
|
|
|
|
self.assert_not_in_success_response(['id_password'], result)
|
|
|
|
self.assert_in_success_response(['id_full_name'], result)
|
|
|
|
|
|
|
|
# Click confirm registration button.
|
|
|
|
result = self.client_post(
|
|
|
|
'/accounts/register/',
|
|
|
|
{'full_name': name,
|
|
|
|
'key': confirmation_key,
|
|
|
|
'terms': True})
|
2017-08-04 09:19:32 +02:00
|
|
|
|
2017-09-27 07:01:41 +02:00
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
user_profile = get_user(email, realm)
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
2016-11-01 08:03:10 +01:00
|
|
|
|
2016-10-17 14:28:23 +02:00
|
|
|
class GoogleLoginTest(GoogleOAuthTest):
|
2017-10-03 01:31:20 +02:00
|
|
|
@override_settings(ROOT_DOMAIN_LANDING_PAGE=True)
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_subdomains_homepage(self) -> None:
|
2016-10-07 11:16:49 +02:00
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
2017-05-25 01:40:26 +02:00
|
|
|
value=self.example_email("hamlet"))])
|
2016-10-07 11:16:49 +02:00
|
|
|
account_response = ResponseMock(200, account_data)
|
2017-10-03 01:31:20 +02:00
|
|
|
result = self.google_oauth2_test(token_response, account_response, subdomain="")
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertIn('subdomain=1', result.url)
|
2016-10-07 11:16:49 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_400_token_response(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
token_response = ResponseMock(400, {})
|
|
|
|
with mock.patch("logging.warning") as m:
|
2017-05-24 04:21:29 +02:00
|
|
|
result = self.google_oauth2_test(token_response, ResponseMock(500, {}))
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
self.assertEqual(m.call_args_list[0][0][0],
|
2016-12-16 05:51:27 +01:00
|
|
|
"User error converting Google oauth2 login to token: Response text")
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_500_token_response(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
token_response = ResponseMock(500, {})
|
|
|
|
with mock.patch("logging.error") as m:
|
2017-05-24 04:21:29 +02:00
|
|
|
result = self.google_oauth2_test(token_response, ResponseMock(500, {}))
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
self.assertEqual(m.call_args_list[0][0][0],
|
2016-12-16 05:51:27 +01:00
|
|
|
"Could not convert google oauth2 code to access_token: Response text")
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_400_account_response(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_response = ResponseMock(400, {})
|
|
|
|
with mock.patch("logging.warning") as m:
|
|
|
|
result = self.google_oauth2_test(token_response, account_response)
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
self.assertEqual(m.call_args_list[0][0][0],
|
2016-12-16 05:51:27 +01:00
|
|
|
"Google login failed making info API call: Response text")
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_500_account_response(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_response = ResponseMock(500, {})
|
|
|
|
with mock.patch("logging.error") as m:
|
|
|
|
result = self.google_oauth2_test(token_response, account_response)
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
self.assertEqual(m.call_args_list[0][0][0],
|
2016-12-16 05:51:27 +01:00
|
|
|
"Google login failed making API call: Response text")
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_account_response_no_email(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
with mock.patch("logging.error") as m:
|
2017-09-16 19:34:59 +02:00
|
|
|
result = self.google_oauth2_test(token_response, account_response,
|
|
|
|
subdomain="zulip")
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
2016-09-14 02:26:32 +02:00
|
|
|
self.assertIn("Google oauth2 account email not found:", m.call_args_list[0][0][0])
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_error_access_denied(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
result = self.client_get("/accounts/login/google/done/?error=access_denied")
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 302)
|
2016-11-07 11:57:45 +01:00
|
|
|
path = urllib.parse.urlparse(result.url).path
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(path, "/")
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_error_other(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
with mock.patch("logging.warning") as m:
|
|
|
|
result = self.client_get("/accounts/login/google/done/?error=some_other_error")
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
self.assertEqual(m.call_args_list[0][0][0],
|
2016-12-16 05:51:27 +01:00
|
|
|
"Error from google oauth2 login: some_other_error")
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_missing_csrf(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
with mock.patch("logging.warning") as m:
|
|
|
|
result = self.client_get("/accounts/login/google/done/")
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
self.assertEqual(m.call_args_list[0][0][0],
|
2016-12-16 05:51:27 +01:00
|
|
|
'Missing Google oauth2 CSRF state')
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_csrf_malformed(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
with mock.patch("logging.warning") as m:
|
|
|
|
result = self.client_get("/accounts/login/google/done/?state=badstate")
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
self.assertEqual(m.call_args_list[0][0][0],
|
2016-12-16 05:51:27 +01:00
|
|
|
'Missing Google oauth2 CSRF state')
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_csrf_badstate(self) -> None:
|
2016-09-13 21:30:18 +02:00
|
|
|
with mock.patch("logging.warning") as m:
|
2017-08-04 09:19:32 +02:00
|
|
|
result = self.client_get("/accounts/login/google/done/?state=badstate:otherbadstate:more::")
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
|
|
|
self.assertEqual(m.call_args_list[0][0][0],
|
2016-12-16 05:51:27 +01:00
|
|
|
'Google oauth2 CSRF error')
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2018-01-11 23:08:53 +01:00
|
|
|
class JSONFetchAPIKeyTest(ZulipTestCase):
|
|
|
|
def setUp(self) -> None:
|
|
|
|
self.user_profile = self.example_user('hamlet')
|
|
|
|
self.email = self.user_profile.email
|
|
|
|
|
|
|
|
def test_success(self) -> None:
|
|
|
|
self.login(self.email)
|
|
|
|
result = self.client_post("/json/fetch_api_key",
|
|
|
|
dict(user_profile=self.user_profile,
|
|
|
|
password=initial_password(self.email)))
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
def test_not_loggedin(self) -> None:
|
|
|
|
result = self.client_post("/json/fetch_api_key",
|
|
|
|
dict(user_profile=self.user_profile,
|
|
|
|
password=initial_password(self.email)))
|
|
|
|
self.assert_json_error(result,
|
|
|
|
"Not logged in: API authentication or user session required", 401)
|
|
|
|
|
|
|
|
def test_wrong_password(self) -> None:
|
|
|
|
self.login(self.email)
|
|
|
|
result = self.client_post("/json/fetch_api_key",
|
|
|
|
dict(user_profile=self.user_profile,
|
|
|
|
password="wrong"))
|
|
|
|
self.assert_json_error(result, "Your username or password is incorrect.", 400)
|
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class FetchAPIKeyTest(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def setUp(self) -> None:
|
2017-05-07 21:25:59 +02:00
|
|
|
self.user_profile = self.example_user('hamlet')
|
|
|
|
self.email = self.user_profile.email
|
2016-04-21 21:07:43 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_success(self) -> None:
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
2016-04-21 21:07:43 +02:00
|
|
|
dict(username=self.email,
|
|
|
|
password=initial_password(self.email)))
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_invalid_email(self) -> None:
|
2017-04-07 08:21:29 +02:00
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
|
|
|
dict(username='hamlet',
|
|
|
|
password=initial_password(self.email)))
|
|
|
|
self.assert_json_error(result, "Enter a valid email address.", 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_wrong_password(self) -> None:
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
2016-04-21 21:07:43 +02:00
|
|
|
dict(username=self.email,
|
|
|
|
password="wrong"))
|
|
|
|
self.assert_json_error(result, "Your username or password is incorrect.", 403)
|
|
|
|
|
2017-06-15 07:15:57 +02:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GoogleMobileOauth2Backend',),
|
|
|
|
SEND_LOGIN_EMAILS=True)
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_token_success(self) -> None:
|
2017-06-15 07:15:57 +02:00
|
|
|
self.assertEqual(len(mail.outbox), 0)
|
2017-03-25 20:44:14 +01:00
|
|
|
with mock.patch(
|
|
|
|
'apiclient.sample_tools.client.verify_id_token',
|
|
|
|
return_value={
|
|
|
|
"email_verified": True,
|
2017-05-25 01:40:26 +02:00
|
|
|
"email": self.example_email("hamlet"),
|
2017-03-25 20:44:14 +01:00
|
|
|
}):
|
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
|
|
|
dict(username="google-oauth2-token",
|
|
|
|
password="token"))
|
|
|
|
self.assert_json_success(result)
|
2017-06-15 07:15:57 +02:00
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
2017-03-25 20:44:14 +01:00
|
|
|
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GoogleMobileOauth2Backend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_token_failure(self) -> None:
|
2017-07-03 13:39:52 +02:00
|
|
|
payload = dict(email_verified=False)
|
|
|
|
with mock.patch('apiclient.sample_tools.client.verify_id_token', return_value=payload):
|
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
|
|
|
dict(username="google-oauth2-token",
|
|
|
|
password="token"))
|
|
|
|
self.assert_json_error(result, "Your username or password is incorrect.", 403)
|
2017-03-25 20:44:14 +01:00
|
|
|
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GoogleMobileOauth2Backend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_google_oauth2_token_unregistered(self) -> None:
|
2017-03-25 20:44:14 +01:00
|
|
|
with mock.patch(
|
|
|
|
'apiclient.sample_tools.client.verify_id_token',
|
|
|
|
return_value={
|
|
|
|
"email_verified": True,
|
|
|
|
"email": "nobody@zulip.com",
|
|
|
|
}):
|
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
|
|
|
dict(username="google-oauth2-token",
|
|
|
|
password="token"))
|
|
|
|
self.assert_json_error(
|
|
|
|
result,
|
|
|
|
"This user is not registered; do so from a browser.",
|
|
|
|
403)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_password_auth_disabled(self) -> None:
|
2016-04-21 21:07:43 +02:00
|
|
|
with mock.patch('zproject.backends.password_auth_enabled', return_value=False):
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
2016-04-21 21:07:43 +02:00
|
|
|
dict(username=self.email,
|
|
|
|
password=initial_password(self.email)))
|
|
|
|
self.assert_json_error_contains(result, "Password auth is disabled", 403)
|
|
|
|
|
2016-11-07 01:41:29 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_ldap_auth_email_auth_disabled_success(self) -> None:
|
2016-11-07 01:41:29 +01:00
|
|
|
ldap_patcher = mock.patch('django_auth_ldap.config.ldap.initialize')
|
|
|
|
self.mock_initialize = ldap_patcher.start()
|
|
|
|
self.mock_ldap = MockLDAP()
|
|
|
|
self.mock_initialize.return_value = self.mock_ldap
|
|
|
|
self.backend = ZulipLDAPAuthBackend()
|
|
|
|
|
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='zulip.com',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
|
|
|
dict(username=self.email,
|
|
|
|
password="testing"))
|
|
|
|
self.assert_json_success(result)
|
|
|
|
self.mock_ldap.reset()
|
|
|
|
self.mock_initialize.stop()
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_inactive_user(self) -> None:
|
2016-04-21 21:07:43 +02:00
|
|
|
do_deactivate_user(self.user_profile)
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
2016-04-21 21:07:43 +02:00
|
|
|
dict(username=self.email,
|
|
|
|
password=initial_password(self.email)))
|
|
|
|
self.assert_json_error_contains(result, "Your account has been disabled", 403)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_deactivated_realm(self) -> None:
|
2016-04-21 21:07:43 +02:00
|
|
|
do_deactivate_realm(self.user_profile.realm)
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
2016-04-21 21:07:43 +02:00
|
|
|
dict(username=self.email,
|
|
|
|
password=initial_password(self.email)))
|
|
|
|
self.assert_json_error_contains(result, "Your realm has been deactivated", 403)
|
2016-06-01 02:28:27 +02:00
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class DevFetchAPIKeyTest(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def setUp(self) -> None:
|
2017-05-07 21:25:59 +02:00
|
|
|
self.user_profile = self.example_user('hamlet')
|
|
|
|
self.email = self.user_profile.email
|
2016-06-01 02:28:27 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_success(self) -> None:
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/api/v1/dev_fetch_api_key",
|
2016-06-01 02:28:27 +02:00
|
|
|
dict(username=self.email))
|
|
|
|
self.assert_json_success(result)
|
2017-08-17 08:28:05 +02:00
|
|
|
data = result.json()
|
2016-06-01 02:28:27 +02:00
|
|
|
self.assertEqual(data["email"], self.email)
|
|
|
|
self.assertEqual(data['api_key'], self.user_profile.api_key)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_invalid_email(self) -> None:
|
2017-04-07 08:21:29 +02:00
|
|
|
email = 'hamlet'
|
|
|
|
result = self.client_post("/api/v1/dev_fetch_api_key",
|
|
|
|
dict(username=email))
|
|
|
|
self.assert_json_error_contains(result, "Enter a valid email address.", 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_unregistered_user(self) -> None:
|
2017-05-22 01:34:21 +02:00
|
|
|
email = 'foo@zulip.com'
|
|
|
|
result = self.client_post("/api/v1/dev_fetch_api_key",
|
|
|
|
dict(username=email))
|
|
|
|
self.assert_json_error_contains(result, "This user is not registered.", 403)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_inactive_user(self) -> None:
|
2016-06-01 02:28:27 +02:00
|
|
|
do_deactivate_user(self.user_profile)
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/api/v1/dev_fetch_api_key",
|
2016-06-01 02:28:27 +02:00
|
|
|
dict(username=self.email))
|
|
|
|
self.assert_json_error_contains(result, "Your account has been disabled", 403)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_deactivated_realm(self) -> None:
|
2016-06-01 02:28:27 +02:00
|
|
|
do_deactivate_realm(self.user_profile.realm)
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/api/v1/dev_fetch_api_key",
|
2016-06-01 02:28:27 +02:00
|
|
|
dict(username=self.email))
|
|
|
|
self.assert_json_error_contains(result, "Your realm has been deactivated", 403)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_dev_auth_disabled(self) -> None:
|
2016-10-12 04:50:38 +02:00
|
|
|
with mock.patch('zerver.views.auth.dev_auth_enabled', return_value=False):
|
2016-07-28 00:30:22 +02:00
|
|
|
result = self.client_post("/api/v1/dev_fetch_api_key",
|
2016-06-01 02:28:27 +02:00
|
|
|
dict(username=self.email))
|
|
|
|
self.assert_json_error_contains(result, "Dev environment not enabled.", 400)
|
2016-06-01 02:28:43 +02:00
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class DevGetEmailsTest(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_success(self) -> None:
|
2016-07-28 00:38:45 +02:00
|
|
|
result = self.client_get("/api/v1/dev_get_emails")
|
2016-06-01 02:28:43 +02:00
|
|
|
self.assert_json_success(result)
|
2016-07-12 15:41:45 +02:00
|
|
|
self.assert_in_response("direct_admins", result)
|
|
|
|
self.assert_in_response("direct_users", result)
|
2016-06-01 02:28:43 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_dev_auth_disabled(self) -> None:
|
2016-10-12 04:50:38 +02:00
|
|
|
with mock.patch('zerver.views.auth.dev_auth_enabled', return_value=False):
|
2016-07-28 00:38:45 +02:00
|
|
|
result = self.client_get("/api/v1/dev_get_emails")
|
2016-06-01 02:28:43 +02:00
|
|
|
self.assert_json_error_contains(result, "Dev environment not enabled.", 400)
|
2016-06-21 03:32:23 +02:00
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class FetchAuthBackends(ZulipTestCase):
|
2017-11-19 04:02:03 +01:00
|
|
|
def assert_on_error(self, error: Optional[str]) -> None:
|
2017-05-04 01:13:56 +02:00
|
|
|
if error:
|
|
|
|
raise AssertionError(error)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_get_server_settings(self) -> None:
|
2018-02-12 23:12:47 +01:00
|
|
|
def check_result(result: HttpResponse, extra_fields: List[Tuple[str, Validator]]=[]) -> None:
|
2017-08-25 23:59:49 +02:00
|
|
|
self.assert_json_success(result)
|
2018-02-12 23:12:47 +01:00
|
|
|
checker = check_dict_only([
|
2017-08-25 23:59:49 +02:00
|
|
|
('authentication_methods', check_dict_only([
|
|
|
|
('google', check_bool),
|
|
|
|
('github', check_bool),
|
2017-10-24 20:59:11 +02:00
|
|
|
('email', check_bool),
|
|
|
|
('ldap', check_bool),
|
2018-02-12 23:12:47 +01:00
|
|
|
('dev', check_bool),
|
2018-02-06 23:36:56 +01:00
|
|
|
('remoteuser', check_bool),
|
2017-08-25 23:59:49 +02:00
|
|
|
('password', check_bool),
|
|
|
|
])),
|
2017-09-15 19:13:48 +02:00
|
|
|
('email_auth_enabled', check_bool),
|
|
|
|
('require_email_format_usernames', check_bool),
|
2017-08-25 23:59:49 +02:00
|
|
|
('realm_uri', check_string),
|
|
|
|
('zulip_version', check_string),
|
2018-02-12 23:34:59 +01:00
|
|
|
('push_notifications_enabled', check_bool),
|
2017-08-25 23:59:49 +02:00
|
|
|
('msg', check_string),
|
|
|
|
('result', check_string),
|
2018-02-12 23:12:47 +01:00
|
|
|
] + extra_fields)
|
|
|
|
self.assert_on_error(checker("data", result.json()))
|
|
|
|
|
|
|
|
result = self.client_get("/api/v1/server_settings", subdomain="")
|
|
|
|
check_result(result)
|
2017-08-25 23:59:49 +02:00
|
|
|
|
2017-10-03 01:31:20 +02:00
|
|
|
with self.settings(ROOT_DOMAIN_LANDING_PAGE=False):
|
2018-02-12 23:12:47 +01:00
|
|
|
result = self.client_get("/api/v1/server_settings", subdomain="")
|
|
|
|
check_result(result)
|
|
|
|
|
|
|
|
with self.settings(ROOT_DOMAIN_LANDING_PAGE=False):
|
|
|
|
result = self.client_get("/api/v1/server_settings", subdomain="zulip")
|
|
|
|
check_result(result, [
|
2017-05-04 01:13:56 +02:00
|
|
|
('realm_name', check_string),
|
|
|
|
('realm_description', check_string),
|
|
|
|
('realm_icon', check_string),
|
|
|
|
])
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_fetch_auth_backend_format(self) -> None:
|
2016-07-28 00:38:45 +02:00
|
|
|
result = self.client_get("/api/v1/get_auth_backends")
|
2016-06-21 03:32:23 +02:00
|
|
|
self.assert_json_success(result)
|
2017-08-17 08:28:05 +02:00
|
|
|
data = result.json()
|
2016-06-21 03:32:23 +02:00
|
|
|
self.assertEqual(set(data.keys()),
|
2017-10-24 20:59:11 +02:00
|
|
|
{'msg', 'password', 'github', 'google', 'email', 'ldap',
|
2018-02-06 23:36:56 +01:00
|
|
|
'dev', 'result', 'remoteuser', 'zulip_version'})
|
2017-02-27 08:30:26 +01:00
|
|
|
for backend in set(data.keys()) - {'msg', 'result', 'zulip_version'}:
|
2016-06-21 03:32:23 +02:00
|
|
|
self.assertTrue(isinstance(data[backend], bool))
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_fetch_auth_backend(self) -> None:
|
2016-06-21 03:32:23 +02:00
|
|
|
backends = [GoogleMobileOauth2Backend(), DevAuthBackend()]
|
|
|
|
with mock.patch('django.contrib.auth.get_backends', return_value=backends):
|
2016-07-28 00:38:45 +02:00
|
|
|
result = self.client_get("/api/v1/get_auth_backends")
|
2016-06-21 03:32:23 +02:00
|
|
|
self.assert_json_success(result)
|
2017-08-17 08:28:05 +02:00
|
|
|
data = result.json()
|
2016-06-21 03:32:23 +02:00
|
|
|
self.assertEqual(data, {
|
|
|
|
'msg': '',
|
|
|
|
'password': False,
|
2017-04-27 23:34:44 +02:00
|
|
|
'github': False,
|
2016-06-21 03:32:23 +02:00
|
|
|
'google': True,
|
|
|
|
'dev': True,
|
2017-10-24 20:59:11 +02:00
|
|
|
'email': False,
|
|
|
|
'ldap': False,
|
2018-02-06 23:36:56 +01:00
|
|
|
'remoteuser': False,
|
2016-06-21 03:32:23 +02:00
|
|
|
'result': 'success',
|
2017-02-27 08:30:26 +01:00
|
|
|
'zulip_version': ZULIP_VERSION,
|
2016-06-21 03:32:23 +02:00
|
|
|
})
|
2016-10-24 08:35:16 +02:00
|
|
|
|
2017-03-10 06:29:09 +01:00
|
|
|
# Test subdomains cases
|
2017-10-03 01:31:20 +02:00
|
|
|
with self.settings(ROOT_DOMAIN_LANDING_PAGE=False):
|
2017-03-10 06:29:09 +01:00
|
|
|
result = self.client_get("/api/v1/get_auth_backends")
|
|
|
|
self.assert_json_success(result)
|
2017-08-17 08:28:05 +02:00
|
|
|
data = result.json()
|
2017-03-10 06:29:09 +01:00
|
|
|
self.assertEqual(data, {
|
|
|
|
'msg': '',
|
|
|
|
'password': False,
|
2017-04-27 23:34:44 +02:00
|
|
|
'github': False,
|
2017-03-10 06:29:09 +01:00
|
|
|
'google': True,
|
2017-10-24 20:59:11 +02:00
|
|
|
'email': False,
|
|
|
|
'ldap': False,
|
2018-02-06 23:36:56 +01:00
|
|
|
'remoteuser': False,
|
2017-03-10 06:29:09 +01:00
|
|
|
'dev': True,
|
|
|
|
'result': 'success',
|
|
|
|
'zulip_version': ZULIP_VERSION,
|
|
|
|
})
|
|
|
|
|
|
|
|
# Verify invalid subdomain
|
|
|
|
result = self.client_get("/api/v1/get_auth_backends",
|
2017-08-25 22:02:00 +02:00
|
|
|
subdomain="invalid")
|
2017-03-10 06:29:09 +01:00
|
|
|
self.assert_json_error_contains(result, "Invalid subdomain", 400)
|
|
|
|
|
|
|
|
# Verify correct behavior with a valid subdomain with
|
|
|
|
# some backends disabled for the realm
|
|
|
|
realm = get_realm("zulip")
|
2017-03-21 18:08:40 +01:00
|
|
|
do_set_realm_authentication_methods(realm, dict(Google=False, Email=False, Dev=True))
|
2017-03-10 06:29:09 +01:00
|
|
|
result = self.client_get("/api/v1/get_auth_backends",
|
2017-08-25 22:02:00 +02:00
|
|
|
subdomain="zulip")
|
2017-03-10 06:29:09 +01:00
|
|
|
self.assert_json_success(result)
|
2017-08-17 08:28:05 +02:00
|
|
|
data = result.json()
|
2017-03-10 06:29:09 +01:00
|
|
|
self.assertEqual(data, {
|
|
|
|
'msg': '',
|
|
|
|
'password': False,
|
2017-04-27 23:34:44 +02:00
|
|
|
'github': False,
|
2017-03-10 06:29:09 +01:00
|
|
|
'google': False,
|
2017-10-24 20:59:11 +02:00
|
|
|
'email': False,
|
|
|
|
'ldap': False,
|
2018-02-06 23:36:56 +01:00
|
|
|
'remoteuser': False,
|
2017-03-10 06:29:09 +01:00
|
|
|
'dev': True,
|
|
|
|
'result': 'success',
|
|
|
|
'zulip_version': ZULIP_VERSION,
|
|
|
|
})
|
2017-10-03 01:31:20 +02:00
|
|
|
with self.settings(ROOT_DOMAIN_LANDING_PAGE=True):
|
2017-08-25 04:32:16 +02:00
|
|
|
# With ROOT_DOMAIN_LANDING_PAGE, homepage fails
|
2017-03-10 06:29:09 +01:00
|
|
|
result = self.client_get("/api/v1/get_auth_backends",
|
2017-08-25 22:02:00 +02:00
|
|
|
subdomain="")
|
2017-03-10 06:29:09 +01:00
|
|
|
self.assert_json_error_contains(result, "Subdomain required", 400)
|
|
|
|
|
2017-08-25 04:32:16 +02:00
|
|
|
# With ROOT_DOMAIN_LANDING_PAGE, subdomain pages succeed
|
2017-03-10 06:29:09 +01:00
|
|
|
result = self.client_get("/api/v1/get_auth_backends",
|
2017-08-25 22:02:00 +02:00
|
|
|
subdomain="zulip")
|
2017-03-10 06:29:09 +01:00
|
|
|
self.assert_json_success(result)
|
2017-08-17 08:28:05 +02:00
|
|
|
data = result.json()
|
2017-03-10 06:29:09 +01:00
|
|
|
self.assertEqual(data, {
|
|
|
|
'msg': '',
|
|
|
|
'password': False,
|
2017-04-27 23:34:44 +02:00
|
|
|
'github': False,
|
2017-03-10 06:29:09 +01:00
|
|
|
'google': False,
|
2017-10-24 20:59:11 +02:00
|
|
|
'email': False,
|
2018-02-06 23:36:56 +01:00
|
|
|
'remoteuser': False,
|
2017-10-24 20:59:11 +02:00
|
|
|
'ldap': False,
|
2017-03-10 06:29:09 +01:00
|
|
|
'dev': True,
|
|
|
|
'result': 'success',
|
|
|
|
'zulip_version': ZULIP_VERSION,
|
|
|
|
})
|
|
|
|
|
2016-10-24 08:35:16 +02:00
|
|
|
class TestDevAuthBackend(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success(self) -> None:
|
2017-05-07 19:39:30 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
email = user_profile.email
|
2016-10-24 08:35:16 +02:00
|
|
|
data = {'direct_email': email}
|
|
|
|
result = self.client_post('/accounts/login/local/', data)
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_with_subdomain(self) -> None:
|
2017-05-07 19:39:30 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
email = user_profile.email
|
2017-03-25 20:44:14 +01:00
|
|
|
data = {'direct_email': email}
|
2017-10-03 01:31:20 +02:00
|
|
|
|
|
|
|
result = self.client_post('/accounts/login/local/', data)
|
2017-03-25 20:44:14 +01:00
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_choose_realm(self) -> None:
|
2017-09-15 22:03:49 +02:00
|
|
|
result = self.client_post('/devlogin/', subdomain="zulip")
|
|
|
|
self.assert_in_success_response(["Click on a user to log in to Zulip Dev!"], result)
|
|
|
|
self.assert_in_success_response(["iago@zulip.com", "hamlet@zulip.com"], result)
|
|
|
|
|
|
|
|
result = self.client_post('/devlogin/', subdomain="")
|
2017-08-15 00:13:58 +02:00
|
|
|
self.assert_in_success_response(["Click on a user to log in!"], result)
|
|
|
|
self.assert_in_success_response(["iago@zulip.com", "hamlet@zulip.com"], result)
|
|
|
|
self.assert_in_success_response(["starnine@mit.edu", "espuser@mit.edu"], result)
|
|
|
|
|
|
|
|
data = {'new_realm': 'zephyr'}
|
2017-09-15 22:03:49 +02:00
|
|
|
result = self.client_post('/devlogin/', data, subdomain="zulip")
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertEqual(result.url, "http://zephyr.testserver")
|
|
|
|
result = self.client_get('/devlogin/', subdomain="zephyr")
|
2017-08-15 00:13:58 +02:00
|
|
|
self.assert_in_success_response(["starnine@mit.edu", "espuser@mit.edu"], result)
|
|
|
|
self.assert_in_success_response(["Click on a user to log in to MIT!"], result)
|
2017-09-15 22:03:49 +02:00
|
|
|
self.assert_not_in_success_response(["iago@zulip.com", "hamlet@zulip.com"], result)
|
2017-08-15 00:13:58 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_choose_realm_with_subdomains_enabled(self) -> None:
|
2017-08-15 00:13:58 +02:00
|
|
|
with mock.patch('zerver.views.auth.is_subdomain_root_or_alias', return_value=False):
|
|
|
|
with mock.patch('zerver.views.auth.get_realm_from_request', return_value=get_realm('zulip')):
|
2017-10-03 01:31:20 +02:00
|
|
|
result = self.client_get("http://zulip.testserver/devlogin/")
|
|
|
|
self.assert_in_success_response(["iago@zulip.com", "hamlet@zulip.com"], result)
|
|
|
|
self.assert_not_in_success_response(["starnine@mit.edu", "espuser@mit.edu"], result)
|
|
|
|
self.assert_in_success_response(["Click on a user to log in to Zulip Dev!"], result)
|
2017-08-15 00:13:58 +02:00
|
|
|
|
|
|
|
with mock.patch('zerver.views.auth.get_realm_from_request', return_value=get_realm('zephyr')):
|
2017-10-03 01:31:20 +02:00
|
|
|
result = self.client_post("http://zulip.testserver/devlogin/", {'new_realm': 'zephyr'})
|
|
|
|
self.assertEqual(result["Location"], "http://zephyr.testserver")
|
2017-08-15 00:13:58 +02:00
|
|
|
|
2017-10-03 01:31:20 +02:00
|
|
|
result = self.client_get("http://zephyr.testserver/devlogin/")
|
|
|
|
self.assert_not_in_success_response(["iago@zulip.com", "hamlet@zulip.com"], result)
|
|
|
|
self.assert_in_success_response(["starnine@mit.edu", "espuser@mit.edu"], result)
|
|
|
|
self.assert_in_success_response(["Click on a user to log in to MIT!"], result)
|
2017-08-15 00:13:58 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure(self) -> None:
|
2017-05-25 01:40:26 +02:00
|
|
|
email = self.example_email("hamlet")
|
2016-10-24 08:35:16 +02:00
|
|
|
data = {'direct_email': email}
|
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.EmailAuthBackend',)):
|
2018-02-21 06:31:53 +01:00
|
|
|
with mock.patch('django.core.handlers.exception.logger'):
|
|
|
|
response = self.client_post('/accounts/login/local/', data)
|
|
|
|
self.assertRedirects(response, reverse('dev_not_supported'))
|
2016-10-24 08:35:16 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_nonexistent_user(self) -> None:
|
2016-10-24 08:35:16 +02:00
|
|
|
email = 'nonexisting@zulip.com'
|
|
|
|
data = {'direct_email': email}
|
2018-02-21 06:31:53 +01:00
|
|
|
with mock.patch('django.core.handlers.exception.logger'):
|
|
|
|
response = self.client_post('/accounts/login/local/', data)
|
|
|
|
self.assertRedirects(response, reverse('dev_not_supported'))
|
2016-10-24 09:09:31 +02:00
|
|
|
|
|
|
|
class TestZulipRemoteUserBackend(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success(self) -> None:
|
2017-05-07 19:39:30 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
email = user_profile.email
|
2016-10-24 09:09:31 +02:00
|
|
|
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(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success_with_sso_append_domain(self) -> None:
|
2016-10-24 09:09:31 +02:00
|
|
|
username = 'hamlet'
|
2017-05-07 19:39:30 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2016-10-24 09:09:31 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',),
|
|
|
|
SSO_APPEND_DOMAIN='zulip.com'):
|
|
|
|
result = self.client_post('/accounts/login/sso/', REMOTE_USER=username)
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure(self) -> None:
|
2017-05-25 01:40:26 +02:00
|
|
|
email = self.example_email("hamlet")
|
2016-10-24 09:09:31 +02:00
|
|
|
result = self.client_post('/accounts/login/sso/', REMOTE_USER=email)
|
2017-06-04 11:36:52 +02:00
|
|
|
self.assertEqual(result.status_code, 200) # This should ideally be not 200.
|
2016-10-24 09:09:31 +02:00
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_nonexisting_user(self) -> None:
|
2016-10-24 09:09:31 +02:00
|
|
|
email = 'nonexisting@zulip.com'
|
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',)):
|
|
|
|
result = self.client_post('/accounts/login/sso/', REMOTE_USER=email)
|
2017-04-20 08:25:15 +02:00
|
|
|
self.assertEqual(result.status_code, 200)
|
2016-10-24 09:09:31 +02:00
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
2017-07-17 17:31:05 +02:00
|
|
|
self.assert_in_response("No account found for", result)
|
2016-10-24 09:09:31 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_invalid_email(self) -> None:
|
2017-04-07 08:21:29 +02:00
|
|
|
email = 'hamlet'
|
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',)):
|
|
|
|
result = self.client_post('/accounts/login/sso/', REMOTE_USER=email)
|
|
|
|
self.assert_json_error_contains(result, "Enter a valid email address.", 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_missing_field(self) -> None:
|
2016-10-24 09:09:31 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',)):
|
|
|
|
result = self.client_post('/accounts/login/sso/')
|
|
|
|
self.assert_json_error_contains(result, "No REMOTE_USER set.", 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_wrong_subdomain(self) -> None:
|
2017-05-25 01:40:26 +02:00
|
|
|
email = self.example_email("hamlet")
|
2017-10-03 01:31:20 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',)):
|
2016-10-24 09:09:31 +02:00
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='acme'):
|
2017-03-05 04:17:12 +01:00
|
|
|
result = self.client_post('http://testserver:9080/accounts/login/sso/',
|
|
|
|
REMOTE_USER=email)
|
2016-10-24 09:09:31 +02:00
|
|
|
self.assertEqual(result.status_code, 200)
|
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
2017-07-17 17:31:05 +02:00
|
|
|
self.assert_in_response("No account found for", result)
|
2016-10-24 09:09:31 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_empty_subdomain(self) -> None:
|
2017-05-25 01:40:26 +02:00
|
|
|
email = self.example_email("hamlet")
|
2017-10-03 01:31:20 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',)):
|
2016-10-24 09:09:31 +02:00
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value=''):
|
2017-03-05 04:17:12 +01:00
|
|
|
result = self.client_post('http://testserver:9080/accounts/login/sso/',
|
|
|
|
REMOTE_USER=email)
|
2016-10-24 09:09:31 +02:00
|
|
|
self.assertEqual(result.status_code, 200)
|
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
2017-07-17 17:31:05 +02:00
|
|
|
self.assert_in_response("No account found for", result)
|
2016-10-24 09:09:31 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success_under_subdomains(self) -> None:
|
2017-05-07 19:39:30 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
email = user_profile.email
|
2016-10-24 09:09:31 +02:00
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
|
|
|
|
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.assertIs(get_session_dict_user(self.client.session), user_profile.id)
|
2016-10-24 11:38:38 +02:00
|
|
|
|
2018-02-06 23:29:57 +01:00
|
|
|
@override_settings(SEND_LOGIN_EMAILS=True)
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',))
|
|
|
|
def test_login_mobile_flow_otp_success(self) -> None:
|
|
|
|
user_profile = self.example_user('hamlet')
|
|
|
|
email = user_profile.email
|
|
|
|
mobile_flow_otp = '1234abcd' * 8
|
|
|
|
# Verify that the right thing happens with an invalid-format OTP
|
|
|
|
|
|
|
|
result = self.client_post('/accounts/login/sso/',
|
|
|
|
dict(mobile_flow_otp="1234"),
|
|
|
|
REMOTE_USER=email,
|
|
|
|
HTTP_USER_AGENT = "ZulipAndroid")
|
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
|
|
|
self.assert_json_error_contains(result, "Invalid OTP", 400)
|
|
|
|
|
|
|
|
result = self.client_post('/accounts/login/sso/',
|
|
|
|
dict(mobile_flow_otp="invalido" * 8),
|
|
|
|
REMOTE_USER=email,
|
|
|
|
HTTP_USER_AGENT = "ZulipAndroid")
|
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
|
|
|
self.assert_json_error_contains(result, "Invalid OTP", 400)
|
|
|
|
|
|
|
|
result = self.client_post('/accounts/login/sso/',
|
|
|
|
dict(mobile_flow_otp=mobile_flow_otp),
|
|
|
|
REMOTE_USER=email,
|
|
|
|
HTTP_USER_AGENT = "ZulipAndroid")
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
redirect_url = result['Location']
|
|
|
|
parsed_url = urllib.parse.urlparse(redirect_url)
|
|
|
|
query_params = urllib.parse.parse_qs(parsed_url.query)
|
|
|
|
self.assertEqual(parsed_url.scheme, 'zulip')
|
|
|
|
self.assertEqual(query_params["realm"], ['http://zulip.testserver'])
|
|
|
|
self.assertEqual(query_params["email"], [self.example_email("hamlet")])
|
|
|
|
encrypted_api_key = query_params["otp_encrypted_api_key"][0]
|
|
|
|
self.assertEqual(self.example_user('hamlet').api_key,
|
|
|
|
otp_decrypt_api_key(encrypted_api_key, mobile_flow_otp))
|
|
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
|
|
self.assertIn('Zulip on Android', mail.outbox[0].body)
|
|
|
|
|
2016-10-24 11:38:38 +02:00
|
|
|
class TestJWTLogin(ZulipTestCase):
|
|
|
|
"""
|
|
|
|
JWT uses ZulipDummyBackend.
|
|
|
|
"""
|
2016-11-29 07:22:02 +01:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success(self) -> None:
|
2016-10-24 11:38:38 +02:00
|
|
|
payload = {'user': 'hamlet', 'realm': 'zulip.com'}
|
2017-08-25 22:02:00 +02:00
|
|
|
with self.settings(JWT_AUTH_KEYS={'zulip': 'key'}):
|
2017-05-25 01:40:26 +02:00
|
|
|
email = self.example_email("hamlet")
|
2017-05-23 20:57:59 +02:00
|
|
|
realm = get_realm('zulip')
|
2017-08-25 22:02:00 +02:00
|
|
|
auth_key = settings.JWT_AUTH_KEYS['zulip']
|
2016-10-24 11:38:38 +02:00
|
|
|
web_token = jwt.encode(payload, auth_key).decode('utf8')
|
|
|
|
|
2017-05-23 20:57:59 +02:00
|
|
|
user_profile = get_user(email, realm)
|
2016-10-24 11:38:38 +02:00
|
|
|
data = {'json_web_token': web_token}
|
|
|
|
result = self.client_post('/accounts/login/jwt/', data)
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_when_user_is_missing(self) -> None:
|
2016-10-24 11:38:38 +02:00
|
|
|
payload = {'realm': 'zulip.com'}
|
2017-08-25 22:02:00 +02:00
|
|
|
with self.settings(JWT_AUTH_KEYS={'zulip': 'key'}):
|
|
|
|
auth_key = settings.JWT_AUTH_KEYS['zulip']
|
2016-10-24 11:38:38 +02:00
|
|
|
web_token = jwt.encode(payload, auth_key).decode('utf8')
|
|
|
|
data = {'json_web_token': web_token}
|
|
|
|
result = self.client_post('/accounts/login/jwt/', data)
|
|
|
|
self.assert_json_error_contains(result, "No user specified in JSON web token claims", 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_when_realm_is_missing(self) -> None:
|
2016-10-24 11:38:38 +02:00
|
|
|
payload = {'user': 'hamlet'}
|
2017-08-25 22:02:00 +02:00
|
|
|
with self.settings(JWT_AUTH_KEYS={'zulip': 'key'}):
|
|
|
|
auth_key = settings.JWT_AUTH_KEYS['zulip']
|
2016-10-24 11:38:38 +02:00
|
|
|
web_token = jwt.encode(payload, auth_key).decode('utf8')
|
|
|
|
data = {'json_web_token': web_token}
|
|
|
|
result = self.client_post('/accounts/login/jwt/', data)
|
|
|
|
self.assert_json_error_contains(result, "No realm specified in JSON web token claims", 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_when_key_does_not_exist(self) -> None:
|
2016-10-24 11:38:38 +02:00
|
|
|
data = {'json_web_token': 'not relevant'}
|
|
|
|
result = self.client_post('/accounts/login/jwt/', data)
|
|
|
|
self.assert_json_error_contains(result, "Auth key for this subdomain not found.", 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_when_key_is_missing(self) -> None:
|
2017-08-25 22:02:00 +02:00
|
|
|
with self.settings(JWT_AUTH_KEYS={'zulip': 'key'}):
|
2016-10-24 11:38:38 +02:00
|
|
|
result = self.client_post('/accounts/login/jwt/')
|
|
|
|
self.assert_json_error_contains(result, "No JSON web token passed in request", 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_when_bad_token_is_passed(self) -> None:
|
2017-08-25 22:02:00 +02:00
|
|
|
with self.settings(JWT_AUTH_KEYS={'zulip': 'key'}):
|
2016-10-24 11:38:38 +02:00
|
|
|
result = self.client_post('/accounts/login/jwt/')
|
|
|
|
self.assert_json_error_contains(result, "No JSON web token passed in request", 400)
|
|
|
|
data = {'json_web_token': 'bad token'}
|
|
|
|
result = self.client_post('/accounts/login/jwt/', data)
|
|
|
|
self.assert_json_error_contains(result, "Bad JSON web token", 400)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_when_user_does_not_exist(self) -> None:
|
2016-10-24 11:38:38 +02:00
|
|
|
payload = {'user': 'nonexisting', 'realm': 'zulip.com'}
|
2017-08-25 22:02:00 +02:00
|
|
|
with self.settings(JWT_AUTH_KEYS={'zulip': 'key'}):
|
|
|
|
auth_key = settings.JWT_AUTH_KEYS['zulip']
|
2016-10-24 11:38:38 +02:00
|
|
|
web_token = jwt.encode(payload, auth_key).decode('utf8')
|
|
|
|
data = {'json_web_token': web_token}
|
|
|
|
result = self.client_post('/accounts/login/jwt/', data)
|
2017-06-04 11:36:52 +02:00
|
|
|
self.assertEqual(result.status_code, 200) # This should ideally be not 200.
|
2016-10-24 11:38:38 +02:00
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
|
|
|
|
2017-03-25 20:44:14 +01:00
|
|
|
# The /accounts/login/jwt/ endpoint should also handle the case
|
|
|
|
# where the authentication attempt throws UserProfile.DoesNotExist.
|
|
|
|
with mock.patch(
|
|
|
|
'zerver.views.auth.authenticate',
|
|
|
|
side_effect=UserProfile.DoesNotExist("Do not exist")):
|
|
|
|
result = self.client_post('/accounts/login/jwt/', data)
|
2017-06-04 11:36:52 +02:00
|
|
|
self.assertEqual(result.status_code, 200) # This should ideally be not 200.
|
2017-03-25 20:44:14 +01:00
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_wrong_subdomain(self) -> None:
|
2016-10-24 11:38:38 +02:00
|
|
|
payload = {'user': 'hamlet', 'realm': 'zulip.com'}
|
2017-10-03 01:31:20 +02:00
|
|
|
with self.settings(JWT_AUTH_KEYS={'acme': 'key'}):
|
2017-10-28 00:48:13 +02:00
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='acme'), \
|
|
|
|
mock.patch('logging.warning'):
|
2016-10-24 11:38:38 +02:00
|
|
|
auth_key = settings.JWT_AUTH_KEYS['acme']
|
|
|
|
web_token = jwt.encode(payload, auth_key).decode('utf8')
|
|
|
|
|
|
|
|
data = {'json_web_token': web_token}
|
|
|
|
result = self.client_post('/accounts/login/jwt/', data)
|
|
|
|
self.assert_json_error_contains(result, "Wrong subdomain", 400)
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), None)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_empty_subdomain(self) -> None:
|
2016-10-24 11:38:38 +02:00
|
|
|
payload = {'user': 'hamlet', 'realm': 'zulip.com'}
|
2017-10-03 01:31:20 +02:00
|
|
|
with self.settings(JWT_AUTH_KEYS={'': 'key'}):
|
2017-10-28 00:48:13 +02:00
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value=''), \
|
|
|
|
mock.patch('logging.warning'):
|
2016-10-24 11:38:38 +02:00
|
|
|
auth_key = settings.JWT_AUTH_KEYS['']
|
|
|
|
web_token = jwt.encode(payload, auth_key).decode('utf8')
|
|
|
|
|
|
|
|
data = {'json_web_token': web_token}
|
|
|
|
result = self.client_post('/accounts/login/jwt/', data)
|
|
|
|
self.assert_json_error_contains(result, "Wrong subdomain", 400)
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), None)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success_under_subdomains(self) -> None:
|
2016-10-24 11:38:38 +02:00
|
|
|
payload = {'user': 'hamlet', 'realm': 'zulip.com'}
|
2017-10-03 01:31:20 +02:00
|
|
|
with self.settings(JWT_AUTH_KEYS={'zulip': 'key'}):
|
2016-10-24 11:38:38 +02:00
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
|
|
|
|
auth_key = settings.JWT_AUTH_KEYS['zulip']
|
|
|
|
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)
|
2017-05-23 20:57:59 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2016-10-24 11:38:38 +02:00
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
2016-10-24 14:41:45 +02:00
|
|
|
|
|
|
|
class TestLDAP(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def setUp(self) -> None:
|
2017-05-07 19:39:30 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2016-10-24 14:41:45 +02:00
|
|
|
self.setup_subdomain(user_profile)
|
|
|
|
|
|
|
|
ldap_patcher = mock.patch('django_auth_ldap.config.ldap.initialize')
|
|
|
|
self.mock_initialize = ldap_patcher.start()
|
|
|
|
self.mock_ldap = MockLDAP()
|
|
|
|
self.mock_initialize.return_value = self.mock_ldap
|
|
|
|
self.backend = ZulipLDAPAuthBackend()
|
2017-01-22 11:21:27 +01:00
|
|
|
# Internally `_realm` attribute is automatically set by the
|
|
|
|
# `authenticate()` method. But for testing the `get_or_create_user()`
|
|
|
|
# method separately, we need to set it manually.
|
|
|
|
self.backend._realm = get_realm('zulip')
|
2016-10-24 14:41:45 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def tearDown(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
self.mock_ldap.reset()
|
|
|
|
self.mock_initialize.stop()
|
|
|
|
|
2017-11-19 04:02:03 +01:00
|
|
|
def setup_subdomain(self, user_profile: UserProfile) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
realm = user_profile.realm
|
2016-10-26 18:13:43 +02:00
|
|
|
realm.string_id = 'zulip'
|
2016-10-24 14:41:45 +02:00
|
|
|
realm.save()
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='zulip.com',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
2017-11-17 23:56:45 +01:00
|
|
|
user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing',
|
|
|
|
realm=get_realm('zulip'))
|
2017-05-24 04:21:29 +02:00
|
|
|
|
|
|
|
assert(user_profile is not None)
|
2017-05-25 01:40:26 +02:00
|
|
|
self.assertEqual(user_profile.email, self.example_email("hamlet"))
|
2016-10-24 14:41:45 +02:00
|
|
|
|
2017-09-10 17:25:24 +02:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success_with_email_attr(self) -> None:
|
2017-09-10 17:25:24 +02:00
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=letham,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing',
|
|
|
|
'email': ['hamlet@zulip.com'],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(LDAP_EMAIL_ATTR='email',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
2017-11-17 23:56:45 +01:00
|
|
|
user_profile = self.backend.authenticate("letham", 'testing',
|
|
|
|
realm=get_realm('zulip'))
|
2017-09-10 17:25:24 +02:00
|
|
|
|
|
|
|
assert (user_profile is not None)
|
|
|
|
self.assertEqual(user_profile.email, self.example_email("hamlet"))
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_wrong_password(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='zulip.com',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
2017-06-21 10:12:56 +02:00
|
|
|
user = self.backend.authenticate(self.example_email("hamlet"), 'wrong')
|
|
|
|
self.assertIs(user, None)
|
2016-10-24 14:41:45 +02:00
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_nonexistent_user(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='zulip.com',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
2017-06-21 10:12:56 +02:00
|
|
|
user = self.backend.authenticate('nonexistent@zulip.com', 'testing')
|
|
|
|
self.assertIs(user, None)
|
2016-10-24 14:41:45 +02:00
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_ldap_permissions(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
backend = self.backend
|
|
|
|
self.assertFalse(backend.has_perm(None, None))
|
|
|
|
self.assertFalse(backend.has_module_perms(None, None))
|
|
|
|
self.assertTrue(backend.get_all_permissions(None, None) == set())
|
|
|
|
self.assertTrue(backend.get_group_permissions(None, None) == set())
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_django_to_ldap_username(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
backend = self.backend
|
|
|
|
with self.settings(LDAP_APPEND_DOMAIN='zulip.com'):
|
|
|
|
username = backend.django_to_ldap_username('"hamlet@test"@zulip.com')
|
|
|
|
self.assertEqual(username, '"hamlet@test"')
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_ldap_to_django_username(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
backend = self.backend
|
|
|
|
with self.settings(LDAP_APPEND_DOMAIN='zulip.com'):
|
|
|
|
username = backend.ldap_to_django_username('"hamlet@test"')
|
|
|
|
self.assertEqual(username, '"hamlet@test"@zulip.com')
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_get_or_create_user_when_user_exists(self) -> None:
|
2017-11-05 11:49:43 +01:00
|
|
|
class _LDAPUser:
|
2016-10-24 14:41:45 +02:00
|
|
|
attrs = {'fn': ['Full Name'], 'sn': ['Short Name']}
|
|
|
|
|
|
|
|
backend = self.backend
|
2017-05-25 01:40:26 +02:00
|
|
|
email = self.example_email("hamlet")
|
|
|
|
user_profile, created = backend.get_or_create_user(str(email), _LDAPUser())
|
2016-10-24 14:41:45 +02:00
|
|
|
self.assertFalse(created)
|
|
|
|
self.assertEqual(user_profile.email, email)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_get_or_create_user_when_user_does_not_exist(self) -> None:
|
2017-11-05 11:49:43 +01:00
|
|
|
class _LDAPUser:
|
2016-10-24 14:41:45 +02:00
|
|
|
attrs = {'fn': ['Full Name'], 'sn': ['Short Name']}
|
|
|
|
|
|
|
|
ldap_user_attr_map = {'full_name': 'fn', 'short_name': 'sn'}
|
|
|
|
|
|
|
|
with self.settings(AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map):
|
|
|
|
backend = self.backend
|
|
|
|
email = 'nonexisting@zulip.com'
|
|
|
|
user_profile, created = backend.get_or_create_user(email, _LDAPUser())
|
|
|
|
self.assertTrue(created)
|
|
|
|
self.assertEqual(user_profile.email, email)
|
|
|
|
self.assertEqual(user_profile.full_name, 'Full Name')
|
|
|
|
|
2017-02-08 05:04:14 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_get_or_create_user_when_user_has_invalid_name(self) -> None:
|
2017-11-05 11:49:43 +01:00
|
|
|
class _LDAPUser:
|
2017-02-08 05:04:14 +01:00
|
|
|
attrs = {'fn': ['<invalid name>'], 'sn': ['Short Name']}
|
|
|
|
|
|
|
|
ldap_user_attr_map = {'full_name': 'fn', 'short_name': 'sn'}
|
|
|
|
|
|
|
|
with self.settings(AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map):
|
|
|
|
backend = self.backend
|
|
|
|
email = 'nonexisting@zulip.com'
|
|
|
|
with self.assertRaisesRegex(Exception, "Invalid characters in name!"):
|
|
|
|
backend.get_or_create_user(email, _LDAPUser())
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_get_or_create_user_when_realm_is_deactivated(self) -> None:
|
2017-11-05 11:49:43 +01:00
|
|
|
class _LDAPUser:
|
2016-10-24 14:41:45 +02:00
|
|
|
attrs = {'fn': ['Full Name'], 'sn': ['Short Name']}
|
|
|
|
|
|
|
|
ldap_user_attr_map = {'full_name': 'fn', 'short_name': 'sn'}
|
|
|
|
|
|
|
|
with self.settings(AUTH_LDAP_USER_ATTR_MAP=ldap_user_attr_map):
|
|
|
|
backend = self.backend
|
|
|
|
email = 'nonexisting@zulip.com'
|
2017-01-22 11:21:27 +01:00
|
|
|
do_deactivate_realm(backend._realm)
|
2016-12-16 02:11:42 +01:00
|
|
|
with self.assertRaisesRegex(Exception, 'Realm has been deactivated'):
|
2016-10-24 14:41:45 +02:00
|
|
|
backend.get_or_create_user(email, _LDAPUser())
|
|
|
|
|
2017-09-10 17:25:24 +02:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_get_or_create_user_when_ldap_has_no_email_attr(self) -> None:
|
2017-11-05 11:49:43 +01:00
|
|
|
class _LDAPUser:
|
2017-09-10 17:25:24 +02:00
|
|
|
attrs = {'fn': ['Full Name'], 'sn': ['Short Name']}
|
|
|
|
|
|
|
|
nonexisting_attr = 'email'
|
|
|
|
with self.settings(LDAP_EMAIL_ATTR=nonexisting_attr):
|
|
|
|
backend = self.backend
|
|
|
|
email = 'nonexisting@zulip.com'
|
|
|
|
with self.assertRaisesRegex(Exception, 'LDAP user doesn\'t have the needed email attribute'):
|
|
|
|
backend.get_or_create_user(email, _LDAPUser())
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_django_to_ldap_username_when_domain_does_not_match(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
backend = self.backend
|
2017-05-25 01:40:26 +02:00
|
|
|
email = self.example_email("hamlet")
|
2016-12-16 02:11:42 +01:00
|
|
|
with self.assertRaisesRegex(Exception, 'Username does not match LDAP domain.'):
|
2016-10-24 14:41:45 +02:00
|
|
|
with self.settings(LDAP_APPEND_DOMAIN='acme.com'):
|
|
|
|
backend.django_to_ldap_username(email)
|
|
|
|
|
2017-09-27 21:30:53 +02:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_when_domain_does_not_match(self) -> None:
|
2017-09-27 21:30:53 +02:00
|
|
|
with self.settings(LDAP_APPEND_DOMAIN='acme.com'):
|
|
|
|
user_profile = self.backend.authenticate(self.example_email("hamlet"), 'pass')
|
|
|
|
self.assertIs(user_profile, None)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_wrong_subdomain(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='zulip.com',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
2017-05-25 01:40:26 +02:00
|
|
|
user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing',
|
2017-11-17 23:56:45 +01:00
|
|
|
realm=get_realm('zephyr'))
|
2016-10-24 14:41:45 +02:00
|
|
|
self.assertIs(user_profile, None)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_invalid_subdomain(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='zulip.com',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
2017-05-25 01:40:26 +02:00
|
|
|
user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing',
|
2017-11-17 23:56:45 +01:00
|
|
|
realm=None)
|
2016-10-24 14:41:45 +02:00
|
|
|
self.assertIs(user_profile, None)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success_with_valid_subdomain(self) -> None:
|
2016-10-24 14:41:45 +02:00
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='zulip.com',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
2017-05-25 01:40:26 +02:00
|
|
|
user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing',
|
2017-11-17 23:56:45 +01:00
|
|
|
realm=get_realm('zulip'))
|
2017-05-24 04:21:29 +02:00
|
|
|
assert(user_profile is not None)
|
2017-05-25 01:40:26 +02:00
|
|
|
self.assertEqual(user_profile.email, self.example_email("hamlet"))
|
2016-10-26 12:46:51 +02:00
|
|
|
|
2017-11-18 01:07:20 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_failure_due_to_deactivated_user(self) -> None:
|
2017-11-18 01:07:20 +01:00
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
user_profile = self.example_user("hamlet")
|
|
|
|
do_deactivate_user(user_profile)
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='zulip.com',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=zulip,dc=com'):
|
|
|
|
user_profile = self.backend.authenticate(self.example_email("hamlet"), 'testing',
|
|
|
|
realm=get_realm('zulip'))
|
|
|
|
self.assertIs(user_profile, None)
|
|
|
|
|
2017-01-22 11:21:27 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_login_success_when_user_does_not_exist_with_valid_subdomain(
|
|
|
|
self) -> None:
|
2017-01-22 11:21:27 +01:00
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=nonexisting,ou=users,dc=acme,dc=com': {
|
|
|
|
'cn': ['NonExisting', ],
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
LDAP_APPEND_DOMAIN='acme.com',
|
|
|
|
AUTH_LDAP_BIND_PASSWORD='',
|
|
|
|
AUTH_LDAP_USER_DN_TEMPLATE='uid=%(user)s,ou=users,dc=acme,dc=com'):
|
|
|
|
user_profile = self.backend.authenticate('nonexisting@acme.com', 'testing',
|
2017-11-17 23:56:45 +01:00
|
|
|
realm=get_realm('zulip'))
|
2017-05-24 04:21:29 +02:00
|
|
|
assert(user_profile is not None)
|
2017-01-22 11:21:27 +01:00
|
|
|
self.assertEqual(user_profile.email, 'nonexisting@acme.com')
|
|
|
|
self.assertEqual(user_profile.full_name, 'NonExisting')
|
|
|
|
self.assertEqual(user_profile.realm.string_id, 'zulip')
|
|
|
|
|
2016-10-26 12:46:51 +02:00
|
|
|
class TestZulipLDAPUserPopulator(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_authenticate(self) -> None:
|
2016-10-26 12:46:51 +02:00
|
|
|
backend = ZulipLDAPUserPopulator()
|
2017-05-25 01:40:26 +02:00
|
|
|
result = backend.authenticate(self.example_email("hamlet"), 'testing') # type: ignore # complains that the function does not return any value!
|
2016-10-26 12:46:51 +02:00
|
|
|
self.assertIs(result, None)
|
2016-10-26 12:39:09 +02:00
|
|
|
|
|
|
|
class TestZulipAuthMixin(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_get_user(self) -> None:
|
2016-10-26 12:39:09 +02:00
|
|
|
backend = ZulipAuthMixin()
|
|
|
|
result = backend.get_user(11111)
|
|
|
|
self.assertIs(result, None)
|
2016-10-26 13:50:00 +02:00
|
|
|
|
|
|
|
class TestPasswordAuthEnabled(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_password_auth_enabled_for_ldap(self) -> None:
|
2016-10-26 13:50:00 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',)):
|
2017-01-08 20:24:05 +01:00
|
|
|
realm = Realm.objects.get(string_id='zulip')
|
2016-10-26 13:50:00 +02:00
|
|
|
self.assertTrue(password_auth_enabled(realm))
|
2016-10-26 14:40:14 +02:00
|
|
|
|
2017-09-15 16:59:03 +02:00
|
|
|
class TestRequireEmailFormatUsernames(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_require_email_format_usernames_for_ldap_with_append_domain(
|
|
|
|
self) -> None:
|
2017-09-15 16:59:03 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',),
|
|
|
|
LDAP_APPEND_DOMAIN="zulip.com"):
|
|
|
|
realm = Realm.objects.get(string_id='zulip')
|
|
|
|
self.assertFalse(require_email_format_usernames(realm))
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_require_email_format_usernames_for_ldap_with_email_attr(
|
|
|
|
self) -> None:
|
2017-09-15 16:59:03 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',),
|
|
|
|
LDAP_EMAIL_ATTR="email"):
|
|
|
|
realm = Realm.objects.get(string_id='zulip')
|
|
|
|
self.assertFalse(require_email_format_usernames(realm))
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_require_email_format_usernames_for_email_only(self) -> None:
|
2017-09-15 16:59:03 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.EmailAuthBackend',)):
|
|
|
|
realm = Realm.objects.get(string_id='zulip')
|
|
|
|
self.assertTrue(require_email_format_usernames(realm))
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_require_email_format_usernames_for_email_and_ldap_with_email_attr(
|
|
|
|
self) -> None:
|
2017-09-15 16:59:03 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.EmailAuthBackend',
|
|
|
|
'zproject.backends.ZulipLDAPAuthBackend'),
|
|
|
|
LDAP_EMAIL_ATTR="email"):
|
|
|
|
realm = Realm.objects.get(string_id='zulip')
|
|
|
|
self.assertFalse(require_email_format_usernames(realm))
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_require_email_format_usernames_for_email_and_ldap_with_append_email(
|
|
|
|
self) -> None:
|
2017-09-15 16:59:03 +02:00
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.EmailAuthBackend',
|
|
|
|
'zproject.backends.ZulipLDAPAuthBackend'),
|
|
|
|
LDAP_APPEND_DOMAIN="zulip.com"):
|
|
|
|
realm = Realm.objects.get(string_id='zulip')
|
|
|
|
self.assertFalse(require_email_format_usernames(realm))
|
|
|
|
|
2016-10-26 14:40:14 +02:00
|
|
|
class TestMaybeSendToRegistration(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_sso_only_when_preregistration_user_does_not_exist(self) -> None:
|
2016-10-26 14:40:14 +02:00
|
|
|
rf = RequestFactory()
|
|
|
|
request = rf.get('/')
|
|
|
|
request.session = {}
|
|
|
|
request.user = None
|
|
|
|
|
|
|
|
# Creating a mock Django form in order to keep the test simple.
|
|
|
|
# This form will be returned by the create_hompage_form function
|
|
|
|
# and will always be valid so that the code that we want to test
|
|
|
|
# actually runs.
|
2017-11-05 11:49:43 +01:00
|
|
|
class Form:
|
2017-11-19 04:02:03 +01:00
|
|
|
def is_valid(self) -> bool:
|
2016-10-26 14:40:14 +02:00
|
|
|
return True
|
|
|
|
|
|
|
|
with self.settings(ONLY_SSO=True):
|
2016-12-24 04:35:58 +01:00
|
|
|
with mock.patch('zerver.views.auth.HomepageForm', return_value=Form()):
|
2016-10-26 14:40:14 +02:00
|
|
|
self.assertEqual(PreregistrationUser.objects.all().count(), 0)
|
2017-05-25 01:40:26 +02:00
|
|
|
result = maybe_send_to_registration(request, self.example_email("hamlet"))
|
2016-10-26 14:40:14 +02:00
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
confirmation = Confirmation.objects.all().first()
|
|
|
|
confirmation_key = confirmation.confirmation_key
|
|
|
|
self.assertIn('do_confirm/' + confirmation_key, result.url)
|
|
|
|
self.assertEqual(PreregistrationUser.objects.all().count(), 1)
|
|
|
|
|
2016-12-01 08:54:21 +01:00
|
|
|
result = self.client_get(result.url)
|
|
|
|
self.assert_in_response('action="/accounts/register/"', result)
|
|
|
|
self.assert_in_response('value="{0}" name="key"'.format(confirmation_key), result)
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_sso_only_when_preregistration_user_exists(self) -> None:
|
2016-10-26 14:40:14 +02:00
|
|
|
rf = RequestFactory()
|
|
|
|
request = rf.get('/')
|
|
|
|
request.session = {}
|
|
|
|
request.user = None
|
|
|
|
|
|
|
|
# Creating a mock Django form in order to keep the test simple.
|
|
|
|
# This form will be returned by the create_hompage_form function
|
|
|
|
# and will always be valid so that the code that we want to test
|
|
|
|
# actually runs.
|
2017-11-05 11:49:43 +01:00
|
|
|
class Form:
|
2017-11-19 04:02:03 +01:00
|
|
|
def is_valid(self) -> bool:
|
2016-10-26 14:40:14 +02:00
|
|
|
return True
|
|
|
|
|
2017-05-25 01:40:26 +02:00
|
|
|
email = self.example_email("hamlet")
|
2016-10-26 14:40:14 +02:00
|
|
|
user = PreregistrationUser(email=email)
|
|
|
|
user.save()
|
|
|
|
|
|
|
|
with self.settings(ONLY_SSO=True):
|
2016-12-24 04:35:58 +01:00
|
|
|
with mock.patch('zerver.views.auth.HomepageForm', return_value=Form()):
|
2016-10-26 14:40:14 +02:00
|
|
|
self.assertEqual(PreregistrationUser.objects.all().count(), 1)
|
|
|
|
result = maybe_send_to_registration(request, email)
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
confirmation = Confirmation.objects.all().first()
|
|
|
|
confirmation_key = confirmation.confirmation_key
|
|
|
|
self.assertIn('do_confirm/' + confirmation_key, result.url)
|
|
|
|
self.assertEqual(PreregistrationUser.objects.all().count(), 1)
|
2016-11-02 21:51:56 +01:00
|
|
|
|
|
|
|
class TestAdminSetBackends(ZulipTestCase):
|
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_change_enabled_backends(self) -> None:
|
2016-11-02 21:51:56 +01:00
|
|
|
# Log in as admin
|
2017-05-25 01:44:04 +02:00
|
|
|
self.login(self.example_email("iago"))
|
2016-11-02 21:51:56 +01:00
|
|
|
result = self.client_patch("/json/realm", {
|
|
|
|
'authentication_methods': ujson.dumps({u'Email': False, u'Dev': True})})
|
|
|
|
self.assert_json_success(result)
|
2017-01-04 05:30:48 +01:00
|
|
|
realm = get_realm('zulip')
|
2016-11-02 21:51:56 +01:00
|
|
|
self.assertFalse(password_auth_enabled(realm))
|
2016-11-07 21:20:55 +01:00
|
|
|
self.assertTrue(dev_auth_enabled(realm))
|
2016-11-02 21:51:56 +01:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_disable_all_backends(self) -> None:
|
2016-11-02 21:51:56 +01:00
|
|
|
# Log in as admin
|
2017-05-25 01:44:04 +02:00
|
|
|
self.login(self.example_email("iago"))
|
2016-11-02 21:51:56 +01:00
|
|
|
result = self.client_patch("/json/realm", {
|
2016-12-02 00:08:34 +01:00
|
|
|
'authentication_methods': ujson.dumps({u'Email': False, u'Dev': False})})
|
2017-07-25 02:28:33 +02:00
|
|
|
self.assert_json_error(result, 'At least one authentication method must be enabled.')
|
2017-01-04 05:30:48 +01:00
|
|
|
realm = get_realm('zulip')
|
2016-11-02 21:51:56 +01:00
|
|
|
self.assertTrue(password_auth_enabled(realm))
|
2016-11-07 21:20:55 +01:00
|
|
|
self.assertTrue(dev_auth_enabled(realm))
|
2016-11-02 21:51:56 +01:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_supported_backends_only_updated(self) -> None:
|
2016-11-02 21:51:56 +01:00
|
|
|
# Log in as admin
|
2017-05-25 01:44:04 +02:00
|
|
|
self.login(self.example_email("iago"))
|
2016-11-02 21:51:56 +01:00
|
|
|
# Set some supported and unsupported backends
|
|
|
|
result = self.client_patch("/json/realm", {
|
2016-12-02 00:08:34 +01:00
|
|
|
'authentication_methods': ujson.dumps({u'Email': False, u'Dev': True, u'GitHub': False})})
|
2016-11-02 21:51:56 +01:00
|
|
|
self.assert_json_success(result)
|
2017-01-04 05:30:48 +01:00
|
|
|
realm = get_realm('zulip')
|
2016-11-02 21:51:56 +01:00
|
|
|
# Check that unsupported backend is not enabled
|
|
|
|
self.assertFalse(github_auth_enabled(realm))
|
2016-11-07 21:20:55 +01:00
|
|
|
self.assertTrue(dev_auth_enabled(realm))
|
2016-11-02 21:51:56 +01:00
|
|
|
self.assertFalse(password_auth_enabled(realm))
|
2017-04-10 08:06:10 +02:00
|
|
|
|
2017-05-25 02:29:42 +02:00
|
|
|
class LoginEmailValidatorTestCase(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_valid_email(self) -> None:
|
2017-05-25 01:40:26 +02:00
|
|
|
validate_login_email(self.example_email("hamlet"))
|
2017-04-10 08:06:10 +02:00
|
|
|
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_invalid_email(self) -> None:
|
2017-04-10 08:06:10 +02:00
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
validate_login_email(u'hamlet')
|
2017-04-20 08:25:15 +02:00
|
|
|
|
|
|
|
class LoginOrRegisterRemoteUserTestCase(ZulipTestCase):
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_invalid_subdomain(self) -> None:
|
2017-04-20 08:25:15 +02:00
|
|
|
full_name = 'Hamlet'
|
|
|
|
invalid_subdomain = True
|
2017-05-23 20:57:59 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2017-04-20 08:25:15 +02:00
|
|
|
request = POSTRequestMock({}, user_profile)
|
|
|
|
response = login_or_register_remote_user(
|
|
|
|
request,
|
2017-05-23 20:57:59 +02:00
|
|
|
self.example_email('hamlet'),
|
2017-04-20 08:25:15 +02:00
|
|
|
user_profile,
|
|
|
|
full_name=full_name,
|
|
|
|
invalid_subdomain=invalid_subdomain)
|
|
|
|
self.assertIn('/accounts/login/?subdomain=1', response.url)
|
2017-09-22 10:58:12 +02:00
|
|
|
|
|
|
|
class LDAPBackendTest(ZulipTestCase):
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2017-11-17 10:47:43 +01:00
|
|
|
def test_non_existing_realm(self) -> None:
|
2017-09-22 10:58:12 +02:00
|
|
|
email = self.example_email('hamlet')
|
|
|
|
data = {'username': email, 'password': initial_password(email)}
|
|
|
|
error_type = ZulipLDAPAuthBackend.REALM_IS_NONE_ERROR
|
|
|
|
error = ZulipLDAPConfigurationError('Realm is None', error_type)
|
|
|
|
with mock.patch('zproject.backends.ZulipLDAPAuthBackend.get_or_create_user',
|
|
|
|
side_effect=error), \
|
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser._authenticate_user_dn'):
|
|
|
|
response = self.client_post('/login/', data)
|
|
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
self.assertEqual(response.url, reverse('ldap_error_realm_is_none'))
|
|
|
|
response = self.client_get(response.url)
|
|
|
|
self.assert_in_response('You are trying to login using LDAP '
|
|
|
|
'without creating an',
|
|
|
|
response)
|