141 lines
4.6 KiB
Python
141 lines
4.6 KiB
Python
"""Common utility for testing third party oauth2 features."""
|
|
import json
|
|
from base64 import b64encode
|
|
|
|
import httpretty
|
|
from onelogin.saml2.utils import OneLogin_Saml2_Utils
|
|
|
|
from provider.constants import PUBLIC
|
|
from provider.oauth2.models import Client
|
|
from social_core.backends.facebook import FacebookOAuth2, API_VERSION as FACEBOOK_API_VERSION
|
|
from social_django.models import UserSocialAuth, Partial
|
|
|
|
from student.tests.factories import UserFactory
|
|
|
|
from .testutil import ThirdPartyAuthTestMixin
|
|
|
|
|
|
@httpretty.activate
|
|
class ThirdPartyOAuthTestMixin(ThirdPartyAuthTestMixin):
|
|
"""
|
|
Mixin with tests for third party oauth views. A TestCase that includes
|
|
this must define the following:
|
|
|
|
BACKEND: The name of the backend from python-social-auth
|
|
USER_URL: The URL of the endpoint that the backend retrieves user data from
|
|
UID_FIELD: The field in the user data that the backend uses as the user id
|
|
"""
|
|
social_uid = "test_social_uid"
|
|
access_token = "test_access_token"
|
|
client_id = "test_client_id"
|
|
|
|
CREATE_USER = True
|
|
|
|
def setUp(self):
|
|
super(ThirdPartyOAuthTestMixin, self).setUp()
|
|
if self.CREATE_USER:
|
|
self.user = UserFactory()
|
|
UserSocialAuth.objects.create(user=self.user, provider=self.BACKEND, uid=self.social_uid)
|
|
self.oauth_client = self._create_client()
|
|
if self.BACKEND == 'google-oauth2':
|
|
self.configure_google_provider(enabled=True, visible=True)
|
|
elif self.BACKEND == 'facebook':
|
|
self.configure_facebook_provider(enabled=True, visible=True)
|
|
|
|
def tearDown(self):
|
|
super(ThirdPartyOAuthTestMixin, self).tearDown()
|
|
Partial.objects.all().delete()
|
|
|
|
def _create_client(self):
|
|
"""
|
|
Create an OAuth2 client application
|
|
"""
|
|
return Client.objects.create(
|
|
client_id=self.client_id,
|
|
client_type=PUBLIC,
|
|
)
|
|
|
|
def _setup_provider_response(self, success=False, email=''):
|
|
"""
|
|
Register a mock response for the third party user information endpoint;
|
|
success indicates whether the response status code should be 200 or 400
|
|
"""
|
|
if success:
|
|
status = 200
|
|
response = {self.UID_FIELD: self.social_uid}
|
|
if email:
|
|
response.update({'email': email})
|
|
body = json.dumps(response)
|
|
else:
|
|
status = 400
|
|
body = json.dumps({})
|
|
|
|
self._setup_provider_response_with_body(status, body)
|
|
|
|
def _setup_provider_response_with_body(self, status, body):
|
|
"""
|
|
Register a mock response for the third party user information endpoint with given status and body.
|
|
"""
|
|
httpretty.register_uri(
|
|
httpretty.GET,
|
|
self.USER_URL,
|
|
body=body,
|
|
status=status,
|
|
content_type="application/json",
|
|
)
|
|
|
|
|
|
class ThirdPartyOAuthTestMixinFacebook(object):
|
|
"""Tests oauth with the Facebook backend"""
|
|
BACKEND = "facebook"
|
|
USER_URL = FacebookOAuth2.USER_DATA_URL.format(version=FACEBOOK_API_VERSION)
|
|
# In facebook responses, the "id" field is used as the user's identifier
|
|
UID_FIELD = "id"
|
|
|
|
|
|
class ThirdPartyOAuthTestMixinGoogle(object):
|
|
"""Tests oauth with the Google backend"""
|
|
BACKEND = "google-oauth2"
|
|
USER_URL = "https://www.googleapis.com/plus/v1/people/me"
|
|
# In google-oauth2 responses, the "email" field is used as the user's identifier
|
|
UID_FIELD = "email"
|
|
|
|
|
|
def read_and_pre_process_xml(file_name):
|
|
"""
|
|
Read XML file with the name specified in the argument and pre process the xml so that it can be parsed.
|
|
|
|
Pre Processing removes line retune characters (i.e. "\n").
|
|
|
|
Arguments:
|
|
file_name (str): Name of the XML file.
|
|
|
|
Returns:
|
|
(str): Pre Processed contents of the file.
|
|
"""
|
|
with open(file_name, 'r') as xml_file:
|
|
return xml_file.read().replace('\n', '')
|
|
|
|
|
|
def prepare_saml_response_from_xml(xml, relay_state='testshib'):
|
|
"""
|
|
Pre Process XML so that it can be used as a SAML Response coming from SAML IdP.
|
|
|
|
This method will perform the following operations on the XML in given order
|
|
|
|
1. base64 encode XML.
|
|
2. URL encode the base64 encoded data.
|
|
|
|
Arguments:
|
|
xml (string): XML data
|
|
relay_state (string): Relay State of the SAML Response
|
|
|
|
Returns:
|
|
(str): Base64 and URL encoded XML.
|
|
"""
|
|
b64encoded_xml = b64encode(xml)
|
|
return 'RelayState={relay_state}&SAMLResponse={saml_response}'.format(
|
|
relay_state=OneLogin_Saml2_Utils.escape_url(relay_state),
|
|
saml_response=OneLogin_Saml2_Utils.escape_url(b64encoded_xml)
|
|
)
|