tests: Extract SocialAuthBase class.

Extracts out common tests so that future social-auth backends can
be tested without duplicating tests. I have been careful to not
change any testing logic.
This commit is contained in:
Harshit Bansal
2019-02-03 08:18:01 +00:00
committed by Tim Abbott
parent 61ebee6993
commit ae3881b472

View File

@@ -10,6 +10,7 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from oauth2client.crypt import AppIdentityError from oauth2client.crypt import AppIdentityError
from django.core import signing from django.core import signing
from django.urls import reverse from django.urls import reverse
import httpretty import httpretty
import os import os
@@ -404,12 +405,25 @@ class ResponseMock:
def text(self) -> str: def text(self) -> str:
return "Response text" return "Response text"
class GitHubAuthBackendTest(ZulipTestCase): class SocialAuthBase(ZulipTestCase):
"""This is a base class for testing social-auth backends. Following
methods can be overrided by subclasses as per the backend:
registerExtraEndpoints() - If the backend being tested calls some extra
endpoints then they can be added here.
get_account_data_dict() - Return the data returned by the user info endpoint
according to the respective backend.
"""
# Don't run base class tests, make sure to set it to False
# in subclass otherwise its tests will not run.
__unittest_skip__ = True
def setUp(self) -> None: def setUp(self) -> None:
self.user_profile = self.example_user('hamlet') self.user_profile = self.example_user('hamlet')
self.email = self.user_profile.email self.email = self.user_profile.email
self.name = 'Hamlet' self.name = 'Hamlet'
self.backend = GitHubAuthBackend() self.backend = self.BACKEND_CLASS
self.backend.strategy = DjangoStrategy(storage=BaseDjangoStorage()) self.backend.strategy = DjangoStrategy(storage=BaseDjangoStorage())
self.user_profile.backend = self.backend self.user_profile.backend = self.backend
@@ -421,13 +435,13 @@ class GitHubAuthBackendTest(ZulipTestCase):
from social_core.backends.utils import load_backends from social_core.backends.utils import load_backends
load_backends(settings.AUTHENTICATION_BACKENDS, force_load=True) load_backends(settings.AUTHENTICATION_BACKENDS, force_load=True)
def github_oauth2_test(self, account_data_dict: Dict[str, str], def social_auth_test(self, account_data_dict: Dict[str, str],
*, subdomain: Optional[str]=None, *, subdomain: Optional[str]=None,
mobile_flow_otp: Optional[str]=None, mobile_flow_otp: Optional[str]=None,
is_signup: Optional[str]=None, is_signup: Optional[str]=None,
email_data: Optional[List[Dict[str, Any]]]=None, next: str='',
next: str='') -> HttpResponse: **extra_data: Any) -> HttpResponse:
url = "/accounts/login/social/github" url = self.LOGIN_URL
params = {} params = {}
headers = {} headers = {}
if subdomain is not None: if subdomain is not None:
@@ -436,89 +450,70 @@ class GitHubAuthBackendTest(ZulipTestCase):
params['mobile_flow_otp'] = mobile_flow_otp params['mobile_flow_otp'] = mobile_flow_otp
headers['HTTP_USER_AGENT'] = "ZulipAndroid" headers['HTTP_USER_AGENT'] = "ZulipAndroid"
if is_signup is not None: if is_signup is not None:
url = "/accounts/register/social/github" url = self.SIGNUP_URL
params['next'] = next params['next'] = next
if len(params) > 0: if len(params) > 0:
url += "?%s" % (urllib.parse.urlencode(params)) url += "?%s" % (urllib.parse.urlencode(params))
result = self.client_get(url, **headers) result = self.client_get(url, **headers)
expected_result_url_prefix = 'http://testserver/login/github/' expected_result_url_prefix = 'http://testserver/login/%s/' % (self.backend.name,)
if settings.SOCIAL_AUTH_SUBDOMAIN is not None: if settings.SOCIAL_AUTH_SUBDOMAIN is not None:
expected_result_url_prefix = ('http://%s.testserver/login/github/' % expected_result_url_prefix = ('http://%s.testserver/login/%s/' %
settings.SOCIAL_AUTH_SUBDOMAIN) (settings.SOCIAL_AUTH_SUBDOMAIN, self.backend.name,))
if result.status_code != 302 or not result.url.startswith(expected_result_url_prefix): if result.status_code != 302 or not result.url.startswith(expected_result_url_prefix):
return result return result
result = self.client_get(result.url, **headers) result = self.client_get(result.url, **headers)
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
assert 'https://github.com/login/oauth/authorize' in result.url assert self.AUTHORIZATION_URL in result.url
self.client.cookies = result.cookies self.client.cookies = result.cookies
# Next, the browser requests result["Location"], and gets # Next, the browser requests result["Location"], and gets
# redirected back to /complete/github. # redirected back to the registered redirect uri.
token_data_dict = { token_data_dict = {
'access_token': 'foobar', 'access_token': 'foobar',
'token_type': 'bearer' 'token_type': 'bearer'
} }
if not email_data: # We register callbacks for the key URLs on Identity Provider that
# Keeping a verified email before the primary email makes sure # auth completion url will call
# get_verified_emails puts the primary email at the start of the
# email list returned as social_associate_user_helper assumes the
# first email as the primary email.
email_data = [
dict(email="notprimary@example.com",
verified=True),
dict(email=account_data_dict["email"],
verified=True,
primary=True),
dict(email="ignored@example.com",
verified=False),
]
# We register callbacks for the key URLs on github.com that
# /complete/github will call
httpretty.enable() httpretty.enable()
httpretty.register_uri( httpretty.register_uri(
httpretty.POST, httpretty.POST,
"https://github.com/login/oauth/access_token", self.ACCESS_TOKEN_URL,
match_querystring=False, match_querystring=False,
status=200, status=200,
body=json.dumps(token_data_dict)) body=json.dumps(token_data_dict))
httpretty.register_uri( httpretty.register_uri(
httpretty.GET, httpretty.GET,
"https://api.github.com/user", self.USER_INFO_URL,
status=200, status=200,
body=json.dumps(account_data_dict) body=json.dumps(account_data_dict)
) )
httpretty.register_uri( self.registerExtraEndpoints(account_data_dict, **extra_data)
httpretty.GET,
"https://api.github.com/user/emails",
status=200,
body=json.dumps(email_data)
)
parsed_url = urllib.parse.urlparse(result.url) parsed_url = urllib.parse.urlparse(result.url)
csrf_state = urllib.parse.parse_qs(parsed_url.query)['state'] csrf_state = urllib.parse.parse_qs(parsed_url.query)['state']
result = self.client_get("/complete/github/", result = self.client_get(self.AUTH_FINISH_URL,
dict(state=csrf_state), **headers) dict(state=csrf_state), **headers)
httpretty.disable() httpretty.disable()
return result return result
@override_settings(SOCIAL_AUTH_GITHUB_KEY=None) def test_social_auth_no_key(self) -> None:
def test_github_oauth2_no_key(self) -> None: account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
account_data_dict = dict(email=self.email, name=self.name) with self.settings(**{self.CLIENT_KEY_SETTING: None}):
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zulip', next='/user_uploads/image') subdomain='zulip', next='/user_uploads/image')
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/config-error/github") self.assertEqual(result.url, self.CONFIG_ERROR_URL)
def test_github_oauth2_success(self) -> None: def test_social_auth_success(self) -> None:
account_data_dict = dict(email=self.email, name=self.name) account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zulip', next='/user_uploads/image') subdomain='zulip', next='/user_uploads/image')
data = load_subdomain_token(result) data = load_subdomain_token(result)
self.assertEqual(data['email'], self.example_email("hamlet")) self.assertEqual(data['email'], self.example_email("hamlet"))
@@ -532,10 +527,11 @@ class GitHubAuthBackendTest(ZulipTestCase):
self.assertTrue(uri.startswith('http://zulip.testserver/accounts/login/subdomain/')) self.assertTrue(uri.startswith('http://zulip.testserver/accounts/login/subdomain/'))
@override_settings(SOCIAL_AUTH_SUBDOMAIN=None) @override_settings(SOCIAL_AUTH_SUBDOMAIN=None)
def test_github_when_social_auth_subdomain_is_not_set(self) -> None: def test_when_social_auth_subdomain_is_not_set(self) -> None:
account_data_dict = dict(email=self.email, name=self.name) account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zulip', next='/user_uploads/image') subdomain='zulip',
next='/user_uploads/image')
data = load_subdomain_token(result) data = load_subdomain_token(result)
self.assertEqual(data['email'], self.example_email("hamlet")) self.assertEqual(data['email'], self.example_email("hamlet"))
self.assertEqual(data['name'], 'Hamlet') self.assertEqual(data['name'], 'Hamlet')
@@ -547,107 +543,43 @@ class GitHubAuthBackendTest(ZulipTestCase):
parsed_url.path) parsed_url.path)
self.assertTrue(uri.startswith('http://zulip.testserver/accounts/login/subdomain/')) self.assertTrue(uri.startswith('http://zulip.testserver/accounts/login/subdomain/'))
def test_github_oauth2_email_not_verified(self) -> None: def test_social_auth_deactivated_user(self) -> None:
account_data_dict = dict(email=self.email, name=self.name)
email_data = [
dict(email=account_data_dict["email"],
verified=False,
primary=True),
]
with mock.patch('logging.warning') as mock_warning:
result = self.github_oauth2_test(account_data_dict,
subdomain='zulip',
email_data=email_data)
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/login/")
mock_warning.assert_called_once_with("Social auth (GitHub) failed "
"because user has no verified emails")
@override_settings(SOCIAL_AUTH_GITHUB_TEAM_ID='zulip-webapp')
def test_github_oauth2_github_team_not_member_failed(self) -> None:
account_data_dict = dict(email=self.email, name=self.name)
with mock.patch('social_core.backends.github.GithubTeamOAuth2.user_data',
side_effect=AuthFailed('Not found')), \
mock.patch('logging.info') as mock_info:
result = self.github_oauth2_test(account_data_dict,
subdomain='zulip')
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/login/")
mock_info.assert_called_once_with("GitHub user is not member of required team")
@override_settings(SOCIAL_AUTH_GITHUB_TEAM_ID='zulip-webapp')
def test_github_oauth2_github_team_member_success(self) -> None:
account_data_dict = dict(email=self.email, name=self.name)
with mock.patch('social_core.backends.github.GithubTeamOAuth2.user_data',
return_value=account_data_dict):
result = self.github_oauth2_test(account_data_dict,
subdomain='zulip')
data = load_subdomain_token(result)
self.assertEqual(data['email'], self.example_email("hamlet"))
self.assertEqual(data['name'], 'Hamlet')
self.assertEqual(data['subdomain'], 'zulip')
@override_settings(SOCIAL_AUTH_GITHUB_ORG_NAME='Zulip')
def test_github_oauth2_github_organization_not_member_failed(self) -> None:
account_data_dict = dict(email=self.email, name=self.name)
with mock.patch('social_core.backends.github.GithubOrganizationOAuth2.user_data',
side_effect=AuthFailed('Not found')), \
mock.patch('logging.info') as mock_info:
result = self.github_oauth2_test(account_data_dict,
subdomain='zulip')
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/login/")
mock_info.assert_called_once_with("GitHub user is not member of required organization")
@override_settings(SOCIAL_AUTH_GITHUB_ORG_NAME='Zulip')
def test_github_oauth2_github_organization_member_success(self) -> None:
account_data_dict = dict(email=self.email, name=self.name)
with mock.patch('social_core.backends.github.GithubOrganizationOAuth2.user_data',
return_value=account_data_dict):
result = self.github_oauth2_test(account_data_dict,
subdomain='zulip')
data = load_subdomain_token(result)
self.assertEqual(data['email'], self.example_email("hamlet"))
self.assertEqual(data['name'], 'Hamlet')
self.assertEqual(data['subdomain'], 'zulip')
def test_github_oauth2_deactivated_user(self) -> None:
user_profile = self.example_user("hamlet") user_profile = self.example_user("hamlet")
do_deactivate_user(user_profile) do_deactivate_user(user_profile)
account_data_dict = dict(email=self.email, name=self.name) account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zulip') subdomain='zulip')
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/login/") self.assertEqual(result.url, "/login/")
# TODO: verify whether we provide a clear error message # TODO: verify whether we provide a clear error message
def test_github_oauth2_invalid_realm(self) -> None: def test_social_auth_invalid_realm(self) -> None:
account_data_dict = dict(email=self.email, name=self.name) account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
with mock.patch('zerver.middleware.get_realm', return_value=get_realm("zulip")): with mock.patch('zerver.middleware.get_realm', return_value=get_realm("zulip")):
# This mock.patch case somewhat hackishly arranges it so # This mock.patch case somewhat hackishly arranges it so
# that we switch realms halfway through the test # that we switch realms halfway through the test
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='invalid', next='/user_uploads/image') subdomain='invalid', next='/user_uploads/image')
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/accounts/login/?subdomain=1") self.assertEqual(result.url, "/accounts/login/?subdomain=1")
def test_github_oauth2_invalid_email(self) -> None: def test_social_auth_invalid_email(self) -> None:
account_data_dict = dict(email="invalid", name=self.name) account_data_dict = self.get_account_data_dict(email="invalid", name=self.name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zulip', next='/user_uploads/image') subdomain='zulip', next='/user_uploads/image')
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/login/?next=/user_uploads/image") self.assertEqual(result.url, "/login/?next=/user_uploads/image")
def test_user_cannot_log_into_nonexisting_realm(self) -> None: def test_user_cannot_log_into_nonexisting_realm(self) -> None:
account_data_dict = dict(email=self.email, name=self.name) account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='nonexistent') subdomain='nonexistent')
self.assert_in_success_response(["There is no Zulip organization hosted at this subdomain."], self.assert_in_success_response(["There is no Zulip organization hosted at this subdomain."],
result) result)
def test_user_cannot_log_into_wrong_subdomain(self) -> None: def test_user_cannot_log_into_wrong_subdomain(self) -> None:
account_data_dict = dict(email=self.email, name=self.name) account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zephyr') subdomain='zephyr')
self.assertTrue(result.url.startswith("http://zephyr.testserver/accounts/login/subdomain/")) self.assertTrue(result.url.startswith("http://zephyr.testserver/accounts/login/subdomain/"))
result = self.client_get(result.url.replace('http://zephyr.testserver', ''), result = self.client_get(result.url.replace('http://zephyr.testserver', ''),
@@ -655,24 +587,24 @@ class GitHubAuthBackendTest(ZulipTestCase):
self.assert_in_success_response(['Your email address, hamlet@zulip.com, is not in one of the domains ', self.assert_in_success_response(['Your email address, hamlet@zulip.com, is not in one of the domains ',
'that are allowed to register for accounts in this organization.'], result) 'that are allowed to register for accounts in this organization.'], result)
def test_github_oauth2_mobile_success(self) -> None: def test_social_auth_mobile_success(self) -> None:
mobile_flow_otp = '1234abcd' * 8 mobile_flow_otp = '1234abcd' * 8
account_data_dict = dict(email=self.email, name='Full Name') account_data_dict = self.get_account_data_dict(email=self.email, name='Full Name')
self.assertEqual(len(mail.outbox), 0) self.assertEqual(len(mail.outbox), 0)
self.user_profile.date_joined = timezone_now() - datetime.timedelta(seconds=JUST_CREATED_THRESHOLD + 1) self.user_profile.date_joined = timezone_now() - datetime.timedelta(seconds=JUST_CREATED_THRESHOLD + 1)
self.user_profile.save() self.user_profile.save()
with self.settings(SEND_LOGIN_EMAILS=True): with self.settings(SEND_LOGIN_EMAILS=True):
# Verify that the right thing happens with an invalid-format OTP # Verify that the right thing happens with an invalid-format OTP
result = self.github_oauth2_test(account_data_dict, subdomain='zulip', result = self.social_auth_test(account_data_dict, subdomain='zulip',
mobile_flow_otp="1234") mobile_flow_otp="1234")
self.assert_json_error(result, "Invalid OTP") self.assert_json_error(result, "Invalid OTP")
result = self.github_oauth2_test(account_data_dict, subdomain='zulip', result = self.social_auth_test(account_data_dict, subdomain='zulip',
mobile_flow_otp="invalido" * 8) mobile_flow_otp="invalido" * 8)
self.assert_json_error(result, "Invalid OTP") self.assert_json_error(result, "Invalid OTP")
# Now do it correctly # Now do it correctly
result = self.github_oauth2_test(account_data_dict, subdomain='zulip', result = self.social_auth_test(account_data_dict, subdomain='zulip',
mobile_flow_otp=mobile_flow_otp) mobile_flow_otp=mobile_flow_otp)
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
redirect_url = result['Location'] redirect_url = result['Location']
@@ -687,12 +619,12 @@ class GitHubAuthBackendTest(ZulipTestCase):
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
self.assertIn('Zulip on Android', mail.outbox[0].body) self.assertIn('Zulip on Android', mail.outbox[0].body)
def test_github_oauth2_registration_existing_account(self) -> None: def test_social_auth_registration_existing_account(self) -> None:
"""If the user already exists, signup flow just logs them in""" """If the user already exists, signup flow just logs them in"""
email = "hamlet@zulip.com" email = "hamlet@zulip.com"
name = 'Full Name' name = 'Full Name'
account_data_dict = dict(email=email, name=name) account_data_dict = self.get_account_data_dict(email=email, name=name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zulip', is_signup='1') subdomain='zulip', is_signup='1')
data = load_subdomain_token(result) data = load_subdomain_token(result)
self.assertEqual(data['email'], self.example_email("hamlet")) self.assertEqual(data['email'], self.example_email("hamlet"))
@@ -707,13 +639,13 @@ class GitHubAuthBackendTest(ZulipTestCase):
# Name wasn't changed at all # Name wasn't changed at all
self.assertEqual(hamlet.full_name, "King Hamlet") self.assertEqual(hamlet.full_name, "King Hamlet")
def test_github_oauth2_registration(self) -> None: def test_social_auth_registration(self) -> None:
"""If the user doesn't exist yet, GitHub auth can be used to register an account""" """If the user doesn't exist yet, social auth can be used to register an account"""
email = "newuser@zulip.com" email = "newuser@zulip.com"
name = 'Full Name' name = 'Full Name'
realm = get_realm("zulip") realm = get_realm("zulip")
account_data_dict = dict(email=email, name=name) account_data_dict = self.get_account_data_dict(email=email, name=name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zulip', is_signup='1') subdomain='zulip', is_signup='1')
data = load_subdomain_token(result) data = load_subdomain_token(result)
@@ -755,12 +687,12 @@ class GitHubAuthBackendTest(ZulipTestCase):
user_profile = get_user(email, realm) user_profile = get_user(email, realm)
self.assertEqual(get_session_dict_user(self.client.session), user_profile.id) self.assertEqual(get_session_dict_user(self.client.session), user_profile.id)
def test_github_oauth2_registration_without_is_signup(self) -> None: def test_social_auth_registration_without_is_signup(self) -> None:
"""If the user doesn't exist yet, GitHub auth can be used to register an account""" """If `is_signup` is not set then a new account isn't created"""
email = "newuser@zulip.com" email = "newuser@zulip.com"
name = 'Full Name' name = 'Full Name'
account_data_dict = dict(email=email, name=name) account_data_dict = self.get_account_data_dict(email=email, name=name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zulip') subdomain='zulip')
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
data = load_subdomain_token(result) data = load_subdomain_token(result)
@@ -777,12 +709,12 @@ class GitHubAuthBackendTest(ZulipTestCase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
self.assert_in_response("No account found for newuser@zulip.com.", result) self.assert_in_response("No account found for newuser@zulip.com.", result)
def test_github_oauth2_registration_without_is_signup_closed_realm(self) -> None: def test_social_auth_registration_without_is_signup_closed_realm(self) -> None:
"""If the user doesn't exist yet in closed realm, give an error""" """If the user doesn't exist yet in closed realm, give an error"""
email = "nonexisting@phantom.com" email = "nonexisting@phantom.com"
name = 'Full Name' name = 'Full Name'
account_data_dict = dict(email=email, name=name) account_data_dict = self.get_account_data_dict(email=email, name=name)
result = self.github_oauth2_test(account_data_dict, result = self.social_auth_test(account_data_dict,
subdomain='zulip') subdomain='zulip')
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
data = load_subdomain_token(result) data = load_subdomain_token(result)
@@ -802,21 +734,126 @@ class GitHubAuthBackendTest(ZulipTestCase):
'in one of the domains that are allowed to register ' 'in one of the domains that are allowed to register '
'for accounts in this organization.'.format(email), result) 'for accounts in this organization.'.format(email), result)
def test_github_complete(self) -> None: def test_social_auth_complete(self) -> None:
with mock.patch('social_core.backends.oauth.BaseOAuth2.process_error', with mock.patch('social_core.backends.oauth.BaseOAuth2.process_error',
side_effect=AuthFailed('Not found')): side_effect=AuthFailed('Not found')):
result = self.client_get(reverse('social:complete', args=['github'])) result = self.client_get(reverse('social:complete', args=[self.backend.name]))
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertIn('login', result.url) self.assertIn('login', result.url)
def test_github_complete_when_base_exc_is_raised(self) -> None: def test_social_auth_complete_when_base_exc_is_raised(self) -> None:
with mock.patch('social_core.backends.oauth.BaseOAuth2.auth_complete', with mock.patch('social_core.backends.oauth.BaseOAuth2.auth_complete',
side_effect=AuthStateForbidden('State forbidden')), \ side_effect=AuthStateForbidden('State forbidden')), \
mock.patch('zproject.backends.logging.warning'): mock.patch('zproject.backends.logging.warning'):
result = self.client_get(reverse('social:complete', args=['github'])) result = self.client_get(reverse('social:complete', args=[self.backend.name]))
self.assertEqual(result.status_code, 302) self.assertEqual(result.status_code, 302)
self.assertIn('login', result.url) self.assertIn('login', result.url)
class GitHubAuthBackendTest(SocialAuthBase):
__unittest_skip__ = False
BACKEND_CLASS = GitHubAuthBackend
CLIENT_KEY_SETTING = "SOCIAL_AUTH_GITHUB_KEY"
LOGIN_URL = "/accounts/login/social/github"
SIGNUP_URL = "/accounts/register/social/github"
AUTHORIZATION_URL = "https://github.com/login/oauth/authorize"
ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"
USER_INFO_URL = "https://api.github.com/user"
AUTH_FINISH_URL = "/complete/github/"
CONFIG_ERROR_URL = "/config-error/github"
def registerExtraEndpoints(self,
account_data_dict: Dict[str, str],
**extra_data: Any) -> None:
# Keeping a verified email before the primary email makes sure
# get_verified_emails puts the primary email at the start of the
# email list returned as social_associate_user_helper assumes the
# first email as the primary email.
email_data = [
dict(email="notprimary@example.com",
verified=True),
dict(email=account_data_dict["email"],
verified=True,
primary=True),
dict(email="ignored@example.com",
verified=False),
]
email_data = extra_data.get("email_data", email_data)
httpretty.register_uri(
httpretty.GET,
"https://api.github.com/user/emails",
status=200,
body=json.dumps(email_data)
)
def get_account_data_dict(self, email: str, name: str) -> Dict[str, Any]:
return dict(email=email, name=name)
def test_social_auth_email_not_verified(self) -> None:
account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
email_data = [
dict(email=account_data_dict["email"],
verified=False,
primary=True),
]
with mock.patch('logging.warning') as mock_warning:
result = self.social_auth_test(account_data_dict,
subdomain='zulip',
email_data=email_data)
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/login/")
mock_warning.assert_called_once_with("Social auth (GitHub) failed "
"because user has no verified emails")
@override_settings(SOCIAL_AUTH_GITHUB_TEAM_ID='zulip-webapp')
def test_social_auth_github_team_not_member_failed(self) -> None:
account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
with mock.patch('social_core.backends.github.GithubTeamOAuth2.user_data',
side_effect=AuthFailed('Not found')), \
mock.patch('logging.info') as mock_info:
result = self.social_auth_test(account_data_dict,
subdomain='zulip')
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/login/")
mock_info.assert_called_once_with("GitHub user is not member of required team")
@override_settings(SOCIAL_AUTH_GITHUB_TEAM_ID='zulip-webapp')
def test_social_auth_github_team_member_success(self) -> None:
account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
with mock.patch('social_core.backends.github.GithubTeamOAuth2.user_data',
return_value=account_data_dict):
result = self.social_auth_test(account_data_dict,
subdomain='zulip')
data = load_subdomain_token(result)
self.assertEqual(data['email'], self.example_email("hamlet"))
self.assertEqual(data['name'], 'Hamlet')
self.assertEqual(data['subdomain'], 'zulip')
@override_settings(SOCIAL_AUTH_GITHUB_ORG_NAME='Zulip')
def test_social_auth_github_organization_not_member_failed(self) -> None:
account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
with mock.patch('social_core.backends.github.GithubOrganizationOAuth2.user_data',
side_effect=AuthFailed('Not found')), \
mock.patch('logging.info') as mock_info:
result = self.social_auth_test(account_data_dict,
subdomain='zulip')
self.assertEqual(result.status_code, 302)
self.assertEqual(result.url, "/login/")
mock_info.assert_called_once_with("GitHub user is not member of required organization")
@override_settings(SOCIAL_AUTH_GITHUB_ORG_NAME='Zulip')
def test_social_auth_github_organization_member_success(self) -> None:
account_data_dict = self.get_account_data_dict(email=self.email, name=self.name)
with mock.patch('social_core.backends.github.GithubOrganizationOAuth2.user_data',
return_value=account_data_dict):
result = self.social_auth_test(account_data_dict,
subdomain='zulip')
data = load_subdomain_token(result)
self.assertEqual(data['email'], self.example_email("hamlet"))
self.assertEqual(data['name'], 'Hamlet')
self.assertEqual(data['subdomain'], 'zulip')
def test_github_auth_enabled(self) -> None: def test_github_auth_enabled(self) -> None:
with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitHubAuthBackend',)): with self.settings(AUTHENTICATION_BACKENDS=('zproject.backends.GitHubAuthBackend',)):
self.assertTrue(github_auth_enabled()) self.assertTrue(github_auth_enabled())