2016-04-21 18:34:54 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from django.conf import settings
|
2016-09-13 21:30:18 +02:00
|
|
|
from django.http import HttpResponse
|
2016-11-07 00:09:21 +01:00
|
|
|
from django.test import TestCase, override_settings
|
2016-04-21 18:34:54 +02:00
|
|
|
from django_auth_ldap.backend import _LDAPUser
|
2016-07-25 11:24:36 +02:00
|
|
|
from django.test.client import RequestFactory
|
2017-03-03 19:01:52 +01:00
|
|
|
from typing import Any, Callable, Dict, List, Optional, Text
|
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
|
2016-12-01 13:10:59 +01:00
|
|
|
from django.core.urlresolvers 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
|
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-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, \
|
|
|
|
check_bool, check_dict_only, check_string
|
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
|
|
|
)
|
|
|
|
from zerver.models import \
|
2017-01-04 05:30:48 +01:00
|
|
|
get_realm, get_user_profile_by_email, email_to_username, UserProfile, \
|
2016-10-26 14:40:14 +02:00
|
|
|
PreregistrationUser, Realm
|
|
|
|
|
|
|
|
from confirmation.models import Confirmation
|
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-04-19 19:05:04 +02:00
|
|
|
SocialAuthMixin, AUTH_BACKEND_NAME_MAP
|
2016-07-25 11:24:36 +02:00
|
|
|
|
2016-10-26 14:40:14 +02:00
|
|
|
from zerver.views.auth import maybe_send_to_registration
|
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
|
|
|
|
2016-09-13 21:30:18 +02:00
|
|
|
from six.moves import urllib
|
2016-10-17 14:28:23 +02:00
|
|
|
from six.moves.http_cookies import SimpleCookie
|
2016-04-21 18:34:54 +02:00
|
|
|
import ujson
|
2017-04-19 10:04:23 +02:00
|
|
|
from zerver.lib.test_helpers import MockLDAP, unsign_subdomain_cookie
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2017-04-27 06:46:43 +02:00
|
|
|
class AuthBackendTest(ZulipTestCase):
|
2017-03-28 11:11:29 +02:00
|
|
|
email = u"hamlet@zulip.com"
|
|
|
|
|
|
|
|
def get_username(self, email_to_username=None):
|
|
|
|
# type: (Optional[Callable[[Text], Text]]) -> Text
|
|
|
|
username = self.email
|
|
|
|
if email_to_username is not None:
|
|
|
|
username = email_to_username(self.email)
|
|
|
|
|
|
|
|
return username
|
|
|
|
|
2017-03-28 11:16:36 +02:00
|
|
|
def verify_backend(self, backend, good_kwargs=None, bad_kwargs=None):
|
|
|
|
# type: (Any, Optional[Dict[str, Any]], Optional[Dict[str, Any]]) -> None
|
|
|
|
|
|
|
|
user_profile = get_user_profile_by_email(self.email)
|
|
|
|
|
2016-06-03 01:43:28 +02:00
|
|
|
if good_kwargs is None:
|
|
|
|
good_kwargs = {}
|
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-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
|
|
|
|
2016-04-21 18:34:54 +02:00
|
|
|
def test_dummy_backend(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> None
|
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,
|
|
|
|
use_dummy_backend=True),
|
|
|
|
bad_kwargs=dict(username=username,
|
|
|
|
use_dummy_backend=False))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2016-10-07 13:38:01 +02:00
|
|
|
def setup_subdomain(self, user_profile):
|
|
|
|
# type: (UserProfile) -> None
|
|
|
|
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()
|
|
|
|
|
2016-04-21 18:34:54 +02:00
|
|
|
def test_email_auth_backend(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> None
|
2017-03-28 11:16:36 +02:00
|
|
|
email = self.email
|
|
|
|
username = self.get_username()
|
2016-04-21 18:34:54 +02:00
|
|
|
user_profile = get_user_profile_by_email(email)
|
|
|
|
password = "testpassword"
|
|
|
|
user_profile.set_password(password)
|
|
|
|
user_profile.save()
|
2016-10-07 13:38:01 +02:00
|
|
|
self.setup_subdomain(user_profile)
|
|
|
|
|
2016-04-21 18:34:54 +02:00
|
|
|
self.verify_backend(EmailAuthBackend(),
|
2017-03-28 11:16:36 +02:00
|
|
|
bad_kwargs=dict(username=username,
|
|
|
|
password=''),
|
|
|
|
good_kwargs=dict(username=username,
|
|
|
|
password=password))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
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]
|
|
|
|
user = EmailAuthBackend().authenticate(email,
|
|
|
|
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
|
|
|
# Subdomain is ignored when feature is not enabled
|
|
|
|
self.verify_backend(EmailAuthBackend(),
|
|
|
|
good_kwargs=dict(password=password,
|
2017-03-28 11:16:36 +02:00
|
|
|
username=username,
|
2016-12-03 00:04:17 +01:00
|
|
|
realm_subdomain='acme',
|
|
|
|
return_data=dict()))
|
2016-10-07 13:38:01 +02:00
|
|
|
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
# With subdomains, authenticating with the right subdomain
|
|
|
|
# works; using the wrong subdomain doesn't
|
|
|
|
self.verify_backend(EmailAuthBackend(),
|
|
|
|
good_kwargs=dict(password=password,
|
2017-03-28 11:16:36 +02:00
|
|
|
username=username,
|
2016-10-07 13:38:01 +02:00
|
|
|
realm_subdomain='zulip',
|
|
|
|
return_data=dict()),
|
|
|
|
bad_kwargs=dict(password=password,
|
2017-03-28 11:16:36 +02:00
|
|
|
username=username,
|
2016-11-30 14:17:35 +01:00
|
|
|
realm_subdomain='acme',
|
|
|
|
return_data=dict()))
|
2016-10-07 13:38:01 +02:00
|
|
|
# Things work normally in the event that we're using a
|
|
|
|
# non-subdomain login page, even if subdomains are enabled
|
|
|
|
self.verify_backend(EmailAuthBackend(),
|
2017-03-28 11:16:36 +02:00
|
|
|
bad_kwargs=dict(password="wrong",
|
|
|
|
username=username),
|
|
|
|
good_kwargs=dict(password=password,
|
|
|
|
username=username))
|
2016-10-07 13:38:01 +02:00
|
|
|
|
2016-04-21 18:34:54 +02:00
|
|
|
def test_email_auth_backend_disabled_password_auth(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> None
|
2016-08-08 12:19:08 +02:00
|
|
|
email = u"hamlet@zulip.com"
|
2016-04-21 18:34:54 +02:00
|
|
|
user_profile = get_user_profile_by_email(email)
|
|
|
|
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):
|
2016-08-08 12:19:35 +02:00
|
|
|
self.assertIsNone(EmailAuthBackend().authenticate(email, password))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2017-04-27 06:46:43 +02:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipDummyBackend',))
|
|
|
|
def test_no_backend_enabled(self):
|
|
|
|
# type: () -> None
|
|
|
|
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',))
|
|
|
|
def test_any_backend_enabled(self):
|
|
|
|
# type: () -> None
|
|
|
|
|
|
|
|
# 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',))
|
2016-04-21 18:34:54 +02:00
|
|
|
def test_google_backend(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> 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
|
|
|
self.setup_subdomain(user_profile)
|
|
|
|
|
2016-04-21 18:34:54 +02:00
|
|
|
with mock.patch('apiclient.sample_tools.client.verify_id_token', return_value=payload):
|
|
|
|
self.verify_backend(backend)
|
|
|
|
|
2016-10-07 13:38:01 +02:00
|
|
|
# With REALMS_HAVE_SUBDOMAINS off, subdomain is ignored
|
|
|
|
with mock.patch('apiclient.sample_tools.client.verify_id_token', return_value=payload):
|
|
|
|
self.verify_backend(backend,
|
|
|
|
good_kwargs=dict(realm_subdomain='acme'))
|
|
|
|
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
# With subdomains, authenticating with the right subdomain
|
|
|
|
# works; using the wrong subdomain doesn't
|
|
|
|
with mock.patch('apiclient.sample_tools.client.verify_id_token', return_value=payload):
|
|
|
|
self.verify_backend(backend,
|
|
|
|
good_kwargs=dict(realm_subdomain="zulip"),
|
|
|
|
bad_kwargs=dict(realm_subdomain='acme'))
|
|
|
|
|
2016-04-21 18:34:54 +02:00
|
|
|
# Verify valid_attestation parameter is set correctly
|
|
|
|
unverified_payload = dict(email_verified=False)
|
|
|
|
with mock.patch('apiclient.sample_tools.client.verify_id_token', return_value=unverified_payload):
|
2016-06-03 08:01:15 +02:00
|
|
|
ret = dict() # type: Dict[str, str]
|
2016-04-21 18:34:54 +02:00
|
|
|
result = backend.authenticate(return_data=ret)
|
|
|
|
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()
|
|
|
|
result = backend.authenticate(return_data=ret)
|
|
|
|
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()
|
|
|
|
result = backend.authenticate(return_data=ret)
|
|
|
|
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',))
|
2016-04-21 18:34:54 +02:00
|
|
|
def test_ldap_backend(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> 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')), (
|
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser._get_user_attrs',
|
|
|
|
return_value=dict(full_name=['Hamlet']))):
|
2016-04-21 18:34:54 +02:00
|
|
|
self.assertIsNone(backend.authenticate(email, password))
|
|
|
|
|
|
|
|
# For this backend, we mock the internals of django_auth_ldap
|
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')), (
|
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser._get_user_attrs',
|
|
|
|
return_value=dict(full_name=['Hamlet']))):
|
2017-03-28 11:16:36 +02:00
|
|
|
self.verify_backend(backend, good_kwargs=dict(username=username,
|
|
|
|
password=password))
|
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')), (
|
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser._get_user_attrs',
|
|
|
|
return_value=dict(full_name=['Hamlet']))):
|
2017-03-28 11:16:36 +02:00
|
|
|
self.verify_backend(backend,
|
|
|
|
good_kwargs=dict(username=username,
|
|
|
|
password=password,
|
|
|
|
realm_subdomain='acme'))
|
2016-10-07 13:38:01 +02:00
|
|
|
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
# With subdomains, authenticating with the right subdomain
|
|
|
|
# works; using the wrong subdomain doesn't
|
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')), (
|
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser._get_user_attrs',
|
|
|
|
return_value=dict(full_name=['Hamlet']))):
|
2016-10-07 13:38:01 +02:00
|
|
|
self.verify_backend(backend,
|
2017-03-28 11:16:36 +02:00
|
|
|
bad_kwargs=dict(username=username,
|
|
|
|
password=password,
|
2016-10-07 13:38:01 +02:00
|
|
|
realm_subdomain='acme'),
|
2017-03-28 11:16:36 +02:00
|
|
|
good_kwargs=dict(username=username,
|
|
|
|
password=password,
|
2016-10-07 13:38:01 +02:00
|
|
|
realm_subdomain='zulip'))
|
|
|
|
|
2017-03-23 07:49:42 +01:00
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
# With subdomains, authenticating with the right subdomain
|
|
|
|
# works; using the wrong subdomain doesn't
|
|
|
|
with mock.patch('django_auth_ldap.backend._LDAPUser._authenticate_user_dn'), (
|
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser._check_requirements')), (
|
|
|
|
mock.patch('zproject.backends.get_realm', side_effect=Realm.DoesNotExist)), (
|
|
|
|
mock.patch('django_auth_ldap.backend._LDAPUser._get_user_attrs',
|
|
|
|
return_value=dict(full_name=['Hamlet']))):
|
|
|
|
|
|
|
|
user = backend.authenticate(email,
|
|
|
|
password=password,
|
|
|
|
realm_subdomain='zulip')
|
|
|
|
self.assertEqual(user, None)
|
|
|
|
|
2016-04-21 18:34:54 +02:00
|
|
|
def test_devauth_backend(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> None
|
2017-03-28 11:16:36 +02:00
|
|
|
self.verify_backend(DevAuthBackend(),
|
|
|
|
good_kwargs=dict(username=self.get_username()))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',))
|
2016-04-21 18:34:54 +02:00
|
|
|
def test_remote_user_backend(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> None
|
2016-10-07 13:38:01 +02:00
|
|
|
self.setup_subdomain(get_user_profile_by_email(u'hamlet@zulip.com'))
|
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,
|
|
|
|
realm_subdomain='acme'))
|
2016-10-07 13:38:01 +02:00
|
|
|
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
# With subdomains, authenticating with the right subdomain
|
|
|
|
# works; using the wrong subdomain doesn't
|
|
|
|
self.verify_backend(ZulipRemoteUserBackend(),
|
2017-03-28 11:16:36 +02:00
|
|
|
good_kwargs=dict(remote_user=username,
|
|
|
|
realm_subdomain='zulip'),
|
|
|
|
bad_kwargs=dict(remote_user=username,
|
|
|
|
realm_subdomain='acme'))
|
2016-04-21 18:34:54 +02:00
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',))
|
2016-04-21 18:34:54 +02:00
|
|
|
def test_remote_user_backend_sso_append_domain(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> None
|
2016-10-07 13:38:01 +02:00
|
|
|
self.setup_subdomain(get_user_profile_by_email(u'hamlet@zulip.com'))
|
2017-03-28 11:16:36 +02:00
|
|
|
username = self.get_username(email_to_username)
|
2016-04-21 18:34:54 +02:00
|
|
|
with self.settings(SSO_APPEND_DOMAIN='zulip.com'):
|
|
|
|
self.verify_backend(ZulipRemoteUserBackend(),
|
2017-03-28 11:16:36 +02:00
|
|
|
good_kwargs=dict(remote_user=username,
|
|
|
|
realm_subdomain='acme'))
|
2016-10-07 13:38:01 +02:00
|
|
|
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
# With subdomains, authenticating with the right subdomain
|
|
|
|
# works; using the wrong subdomain doesn't
|
|
|
|
with self.settings(SSO_APPEND_DOMAIN='zulip.com'):
|
|
|
|
self.verify_backend(ZulipRemoteUserBackend(),
|
2017-03-28 11:16:36 +02:00
|
|
|
good_kwargs=dict(remote_user=username,
|
|
|
|
realm_subdomain='zulip'),
|
|
|
|
bad_kwargs=dict(remote_user=username,
|
|
|
|
realm_subdomain='acme'))
|
2016-04-21 21:07:43 +02:00
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitHubAuthBackend',))
|
2016-07-25 11:24:36 +02:00
|
|
|
def test_github_backend(self):
|
2016-07-30 00:48:40 +02:00
|
|
|
# type: () -> None
|
2016-07-25 11:24:36 +02:00
|
|
|
email = 'hamlet@zulip.com'
|
2016-10-07 13:38:01 +02:00
|
|
|
self.setup_subdomain(get_user_profile_by_email(email))
|
|
|
|
good_kwargs = dict(response=dict(email=email), return_data=dict(),
|
|
|
|
realm_subdomain='acme')
|
2016-07-29 21:47:14 +02:00
|
|
|
self.verify_backend(GitHubAuthBackend(),
|
2016-07-25 11:24:36 +02:00
|
|
|
good_kwargs=good_kwargs,
|
2016-10-07 13:38:01 +02:00
|
|
|
bad_kwargs=dict())
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
# With subdomains, authenticating with the right subdomain
|
|
|
|
# works; using the wrong subdomain doesn't
|
|
|
|
good_kwargs = dict(response=dict(email=email), return_data=dict(),
|
|
|
|
realm_subdomain='zulip')
|
|
|
|
bad_kwargs = dict(response=dict(email=email), return_data=dict(),
|
|
|
|
realm_subdomain='acme')
|
|
|
|
self.verify_backend(GitHubAuthBackend(),
|
|
|
|
good_kwargs=good_kwargs,
|
|
|
|
bad_kwargs=bad_kwargs)
|
2016-07-25 11:24:36 +02:00
|
|
|
|
2016-11-07 01:44:15 +01:00
|
|
|
class SocialAuthMixinTest(ZulipTestCase):
|
|
|
|
def test_social_auth_mixing(self):
|
|
|
|
# type: () -> None
|
|
|
|
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):
|
2016-07-25 11:24:36 +02:00
|
|
|
def setUp(self):
|
2016-07-30 00:48:40 +02:00
|
|
|
# type: () -> 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 = {}
|
|
|
|
request.get_host = lambda: 'acme.testserver'
|
|
|
|
request.user = self.user_profile
|
|
|
|
self.backend.strategy.request = request
|
|
|
|
|
2016-11-01 23:50:35 +01:00
|
|
|
def do_auth(self, *args, **kwargs):
|
|
|
|
# type: (*Any, **Any) -> UserProfile
|
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitHubAuthBackend',)):
|
2017-04-19 19:05:04 +02:00
|
|
|
return self.backend.authenticate(**kwargs)
|
2016-11-01 23:50:35 +01:00
|
|
|
|
2016-10-26 13:57:17 +02:00
|
|
|
def test_github_auth_enabled(self):
|
|
|
|
# type: () -> None
|
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitHubAuthBackend',)):
|
|
|
|
self.assertTrue(github_auth_enabled())
|
|
|
|
|
|
|
|
def test_full_name_with_missing_key(self):
|
|
|
|
# type: () -> None
|
|
|
|
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-03-09 09:45:21 +01:00
|
|
|
def test_full_name_with_none(self):
|
|
|
|
# type: () -> None
|
|
|
|
self.assertEqual(self.backend.get_full_name(response={'email': None}), '')
|
|
|
|
|
2016-10-07 11:10:21 +02:00
|
|
|
def test_github_backend_do_auth_without_subdomains(self):
|
2016-07-30 00:48:40 +02:00
|
|
|
# type: () -> 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-12 04:50:38 +02:00
|
|
|
mock.patch('zerver.views.auth.login'):
|
2016-11-28 23:29:01 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
2016-10-07 11:10:21 +02:00
|
|
|
result = self.backend.do_auth(response=response)
|
|
|
|
self.assertNotIn('subdomain=1', result.url)
|
|
|
|
|
2016-12-01 13:10:59 +01:00
|
|
|
def test_github_backend_do_auth_with_non_existing_subdomain(self):
|
2016-10-07 11:10:21 +02:00
|
|
|
# type: () -> 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-07 11:10:21 +02:00
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
2016-12-01 13:10:59 +01:00
|
|
|
self.backend.strategy.session_set('subdomain', 'test')
|
2016-11-28 23:29:01 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
2016-10-07 11:10:21 +02:00
|
|
|
result = self.backend.do_auth(response=response)
|
|
|
|
self.assertIn('subdomain=1', result.url)
|
2016-07-25 11:24:36 +02:00
|
|
|
|
2016-12-01 13:10:59 +01:00
|
|
|
def test_github_backend_do_auth_with_subdomains(self):
|
|
|
|
# type: () -> 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):
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
self.backend.strategy.session_set('subdomain', 'zulip')
|
|
|
|
response = dict(email=self.email, name=self.name)
|
|
|
|
result = self.backend.do_auth(response=response)
|
|
|
|
self.assertEqual('http://zulip.testserver/accounts/login/subdomain/', result.url)
|
|
|
|
|
2016-08-02 11:07:45 +02:00
|
|
|
def test_github_backend_do_auth_for_default(self):
|
2016-08-08 10:57:34 +02:00
|
|
|
# type: () -> 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:
|
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)
|
|
|
|
|
2016-10-11 15:14:50 +02:00
|
|
|
kwargs = {'realm_subdomain': 'acme',
|
|
|
|
'response': response,
|
|
|
|
'return_data': {}}
|
|
|
|
result.assert_called_with(self.user_profile, 'fake-access-token', **kwargs)
|
|
|
|
|
2017-03-23 07:49:42 +01:00
|
|
|
def test_github_backend_do_auth_for_default_auth_failed(self):
|
|
|
|
# type: () -> None
|
|
|
|
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:
|
|
|
|
response = dict(email=self.email, name=self.name)
|
|
|
|
|
|
|
|
self.backend.do_auth('fake-access-token', response=response)
|
|
|
|
kwargs = {'realm_subdomain': 'acme',
|
|
|
|
'response': response,
|
|
|
|
'return_data': {}}
|
|
|
|
result.assert_called_with(None, 'fake-access-token', **kwargs)
|
|
|
|
|
2016-08-02 11:07:45 +02:00
|
|
|
def test_github_backend_do_auth_for_team(self):
|
2016-08-08 10:57:34 +02:00
|
|
|
# type: () -> 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:
|
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)
|
|
|
|
|
2016-10-11 15:14:50 +02:00
|
|
|
kwargs = {'realm_subdomain': 'acme',
|
|
|
|
'response': response,
|
|
|
|
'return_data': {}}
|
|
|
|
result.assert_called_with(self.user_profile, 'fake-access-token', **kwargs)
|
|
|
|
|
2016-10-11 14:35:06 +02:00
|
|
|
def test_github_backend_do_auth_for_team_auth_failed(self):
|
|
|
|
# type: () -> 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:
|
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)
|
|
|
|
kwargs = {'realm_subdomain': 'acme',
|
|
|
|
'response': response,
|
|
|
|
'return_data': {}}
|
|
|
|
result.assert_called_with(None, 'fake-access-token', **kwargs)
|
|
|
|
|
2016-08-02 11:07:45 +02:00
|
|
|
def test_github_backend_do_auth_for_org(self):
|
2016-08-08 10:57:34 +02:00
|
|
|
# type: () -> 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:
|
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)
|
|
|
|
|
2016-10-11 15:14:50 +02:00
|
|
|
kwargs = {'realm_subdomain': 'acme',
|
|
|
|
'response': response,
|
|
|
|
'return_data': {}}
|
|
|
|
result.assert_called_with(self.user_profile, 'fake-access-token', **kwargs)
|
|
|
|
|
2016-10-11 14:35:06 +02:00
|
|
|
def test_github_backend_do_auth_for_org_auth_failed(self):
|
|
|
|
# type: () -> 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:
|
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)
|
|
|
|
kwargs = {'realm_subdomain': 'acme',
|
|
|
|
'response': response,
|
|
|
|
'return_data': {}}
|
|
|
|
result.assert_called_with(None, 'fake-access-token', **kwargs)
|
|
|
|
|
|
|
|
def test_github_backend_authenticate_nonexisting_user(self):
|
|
|
|
# type: () -> None
|
|
|
|
with mock.patch('zproject.backends.get_user_profile_by_email',
|
|
|
|
side_effect=UserProfile.DoesNotExist("Do not exist")):
|
2016-11-28 23:29:01 +01:00
|
|
|
response = dict(email=self.email, name=self.name)
|
2016-10-11 14:35:06 +02:00
|
|
|
return_data = dict() # type: Dict[str, Any]
|
|
|
|
user = self.backend.authenticate(return_data=return_data, response=response)
|
|
|
|
self.assertIs(user, None)
|
|
|
|
self.assertTrue(return_data['valid_attestation'])
|
|
|
|
|
2017-03-23 07:49:42 +01:00
|
|
|
def test_github_backend_authenticate_invalid_email(self):
|
|
|
|
# type: () -> None
|
|
|
|
response = dict(email=None, name=self.name)
|
|
|
|
return_data = dict() # type: Dict[str, Any]
|
|
|
|
user = self.backend.authenticate(return_data=return_data, response=response)
|
|
|
|
self.assertIs(user, None)
|
|
|
|
self.assertTrue(return_data['invalid_email'])
|
|
|
|
|
2016-07-25 11:24:36 +02:00
|
|
|
def test_github_backend_inactive_user(self):
|
2016-07-30 00:48:40 +02:00
|
|
|
# type: () -> None
|
2016-08-02 11:22:55 +02:00
|
|
|
def do_auth_inactive(*args, **kwargs):
|
2016-07-30 00:48:40 +02:00
|
|
|
# type: (*Any, **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)
|
|
|
|
|
|
|
|
def test_github_backend_new_user(self):
|
2016-07-30 00:48:40 +02:00
|
|
|
# type: () -> 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
|
|
|
|
|
2016-08-02 11:22:55 +02:00
|
|
|
def do_auth(*args, **kwargs):
|
2016-07-30 00:48:40 +02:00
|
|
|
# type: (*Any, **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['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-04-14 11:01:24 +02:00
|
|
|
self.assert_in_response('Your email address, {}, does not '
|
|
|
|
'correspond to any existing '
|
|
|
|
'organization.'.format(email), result)
|
2016-07-25 11:24:36 +02:00
|
|
|
|
2016-12-01 13:10:59 +01:00
|
|
|
def test_login_url(self):
|
|
|
|
# type: () -> None
|
|
|
|
result = self.client_get('/accounts/login/social/github')
|
|
|
|
self.assertIn(reverse('social:begin', args=['github']), result.url)
|
|
|
|
|
2017-02-28 11:58:03 +01:00
|
|
|
def test_github_complete(self):
|
|
|
|
# type: () -> None
|
|
|
|
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-03-07 08:32:40 +01:00
|
|
|
def test_github_complete_when_base_exc_is_raised(self):
|
|
|
|
# type: () -> None
|
|
|
|
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')), \
|
|
|
|
mock.patch('zproject.backends.logging.exception'):
|
|
|
|
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-03-23 07:49:42 +01:00
|
|
|
def test_github_complete_when_email_is_invalid(self):
|
|
|
|
# type: () -> None
|
|
|
|
from social_django import utils
|
|
|
|
utils.BACKENDS = ('zproject.backends.GitHubAuthBackend',)
|
|
|
|
with mock.patch('zproject.backends.GitHubAuthBackend.get_email_address',
|
2017-03-24 11:00:58 +01:00
|
|
|
return_value=None) as mock_get_email_address, \
|
|
|
|
mock.patch('social_core.backends.oauth.OAuthAuth.validate_state',
|
|
|
|
return_value='state'), \
|
|
|
|
mock.patch('social_core.backends.oauth.BaseOAuth2.request_access_token',
|
|
|
|
return_value={'access_token': 'token'}), \
|
|
|
|
mock.patch('social_core.backends.github.GithubOAuth2.do_auth',
|
|
|
|
side_effect=self.do_auth), \
|
|
|
|
mock.patch('zproject.backends.logging.warning'):
|
|
|
|
result = self.client_get(reverse('social:complete', args=['github']),
|
|
|
|
info={'state': 'state'})
|
|
|
|
self.assertEqual(result.status_code, 200)
|
2017-04-20 21:02:56 +02:00
|
|
|
self.assertIn("Sign up for Zulip", result.content.decode('utf8'))
|
2017-03-24 11:00:58 +01:00
|
|
|
self.assertEqual(mock_get_email_address.call_count, 2)
|
2017-03-23 07:49:42 +01:00
|
|
|
|
|
|
|
utils.BACKENDS = settings.AUTHENTICATION_BACKENDS
|
|
|
|
|
|
|
|
|
2016-09-13 21:30:18 +02:00
|
|
|
class ResponseMock(object):
|
|
|
|
def __init__(self, status_code, data):
|
|
|
|
# type: (int, Any) -> None
|
|
|
|
self.status_code = status_code
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
def json(self):
|
|
|
|
# type: () -> str
|
|
|
|
return self.data
|
|
|
|
|
|
|
|
@property
|
|
|
|
def text(self):
|
|
|
|
# type: () -> str
|
|
|
|
return "Response text"
|
|
|
|
|
2016-10-17 14:28:23 +02:00
|
|
|
class GoogleOAuthTest(ZulipTestCase):
|
2017-03-19 20:01:01 +01:00
|
|
|
def google_oauth2_test(self, token_response, account_response, subdomain=None,
|
|
|
|
mobile_flow_otp=None):
|
|
|
|
# type: (ResponseMock, ResponseMock, Optional[str], Optional[str]) -> 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-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
|
|
|
|
result = self.client_get(result.url)
|
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/",
|
|
|
|
dict(state=csrf_state))
|
|
|
|
return result
|
|
|
|
|
2016-10-17 14:28:23 +02:00
|
|
|
class GoogleSubdomainLoginTest(GoogleOAuthTest):
|
|
|
|
def get_signed_subdomain_cookie(self, data):
|
|
|
|
# type: (Dict[str, str]) -> Dict[str, str]
|
|
|
|
key = 'subdomain.signature'
|
|
|
|
salt = key + 'zerver.views.auth'
|
|
|
|
value = ujson.dumps(data)
|
|
|
|
return {key: signing.get_cookie_signer(salt=salt).sign(value)}
|
|
|
|
|
|
|
|
def test_google_oauth2_start(self):
|
|
|
|
# type: () -> None
|
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
|
|
|
|
result = self.client_get('/accounts/login/google/')
|
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)
|
|
|
|
subdomain = urllib.parse.parse_qs(parsed_url.query)['subdomain']
|
|
|
|
self.assertEqual(subdomain, ['zulip'])
|
|
|
|
|
|
|
|
def test_google_oauth2_success(self):
|
|
|
|
# type: () -> None
|
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
|
|
|
value="hamlet@zulip.com")])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
result = self.google_oauth2_test(token_response, account_response, 'zulip')
|
|
|
|
|
2017-04-19 10:04:23 +02:00
|
|
|
data = unsign_subdomain_cookie(result)
|
2016-10-17 14:28:23 +02:00
|
|
|
self.assertEqual(data['email'], 'hamlet@zulip.com')
|
|
|
|
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)
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(uri, 'http://zulip.testserver/accounts/login/subdomain/')
|
2016-10-17 14:28:23 +02:00
|
|
|
|
2017-03-19 20:01:01 +01:00
|
|
|
def test_google_oauth2_mobile_success(self):
|
|
|
|
# type: () -> None
|
|
|
|
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",
|
|
|
|
value="hamlet@zulip.com")])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
# Verify that the right thing happens with an invalid-format OTP
|
|
|
|
result = self.google_oauth2_test(token_response, account_response, 'zulip',
|
|
|
|
mobile_flow_otp="1234")
|
|
|
|
self.assert_json_error(result, "Invalid OTP")
|
|
|
|
result = self.google_oauth2_test(token_response, account_response, 'zulip',
|
|
|
|
mobile_flow_otp="invalido" * 8)
|
|
|
|
self.assert_json_error(result, "Invalid OTP")
|
|
|
|
|
|
|
|
# Now do it correctly
|
|
|
|
result = self.google_oauth2_test(token_response, account_response, 'zulip',
|
|
|
|
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'])
|
|
|
|
self.assertEqual(query_params["email"], ['hamlet@zulip.com'])
|
|
|
|
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))
|
|
|
|
|
2016-10-17 14:28:23 +02:00
|
|
|
def test_log_into_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
data = {'name': 'Full Name',
|
|
|
|
'email': 'hamlet@zulip.com',
|
|
|
|
'subdomain': 'zulip'}
|
|
|
|
|
|
|
|
self.client.cookies = SimpleCookie(self.get_signed_subdomain_cookie(data))
|
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
|
|
|
|
result = self.client_get('/accounts/login/subdomain/')
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 302)
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2016-10-17 14:28:23 +02:00
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
2017-03-25 20:44:14 +01:00
|
|
|
# If authenticate_remote_user detects a subdomain mismatch, then
|
|
|
|
# the result should redirect to the login page.
|
|
|
|
with mock.patch(
|
|
|
|
'zerver.views.auth.authenticate_remote_user',
|
|
|
|
return_value=(None, {'invalid_subdomain': True})):
|
|
|
|
result = self.client_get('/accounts/login/subdomain/')
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertTrue(result['Location'].endswith, '?subdomain=1')
|
|
|
|
|
2017-04-18 08:34:29 +02:00
|
|
|
def test_log_into_subdomain_when_email_is_none(self):
|
|
|
|
# type: () -> None
|
|
|
|
data = {'name': None,
|
|
|
|
'email': None,
|
|
|
|
'subdomain': 'zulip'}
|
|
|
|
|
|
|
|
self.client.cookies = SimpleCookie(self.get_signed_subdomain_cookie(data))
|
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'), \
|
|
|
|
mock.patch('logging.warning'):
|
|
|
|
result = self.client_get('/accounts/login/subdomain/')
|
|
|
|
self.assertEqual(result.status_code, 200)
|
2017-04-20 21:02:56 +02:00
|
|
|
self.assertIn("Sign up for Zulip", result.content.decode('utf8'))
|
2017-04-18 08:34:29 +02:00
|
|
|
|
2016-10-17 14:28:23 +02:00
|
|
|
def test_user_cannot_log_into_nonexisting_realm(self):
|
|
|
|
# type: () -> None
|
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
|
|
|
value="hamlet@zulip.com")])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
result = self.google_oauth2_test(token_response, account_response, 'acme')
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 302)
|
2016-10-17 14:28:23 +02:00
|
|
|
self.assertIn('subdomain=1', result.url)
|
|
|
|
|
|
|
|
def test_user_cannot_log_into_wrong_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
data = {'name': 'Full Name',
|
|
|
|
'email': 'hamlet@zulip.com',
|
|
|
|
'subdomain': 'acme'}
|
|
|
|
|
|
|
|
self.client.cookies = SimpleCookie(self.get_signed_subdomain_cookie(data))
|
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
|
|
|
|
result = self.client_get('/accounts/login/subdomain/')
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
2016-10-17 14:28:23 +02:00
|
|
|
|
|
|
|
def test_log_into_subdomain_when_signature_is_bad(self):
|
|
|
|
# type: () -> None
|
|
|
|
self.client.cookies = SimpleCookie({'subdomain.signature': 'invlaid'})
|
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
|
|
|
|
result = self.client_get('/accounts/login/subdomain/')
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
2016-10-17 14:28:23 +02:00
|
|
|
|
|
|
|
def test_log_into_subdomain_when_state_is_not_passed(self):
|
|
|
|
# type: () -> None
|
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
|
|
|
|
result = self.client_get('/accounts/login/subdomain/')
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 400)
|
2016-10-17 14:28:23 +02:00
|
|
|
|
2016-11-01 08:03:10 +01:00
|
|
|
def test_google_oauth2_registration(self):
|
|
|
|
# type: () -> None
|
|
|
|
"""If the user doesn't exist yet, Google auth can be used to register an account"""
|
2016-11-30 14:17:35 +01:00
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True), (
|
2017-01-24 07:06:13 +01:00
|
|
|
mock.patch('zerver.views.auth.get_subdomain', return_value='zulip')), (
|
|
|
|
mock.patch('zerver.views.registration.get_subdomain', return_value='zulip')):
|
2016-11-08 06:17:44 +01:00
|
|
|
|
2016-11-01 08:03:10 +01:00
|
|
|
email = "newuser@zulip.com"
|
|
|
|
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, 'zulip')
|
|
|
|
|
2017-04-19 10:04:23 +02:00
|
|
|
data = unsign_subdomain_cookie(result)
|
2016-11-01 08:03:10 +01:00
|
|
|
self.assertEqual(data['email'], email)
|
|
|
|
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-11-01 08:03:10 +01:00
|
|
|
parsed_url = urllib.parse.urlparse(result.url)
|
|
|
|
uri = "{}://{}{}".format(parsed_url.scheme, parsed_url.netloc,
|
|
|
|
parsed_url.path)
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(uri, 'http://zulip.testserver/accounts/login/subdomain/')
|
2016-11-01 08:03:10 +01:00
|
|
|
|
2016-11-08 06:17:44 +01:00
|
|
|
result = self.client_get(result.url)
|
2016-11-01 08:03:10 +01:00
|
|
|
result = self.client_get(result.url) # Call the confirmation url.
|
|
|
|
key_match = re.search('value="(?P<key>[0-9a-f]+)" name="key"', result.content.decode("utf-8"))
|
|
|
|
name_match = re.search('value="(?P<name>[^"]+)" name="full_name"', result.content.decode("utf-8"))
|
|
|
|
|
|
|
|
# This goes through a brief stop on a page that auto-submits via JS
|
|
|
|
result = self.client_post('/accounts/register/',
|
|
|
|
{'full_name': name_match.group("name"),
|
|
|
|
'key': key_match.group("key"),
|
|
|
|
'from_confirmation': "1"})
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 200)
|
2016-11-01 08:03:10 +01:00
|
|
|
result = self.client_post('/accounts/register/',
|
|
|
|
{'full_name': "New User",
|
|
|
|
'password': 'test_password',
|
|
|
|
'key': key_match.group("key"),
|
|
|
|
'terms': True})
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertEqual(result.url, "http://zulip.testserver/")
|
2016-11-01 08:03:10 +01:00
|
|
|
user_profile = get_user_profile_by_email(email)
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
2016-10-17 14:28:23 +02:00
|
|
|
class GoogleLoginTest(GoogleOAuthTest):
|
2016-09-13 21:30:18 +02:00
|
|
|
def test_google_oauth2_success(self):
|
|
|
|
# type: () -> None
|
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
|
|
|
value="hamlet@zulip.com")])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
self.google_oauth2_test(token_response, account_response)
|
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2016-09-13 21:30:18 +02:00
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
|
|
|
def test_google_oauth2_registration(self):
|
|
|
|
# type: () -> None
|
|
|
|
"""If the user doesn't exist yet, Google auth can be used to register an account"""
|
|
|
|
email = "newuser@zulip.com"
|
|
|
|
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)
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
|
|
|
|
result = self.client_get(result.url)
|
|
|
|
key_match = re.search('value="(?P<key>[0-9a-f]+)" name="key"', result.content.decode("utf-8"))
|
|
|
|
name_match = re.search('value="(?P<name>[^"]+)" name="full_name"', result.content.decode("utf-8"))
|
|
|
|
|
|
|
|
# This goes through a brief stop on a page that auto-submits via JS
|
|
|
|
result = self.client_post('/accounts/register/',
|
|
|
|
{'full_name': name_match.group("name"),
|
|
|
|
'key': key_match.group("key"),
|
|
|
|
'from_confirmation': "1"})
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 200)
|
2016-09-13 21:30:18 +02:00
|
|
|
result = self.client_post('/accounts/register/',
|
|
|
|
{'full_name': "New User",
|
|
|
|
'password': 'test_password',
|
|
|
|
'key': key_match.group("key"),
|
|
|
|
'terms': True})
|
2016-12-16 02:01:34 +01:00
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertEqual(result.url, "http://testserver/")
|
2016-09-13 21:30:18 +02:00
|
|
|
|
2016-10-07 11:16:49 +02:00
|
|
|
def test_google_oauth2_wrong_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(formatted="Full Name"),
|
|
|
|
emails=[dict(type="account",
|
|
|
|
value="hamlet@zulip.com")])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
result = self.google_oauth2_test(token_response, account_response)
|
|
|
|
self.assertIn('subdomain=1', result.url)
|
|
|
|
|
2016-09-13 21:30:18 +02:00
|
|
|
def test_google_oauth2_400_token_response(self):
|
|
|
|
# type: () -> None
|
|
|
|
token_response = ResponseMock(400, {})
|
|
|
|
with mock.patch("logging.warning") as m:
|
|
|
|
result = self.google_oauth2_test(token_response, None)
|
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
|
|
|
|
|
|
|
def test_google_oauth2_500_token_response(self):
|
|
|
|
# type: () -> None
|
|
|
|
token_response = ResponseMock(500, {})
|
|
|
|
with mock.patch("logging.error") as m:
|
|
|
|
result = self.google_oauth2_test(token_response, None)
|
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
|
|
|
|
|
|
|
def test_google_oauth2_400_account_response(self):
|
|
|
|
# type: () -> None
|
|
|
|
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
|
|
|
|
|
|
|
def test_google_oauth2_500_account_response(self):
|
|
|
|
# type: () -> None
|
|
|
|
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
|
|
|
|
|
|
|
def test_google_oauth2_no_fullname(self):
|
|
|
|
# type: () -> None
|
|
|
|
token_response = ResponseMock(200, {'access_token': "unique_token"})
|
|
|
|
account_data = dict(name=dict(givenName="Test", familyName="User"),
|
|
|
|
emails=[dict(type="account",
|
|
|
|
value="hamlet@zulip.com")])
|
|
|
|
account_response = ResponseMock(200, account_data)
|
|
|
|
self.google_oauth2_test(token_response, account_response)
|
|
|
|
|
2017-05-07 17:21:26 +02:00
|
|
|
user_profile = self.example_user('hamlet')
|
2016-09-13 21:30:18 +02:00
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
|
|
|
|
|
|
|
def test_google_oauth2_account_response_no_email(self):
|
|
|
|
# type: () -> None
|
|
|
|
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:
|
|
|
|
result = self.google_oauth2_test(token_response, account_response)
|
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
|
|
|
|
|
|
|
def test_google_oauth2_error_access_denied(self):
|
|
|
|
# type: () -> None
|
|
|
|
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
|
|
|
|
|
|
|
def test_google_oauth2_error_other(self):
|
|
|
|
# type: () -> None
|
|
|
|
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
|
|
|
|
|
|
|
def test_google_oauth2_missing_csrf(self):
|
|
|
|
# type: () -> None
|
|
|
|
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
|
|
|
|
|
|
|
def test_google_oauth2_csrf_malformed(self):
|
|
|
|
# type: () -> None
|
|
|
|
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
|
|
|
|
|
|
|
def test_google_oauth2_csrf_badstate(self):
|
|
|
|
# type: () -> None
|
|
|
|
with mock.patch("logging.warning") as m:
|
2017-03-19 20:01:01 +01: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
|
|
|
|
2016-08-23 02:08:42 +02:00
|
|
|
class FetchAPIKeyTest(ZulipTestCase):
|
2016-04-21 21:07:43 +02:00
|
|
|
def setUp(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> 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
|
|
|
|
|
|
|
def test_success(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> 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-04-07 08:21:29 +02:00
|
|
|
def test_invalid_email(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
2016-04-21 21:07:43 +02:00
|
|
|
def test_wrong_password(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> 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-03-25 20:44:14 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GoogleMobileOauth2Backend',))
|
|
|
|
def test_google_oauth2_token_success(self):
|
|
|
|
# type: () -> None
|
|
|
|
with mock.patch(
|
|
|
|
'apiclient.sample_tools.client.verify_id_token',
|
|
|
|
return_value={
|
|
|
|
"email_verified": True,
|
|
|
|
"email": "hamlet@zulip.com",
|
|
|
|
}):
|
|
|
|
result = self.client_post("/api/v1/fetch_api_key",
|
|
|
|
dict(username="google-oauth2-token",
|
|
|
|
password="token"))
|
|
|
|
self.assert_json_success(result)
|
|
|
|
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GoogleMobileOauth2Backend',))
|
|
|
|
def test_google_oauth2_token_failure(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.GoogleMobileOauth2Backend',))
|
|
|
|
def test_google_oauth2_token_unregistered(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
2016-04-21 21:07:43 +02:00
|
|
|
def test_password_auth_disabled(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> 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',))
|
|
|
|
def test_ldap_auth_email_auth_disabled_success(self):
|
|
|
|
# type: () -> None
|
|
|
|
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()
|
|
|
|
|
2016-04-21 21:07:43 +02:00
|
|
|
def test_inactive_user(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> 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)
|
|
|
|
|
|
|
|
def test_deactivated_realm(self):
|
2016-06-04 20:28:02 +02:00
|
|
|
# type: () -> 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):
|
2016-06-01 02:28:27 +02:00
|
|
|
def setUp(self):
|
|
|
|
# type: () -> 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
|
|
|
|
|
|
|
def test_success(self):
|
|
|
|
# type: () -> 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)
|
|
|
|
data = ujson.loads(result.content)
|
|
|
|
self.assertEqual(data["email"], self.email)
|
|
|
|
self.assertEqual(data['api_key'], self.user_profile.api_key)
|
|
|
|
|
2017-04-07 08:21:29 +02:00
|
|
|
def test_invalid_email(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
2016-06-01 02:28:27 +02:00
|
|
|
def test_inactive_user(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
|
|
|
def test_deactivated_realm(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
|
|
|
def test_dev_auth_disabled(self):
|
|
|
|
# type: () -> 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):
|
2016-06-01 02:28:43 +02:00
|
|
|
def test_success(self):
|
|
|
|
# type: () -> 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
|
|
|
|
|
|
|
def test_dev_auth_disabled(self):
|
|
|
|
# type: () -> 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-05-04 01:13:56 +02:00
|
|
|
def assert_on_error(self, error):
|
|
|
|
# type: (Optional[str]) -> None
|
|
|
|
if error:
|
|
|
|
raise AssertionError(error)
|
|
|
|
|
|
|
|
def test_get_server_settings(self):
|
|
|
|
# type: () -> None
|
|
|
|
result = self.client_get("/api/v1/server_settings")
|
|
|
|
self.assert_json_success(result)
|
|
|
|
data = ujson.loads(result.content)
|
|
|
|
schema_checker = check_dict_only([
|
|
|
|
('authentication_methods', check_dict_only([
|
|
|
|
('google', check_bool),
|
|
|
|
('github', check_bool),
|
|
|
|
('dev', check_bool),
|
|
|
|
('password', check_bool),
|
|
|
|
])),
|
|
|
|
('realm_uri', check_string),
|
|
|
|
('zulip_version', check_string),
|
|
|
|
('msg', check_string),
|
|
|
|
('result', check_string),
|
|
|
|
])
|
|
|
|
self.assert_on_error(schema_checker("data", data))
|
|
|
|
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
SUBDOMAINS_HOMEPAGE=False):
|
|
|
|
result = self.client_get("/api/v1/server_settings",
|
|
|
|
HTTP_HOST="zulip.testserver")
|
|
|
|
self.assert_json_success(result)
|
|
|
|
data = ujson.loads(result.content)
|
|
|
|
with_realm_schema_checker = check_dict_only([
|
|
|
|
('zulip_version', check_string),
|
|
|
|
('realm_uri', check_string),
|
|
|
|
('realm_name', check_string),
|
|
|
|
('realm_description', check_string),
|
|
|
|
('realm_icon', check_string),
|
|
|
|
('authentication_methods', check_dict_only([
|
|
|
|
('google', check_bool),
|
|
|
|
('github', check_bool),
|
|
|
|
('dev', check_bool),
|
|
|
|
('password', check_bool),
|
|
|
|
])),
|
|
|
|
('msg', check_string),
|
|
|
|
('result', check_string),
|
|
|
|
])
|
|
|
|
self.assert_on_error(with_realm_schema_checker("data", data))
|
|
|
|
|
2016-06-21 03:32:23 +02:00
|
|
|
def test_fetch_auth_backend_format(self):
|
|
|
|
# type: () -> 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)
|
|
|
|
data = ujson.loads(result.content)
|
|
|
|
self.assertEqual(set(data.keys()),
|
2017-04-27 23:34:44 +02:00
|
|
|
{'msg', 'password', 'github', 'google', 'dev', 'result', '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))
|
|
|
|
|
|
|
|
def test_fetch_auth_backend(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
data = ujson.loads(result.content)
|
|
|
|
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,
|
|
|
|
'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
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
SUBDOMAINS_HOMEPAGE=False):
|
|
|
|
result = self.client_get("/api/v1/get_auth_backends")
|
|
|
|
self.assert_json_success(result)
|
|
|
|
data = ujson.loads(result.content)
|
|
|
|
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,
|
|
|
|
'dev': True,
|
|
|
|
'result': 'success',
|
|
|
|
'zulip_version': ZULIP_VERSION,
|
|
|
|
})
|
|
|
|
|
|
|
|
# Verify invalid subdomain
|
|
|
|
result = self.client_get("/api/v1/get_auth_backends",
|
|
|
|
HTTP_HOST="invalid.testserver")
|
|
|
|
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",
|
|
|
|
HTTP_HOST="zulip.testserver")
|
|
|
|
self.assert_json_success(result)
|
|
|
|
data = ujson.loads(result.content)
|
|
|
|
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,
|
|
|
|
'dev': True,
|
|
|
|
'result': 'success',
|
|
|
|
'zulip_version': ZULIP_VERSION,
|
|
|
|
})
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
SUBDOMAINS_HOMEPAGE=True):
|
|
|
|
# With SUBDOMAINS_HOMEPAGE, homepage fails
|
|
|
|
result = self.client_get("/api/v1/get_auth_backends",
|
|
|
|
HTTP_HOST="testserver")
|
|
|
|
self.assert_json_error_contains(result, "Subdomain required", 400)
|
|
|
|
|
|
|
|
# With SUBDOMAINS_HOMEPAGE, subdomain pages succeed
|
|
|
|
result = self.client_get("/api/v1/get_auth_backends",
|
|
|
|
HTTP_HOST="zulip.testserver")
|
|
|
|
self.assert_json_success(result)
|
|
|
|
data = ujson.loads(result.content)
|
|
|
|
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,
|
|
|
|
'dev': True,
|
|
|
|
'result': 'success',
|
|
|
|
'zulip_version': ZULIP_VERSION,
|
|
|
|
})
|
|
|
|
|
2016-10-24 08:35:16 +02:00
|
|
|
class TestDevAuthBackend(ZulipTestCase):
|
|
|
|
def test_login_success(self):
|
|
|
|
# type: () -> 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-03-25 20:44:14 +01:00
|
|
|
def test_login_with_subdomain(self):
|
|
|
|
# type: () -> 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}
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True):
|
|
|
|
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)
|
|
|
|
|
2016-10-24 08:35:16 +02:00
|
|
|
def test_login_failure(self):
|
|
|
|
# type: () -> None
|
|
|
|
email = 'hamlet@zulip.com'
|
|
|
|
data = {'direct_email': email}
|
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.EmailAuthBackend',)):
|
2016-12-16 02:11:42 +01:00
|
|
|
with self.assertRaisesRegex(Exception, 'Direct login not supported.'):
|
2017-03-05 08:37:39 +01:00
|
|
|
with mock.patch('django.core.handlers.exception.logger'):
|
|
|
|
self.client_post('/accounts/login/local/', data)
|
2016-10-24 08:35:16 +02:00
|
|
|
|
|
|
|
def test_login_failure_due_to_nonexistent_user(self):
|
|
|
|
# type: () -> None
|
|
|
|
email = 'nonexisting@zulip.com'
|
|
|
|
data = {'direct_email': email}
|
2016-12-16 02:11:42 +01:00
|
|
|
with self.assertRaisesRegex(Exception, 'User cannot login'):
|
2017-03-05 08:37:39 +01:00
|
|
|
with mock.patch('django.core.handlers.exception.logger'):
|
|
|
|
self.client_post('/accounts/login/local/', data)
|
2016-10-24 09:09:31 +02:00
|
|
|
|
|
|
|
class TestZulipRemoteUserBackend(ZulipTestCase):
|
|
|
|
def test_login_success(self):
|
|
|
|
# type: () -> 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)
|
|
|
|
|
2016-10-26 14:01:30 +02:00
|
|
|
def test_authenticate_with_missing_user(self):
|
|
|
|
# type: () -> None
|
|
|
|
backend = ZulipRemoteUserBackend()
|
|
|
|
self.assertIs(backend.authenticate(None), None)
|
|
|
|
|
2016-10-24 09:09:31 +02:00
|
|
|
def test_login_success_with_sso_append_domain(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
|
|
|
def test_login_failure(self):
|
|
|
|
# type: () -> None
|
|
|
|
email = 'hamlet@zulip.com'
|
|
|
|
result = self.client_post('/accounts/login/sso/', REMOTE_USER=email)
|
|
|
|
self.assertEqual(result.status_code, 200) # This should ideally be not 200.
|
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
|
|
|
|
|
|
|
def test_login_failure_due_to_nonexisting_user(self):
|
|
|
|
# type: () -> None
|
|
|
|
email = 'nonexisting@zulip.com'
|
|
|
|
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',)):
|
|
|
|
result = self.client_post('/accounts/login/sso/', REMOTE_USER=email)
|
|
|
|
self.assertEqual(result.status_code, 302)
|
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
|
|
|
|
2017-04-07 08:21:29 +02:00
|
|
|
def test_login_failure_due_to_invalid_email(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
2016-10-24 09:09:31 +02:00
|
|
|
def test_login_failure_due_to_missing_field(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
|
|
|
def test_login_failure_due_to_wrong_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
email = 'hamlet@zulip.com'
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',)):
|
|
|
|
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-04-20 21:02:56 +02:00
|
|
|
self.assertIn(b"Sign up for Zulip", result.content)
|
2016-10-24 09:09:31 +02:00
|
|
|
|
|
|
|
def test_login_failure_due_to_empty_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
email = 'hamlet@zulip.com'
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
AUTHENTICATION_BACKENDS=('zproject.backends.ZulipRemoteUserBackend',)):
|
|
|
|
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-04-20 21:02:56 +02:00
|
|
|
self.assertIn(b"Sign up for Zulip", result.content)
|
2016-10-24 09:09:31 +02:00
|
|
|
|
|
|
|
def test_login_success_under_subdomains(self):
|
|
|
|
# type: () -> 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(
|
|
|
|
REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
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
|
|
|
|
|
|
|
class TestJWTLogin(ZulipTestCase):
|
|
|
|
"""
|
|
|
|
JWT uses ZulipDummyBackend.
|
|
|
|
"""
|
2016-11-29 07:22:02 +01:00
|
|
|
|
2016-10-24 11:38:38 +02:00
|
|
|
def test_login_success(self):
|
|
|
|
# type: () -> None
|
|
|
|
payload = {'user': 'hamlet', 'realm': 'zulip.com'}
|
|
|
|
with self.settings(JWT_AUTH_KEYS={'': 'key'}):
|
|
|
|
email = 'hamlet@zulip.com'
|
|
|
|
auth_key = settings.JWT_AUTH_KEYS['']
|
|
|
|
web_token = jwt.encode(payload, auth_key).decode('utf8')
|
|
|
|
|
|
|
|
user_profile = get_user_profile_by_email(email)
|
|
|
|
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)
|
|
|
|
|
|
|
|
def test_login_failure_when_user_is_missing(self):
|
|
|
|
# type: () -> None
|
|
|
|
payload = {'realm': 'zulip.com'}
|
|
|
|
with self.settings(JWT_AUTH_KEYS={'': 'key'}):
|
|
|
|
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, "No user specified in JSON web token claims", 400)
|
|
|
|
|
|
|
|
def test_login_failure_when_realm_is_missing(self):
|
|
|
|
# type: () -> None
|
|
|
|
payload = {'user': 'hamlet'}
|
|
|
|
with self.settings(JWT_AUTH_KEYS={'': 'key'}):
|
|
|
|
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, "No realm specified in JSON web token claims", 400)
|
|
|
|
|
|
|
|
def test_login_failure_when_key_does_not_exist(self):
|
|
|
|
# type: () -> None
|
|
|
|
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)
|
|
|
|
|
|
|
|
def test_login_failure_when_key_is_missing(self):
|
|
|
|
# type: () -> None
|
|
|
|
with self.settings(JWT_AUTH_KEYS={'': 'key'}):
|
|
|
|
result = self.client_post('/accounts/login/jwt/')
|
|
|
|
self.assert_json_error_contains(result, "No JSON web token passed in request", 400)
|
|
|
|
|
|
|
|
def test_login_failure_when_bad_token_is_passed(self):
|
|
|
|
# type: () -> None
|
|
|
|
with self.settings(JWT_AUTH_KEYS={'': 'key'}):
|
|
|
|
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)
|
|
|
|
|
|
|
|
def test_login_failure_when_user_does_not_exist(self):
|
|
|
|
# type: () -> None
|
|
|
|
payload = {'user': 'nonexisting', 'realm': 'zulip.com'}
|
|
|
|
with self.settings(JWT_AUTH_KEYS={'': 'key'}):
|
|
|
|
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.assertEqual(result.status_code, 302) # This should ideally be not 200.
|
|
|
|
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)
|
|
|
|
self.assertEqual(result.status_code, 302) # This should ideally be not 200.
|
|
|
|
self.assertIs(get_session_dict_user(self.client.session), None)
|
|
|
|
|
2016-10-24 11:38:38 +02:00
|
|
|
def test_login_failure_due_to_wrong_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
payload = {'user': 'hamlet', 'realm': 'zulip.com'}
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True, JWT_AUTH_KEYS={'acme': 'key'}):
|
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='acme'):
|
|
|
|
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)
|
|
|
|
|
|
|
|
def test_login_failure_due_to_empty_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
payload = {'user': 'hamlet', 'realm': 'zulip.com'}
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True, JWT_AUTH_KEYS={'': 'key'}):
|
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value=''):
|
|
|
|
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)
|
|
|
|
|
|
|
|
def test_login_success_under_subdomains(self):
|
|
|
|
# type: () -> None
|
|
|
|
payload = {'user': 'hamlet', 'realm': 'zulip.com'}
|
|
|
|
with self.settings(REALMS_HAVE_SUBDOMAINS=True, JWT_AUTH_KEYS={'zulip': 'key'}):
|
|
|
|
with mock.patch('zerver.views.auth.get_subdomain', return_value='zulip'):
|
|
|
|
email = 'hamlet@zulip.com'
|
|
|
|
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)
|
|
|
|
user_profile = get_user_profile_by_email(email)
|
|
|
|
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
|
2016-10-24 14:41:45 +02:00
|
|
|
|
|
|
|
class TestLDAP(ZulipTestCase):
|
|
|
|
def setUp(self):
|
|
|
|
# type: () -> 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
|
|
|
|
2016-11-07 01:33:20 +01:00
|
|
|
def tearDown(self):
|
2016-10-24 14:41:45 +02:00
|
|
|
# type: () -> None
|
|
|
|
self.mock_ldap.reset()
|
|
|
|
self.mock_initialize.stop()
|
|
|
|
|
|
|
|
def setup_subdomain(self, user_profile):
|
|
|
|
# type: (UserProfile) -> None
|
|
|
|
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',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_login_success(self):
|
|
|
|
# type: () -> None
|
|
|
|
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'):
|
|
|
|
user_profile = self.backend.authenticate('hamlet@zulip.com', 'testing')
|
|
|
|
self.assertEqual(user_profile.email, 'hamlet@zulip.com')
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_login_failure_due_to_wrong_password(self):
|
|
|
|
# type: () -> None
|
|
|
|
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'):
|
2016-12-13 10:59:54 +01:00
|
|
|
user = self.backend.authenticate('hamlet@zulip.com', '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',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_login_failure_due_to_nonexistent_user(self):
|
|
|
|
# type: () -> None
|
|
|
|
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'):
|
2016-12-13 10:59:54 +01: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',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_ldap_permissions(self):
|
|
|
|
# type: () -> None
|
|
|
|
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',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_django_to_ldap_username(self):
|
|
|
|
# type: () -> None
|
|
|
|
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',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_ldap_to_django_username(self):
|
|
|
|
# type: () -> None
|
|
|
|
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',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_get_or_create_user_when_user_exists(self):
|
|
|
|
# type: () -> None
|
|
|
|
class _LDAPUser(object):
|
|
|
|
attrs = {'fn': ['Full Name'], 'sn': ['Short Name']}
|
|
|
|
|
|
|
|
backend = self.backend
|
|
|
|
email = 'hamlet@zulip.com'
|
|
|
|
user_profile, created = backend.get_or_create_user(email, _LDAPUser())
|
|
|
|
self.assertFalse(created)
|
|
|
|
self.assertEqual(user_profile.email, email)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_get_or_create_user_when_user_does_not_exist(self):
|
|
|
|
# type: () -> None
|
|
|
|
class _LDAPUser(object):
|
|
|
|
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',))
|
|
|
|
def test_get_or_create_user_when_user_has_invalid_name(self):
|
|
|
|
# type: () -> None
|
|
|
|
class _LDAPUser(object):
|
|
|
|
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',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_get_or_create_user_when_realm_is_deactivated(self):
|
|
|
|
# type: () -> None
|
|
|
|
class _LDAPUser(object):
|
|
|
|
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())
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_django_to_ldap_username_when_domain_does_not_match(self):
|
|
|
|
# type: () -> None
|
|
|
|
backend = self.backend
|
|
|
|
email = 'hamlet@zulip.com'
|
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)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_login_failure_due_to_wrong_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
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('hamlet@zulip.com', 'testing',
|
|
|
|
realm_subdomain='acme')
|
|
|
|
self.assertIs(user_profile, None)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_login_failure_due_to_empty_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
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('hamlet@zulip.com', 'testing',
|
|
|
|
realm_subdomain='')
|
|
|
|
self.assertIs(user_profile, None)
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_login_success_when_subdomain_is_none(self):
|
|
|
|
# type: () -> None
|
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
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('hamlet@zulip.com', 'testing',
|
|
|
|
realm_subdomain=None)
|
|
|
|
self.assertEqual(user_profile.email, 'hamlet@zulip.com')
|
|
|
|
|
2016-11-07 00:09:21 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
2016-10-24 14:41:45 +02:00
|
|
|
def test_login_success_with_valid_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=hamlet,ou=users,dc=zulip,dc=com': {
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
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('hamlet@zulip.com', 'testing',
|
|
|
|
realm_subdomain='zulip')
|
|
|
|
self.assertEqual(user_profile.email, 'hamlet@zulip.com')
|
2016-10-26 12:46:51 +02:00
|
|
|
|
2017-01-22 11:21:27 +01:00
|
|
|
@override_settings(AUTHENTICATION_BACKENDS=('zproject.backends.ZulipLDAPAuthBackend',))
|
|
|
|
def test_login_success_when_user_does_not_exist_with_valid_subdomain(self):
|
|
|
|
# type: () -> None
|
|
|
|
self.mock_ldap.directory = {
|
|
|
|
'uid=nonexisting,ou=users,dc=acme,dc=com': {
|
|
|
|
'cn': ['NonExisting', ],
|
|
|
|
'userPassword': 'testing'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
with self.settings(
|
|
|
|
REALMS_HAVE_SUBDOMAINS=True,
|
|
|
|
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',
|
|
|
|
realm_subdomain='zulip')
|
|
|
|
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):
|
|
|
|
def test_authenticate(self):
|
|
|
|
# type: () -> None
|
|
|
|
backend = ZulipLDAPUserPopulator()
|
|
|
|
result = backend.authenticate('hamlet@zulip.com', 'testing') # type: ignore # complains that the function does not return any value!
|
|
|
|
self.assertIs(result, None)
|
2016-10-26 12:39:09 +02:00
|
|
|
|
|
|
|
class TestZulipAuthMixin(ZulipTestCase):
|
|
|
|
def test_get_user(self):
|
|
|
|
# type: () -> None
|
|
|
|
backend = ZulipAuthMixin()
|
|
|
|
result = backend.get_user(11111)
|
|
|
|
self.assertIs(result, None)
|
2016-10-26 13:50:00 +02:00
|
|
|
|
|
|
|
class TestPasswordAuthEnabled(ZulipTestCase):
|
|
|
|
def test_password_auth_enabled_for_ldap(self):
|
|
|
|
# type: () -> None
|
|
|
|
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
|
|
|
|
|
|
|
class TestMaybeSendToRegistration(ZulipTestCase):
|
|
|
|
def test_sso_only_when_preregistration_user_does_not_exist(self):
|
|
|
|
# type: () -> None
|
|
|
|
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.
|
|
|
|
class Form(object):
|
|
|
|
def is_valid(self):
|
|
|
|
# type: () -> bool
|
|
|
|
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)
|
|
|
|
result = maybe_send_to_registration(request, 'hamlet@zulip.com')
|
|
|
|
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)
|
|
|
|
|
2016-10-26 14:40:14 +02:00
|
|
|
def test_sso_only_when_preregistration_user_exists(self):
|
|
|
|
# type: () -> None
|
|
|
|
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.
|
|
|
|
class Form(object):
|
|
|
|
def is_valid(self):
|
|
|
|
# type: () -> bool
|
|
|
|
return True
|
|
|
|
|
|
|
|
email = 'hamlet@zulip.com'
|
|
|
|
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):
|
|
|
|
|
|
|
|
def test_change_enabled_backends(self):
|
|
|
|
# type: () -> None
|
|
|
|
# Log in as admin
|
|
|
|
self.login("iago@zulip.com")
|
|
|
|
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
|
|
|
|
|
|
|
def test_disable_all_backends(self):
|
|
|
|
# type: () -> None
|
|
|
|
# Log in as admin
|
|
|
|
self.login("iago@zulip.com")
|
|
|
|
result = self.client_patch("/json/realm", {
|
2016-12-02 00:08:34 +01:00
|
|
|
'authentication_methods': ujson.dumps({u'Email': False, u'Dev': False})})
|
2016-11-02 21:51:56 +01:00
|
|
|
self.assert_json_error(result, 'At least one authentication method must be enabled.', status_code=403)
|
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
|
|
|
|
|
|
|
def test_supported_backends_only_updated(self):
|
|
|
|
# type: () -> None
|
|
|
|
# Log in as admin
|
|
|
|
self.login("iago@zulip.com")
|
|
|
|
# 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
|
|
|
|
|
|
|
class LoginEmailValidatorTestCase(TestCase):
|
|
|
|
def test_valid_email(self):
|
|
|
|
# type: () -> None
|
|
|
|
validate_login_email(u'hamlet@zulip.com')
|
|
|
|
|
|
|
|
def test_invalid_email(self):
|
|
|
|
# type: () -> None
|
|
|
|
with self.assertRaises(JsonableError):
|
|
|
|
validate_login_email(u'hamlet')
|