[Python-modules-commits] [python-social-auth] 02/15: Tests for SAML backend
Wolfgang Borgert
debacle at moszumanska.debian.org
Sat Dec 24 15:13:33 UTC 2016
This is an automated email from the git hooks/post-receive script.
debacle pushed a commit to tag v0.2.11
in repository python-social-auth.
commit 05ca867f1166fdb63b9fb838a0b8e44b7d48d11d
Author: Braden MacDonald <braden at opencraft.com>
Date: Fri May 8 00:47:07 2015 -0700
Tests for SAML backend
---
.travis.yml | 3 +
social/tests/actions/test_disconnect.py | 13 +++-
social/tests/backends/data/saml_config.json | 26 +++++++
social/tests/backends/data/saml_response.txt | 1 +
social/tests/backends/test_saml.py | 104 +++++++++++++++++++++++++++
social/tests/models.py | 2 +-
social/tests/strategy.py | 20 ++++++
7 files changed, 167 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index f7aca6f..d9380b2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,6 +17,9 @@ matrix:
env:
- REQUIREMENTS=requirements-python3.txt
- TEST_REQUIREMENTS=social/tests/requirements-python3.txt
+before_install:
+ - sudo apt-get update -qq
+ - sudo apt-get install -y libxmlsec1-dev swig
install:
- "python setup.py -q install"
- "travis_retry pip install -r $REQUIREMENTS"
diff --git a/social/tests/actions/test_disconnect.py b/social/tests/actions/test_disconnect.py
index 328ad8d..ef89d59 100644
--- a/social/tests/actions/test_disconnect.py
+++ b/social/tests/actions/test_disconnect.py
@@ -6,7 +6,7 @@ from social.actions import do_disconnect
from social.exceptions import NotAllowedToDisconnect
from social.utils import parse_qs
-from social.tests.models import User
+from social.tests.models import User, TestUserSocialAuth
from social.tests.actions.actions import BaseActionTest
@@ -24,6 +24,17 @@ class DisconnectActionTest(BaseActionTest):
do_disconnect(self.backend, user)
self.assertEqual(len(user.social), 0)
+ def test_disconnect_with_association_id(self):
+ self.do_login()
+ user = User.get(self.expected_username)
+ user.password = 'password'
+ association_id = user.social[0].id
+ second_usa = TestUserSocialAuth(user, user.social[0].provider, "uid2")
+ self.assertEqual(len(user.social), 2)
+ do_disconnect(self.backend, user, association_id)
+ self.assertEqual(len(user.social), 1)
+ self.assertEqual(user.social[0], second_usa)
+
def test_disconnect_with_partial_pipeline(self):
self.strategy.set_settings({
'SOCIAL_AUTH_DISCONNECT_PIPELINE': (
diff --git a/social/tests/backends/data/saml_config.json b/social/tests/backends/data/saml_config.json
new file mode 100644
index 0000000..5c119e6
--- /dev/null
+++ b/social/tests/backends/data/saml_config.json
@@ -0,0 +1,26 @@
+{
+ "SOCIAL_AUTH_SAML_SP_ENTITY_ID": "https://github.com/omab/python-social-auth/saml-test",
+ "SOCIAL_AUTH_SAML_SP_PUBLIC_CERT": "MIICsDCCAhmgAwIBAgIJAO7BwdjDZcUWMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNBMRkwFwYDVQQIExBCcml0aXNoIENvbHVtYmlhMRswGQYDVQQKExJweXRob24tc29jaWFsLWF1dGgwHhcNMTUwNTA4MDc1ODQ2WhcNMjUwNTA3MDc1ODQ2WjBFMQswCQYDVQQGEwJDQTEZMBcGA1UECBMQQnJpdGlzaCBDb2x1bWJpYTEbMBkGA1UEChMScHl0aG9uLXNvY2lhbC1hdXRoMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq3g1Cl+3uR5vCnN4HbgjTg+m3nHhteEMyb++ycZYre2bxUfsshER6x33l23tHckRYwm7MdBbrp3LrVoiOCdPblTml1IhEPTCwKMhBKvvWqTvgfcSSnRzAWkLlQYSusayy [...]
+ "SOCIAL_AUTH_SAML_SP_PRIVATE_KEY": "MIICXgIBAAKBgQCq3g1Cl+3uR5vCnN4HbgjTg+m3nHhteEMyb++ycZYre2bxUfsshER6x33l23tHckRYwm7MdBbrp3LrVoiOCdPblTml1IhEPTCwKMhBKvvWqTvgfcSSnRzAWkLlQYSusayyZK4n9qcYkV5MFni1rbjx+Mr5aOEmb5u33amMKLwSTwIDAQABAoGBAIHAg6NJSiYC/NYpVzWfKlasuoNy78R5adXYSNZiCR5V5FNm5OzmODZgXUt6g0A7FomshIT/txQWoV7y5FmwPs8n13JY3Hdt4tJ6MHw2feLo710+OEp9VBQus3JsB2F8ONYrGvs00hPPL7h5av/rzTdE8F67YM1mSgeg7xEF6BghAkEA12OOqSzp2MLTNY7PqOaLDzy4aAMVNN3Ntv2jBN0jq7s1b5ilQ2PGkLwdtkicq/VZcRyUqVbZbMwz05II [...]
+ "SOCIAL_AUTH_SAML_ORG_INFO": {
+ "en-US": {"name": "psa", "displayname": "PSA", "url": "https://github.com/omab/python-social-auth/"}
+ },
+ "SOCIAL_AUTH_SAML_TECHNICAL_CONTACT":
+ {"givenName": "Tech Gal", "emailAddress": "technical at example.com"},
+ "SOCIAL_AUTH_SAML_SUPPORT_CONTACT":
+ {"givenName": "Support Guy", "emailAddress": "support at example.com"},
+ "SOCIAL_AUTH_SAML_ENABLED_IDPS": {
+ "testshib": {
+ "entity_id": "https://idp.testshib.org/idp/shibboleth",
+ "url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO",
+ "x509cert": "MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzEVMBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMREwDwYDVQQKEwhUZXN0U2hpYjEZMBcGA1UEAxMQaWRwLnRlc3RzaGliLm9yZzAeFw0wNjA4MzAyMTEyMjVaFw0xNjA4MjcyMTEyMjVaMGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQIEwxQZW5uc3lsdmFuaWExEzARBgNVBAcTClBpdHRzYnVyZ2gxETAPBgNVBAoTCFRlc3RTaGliMRkwFwYDVQQDExBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArYkCGuTmJp9eAOSGHwRJo1SNatB5ZOKqDM9ysg7CyVTDClcpu93gSP10nH4 [...]
+ },
+ "other": {
+ "entity_id": "https://unused.saml.example.com",
+ "singleSignOnService": {
+ "url": "https://unused.saml.example.com/SAML2/Redirect/SSO",
+ "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ }
+ }
+ }
+}
diff --git a/social/tests/backends/data/saml_response.txt b/social/tests/backends/data/saml_response.txt
new file mode 100644
index 0000000..557bb59
--- /dev/null
+++ b/social/tests/backends/data/saml_response.txt
@@ -0,0 +1 @@
+http://myapp.com/?RelayState=testshib&SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL215YXBwLmNvbSIgSUQ9Il8yNTk2NTFlOTY3ZGIwOGZjYTQ4MjdkODI3YWY1M2RkMCIgSW5SZXNwb25zZVRvPSJURVNUX0lEIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMDUtMDlUMDM6NTc6NDMuNzkyWiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBGb3JtYXQ9InVyb [...]
\ No newline at end of file
diff --git a/social/tests/backends/test_saml.py b/social/tests/backends/test_saml.py
new file mode 100644
index 0000000..abe2569
--- /dev/null
+++ b/social/tests/backends/test_saml.py
@@ -0,0 +1,104 @@
+import base64
+import datetime
+from httpretty import HTTPretty
+import json
+from mock import patch
+try:
+ from onelogin.saml2.utils import OneLogin_Saml2_Utils
+except ImportError:
+ pass # Only available for python 2.7 at the moment, so don't worry if this fails
+import os.path
+import re
+import requests
+from social.p3 import urlparse
+from social.utils import parse_qs, url_add_parameters
+from social.tests.models import User
+from social.tests.backends.base import BaseBackendTest
+import sys
+import unittest2
+try:
+ from urllib.parse import urlencode, urlparse, urlunparse, parse_qs
+except ImportError:
+ from urllib import urlencode
+ from urlparse import urlparse, urlunparse, parse_qs
+
+DATA_DIR = os.path.join(os.path.dirname(__file__), 'data')
+
+
+ at unittest2.skipUnless(
+ sys.version_info[:2] == (2, 7),
+ "python-saml currently depends on 2.7; 3+ support coming soon")
+ at unittest2.skipIf('__pypy__' in sys.builtin_module_names, "dm.xmlsec not compatible with pypy")
+class SAMLTest(BaseBackendTest):
+ backend_path = 'social.backends.saml.SAMLAuth'
+ expected_username = 'myself'
+
+ def extra_settings(self):
+ with open(os.path.join(DATA_DIR, 'saml_config.json'), 'r') as config_file:
+ config_str = config_file.read()
+ return json.loads(config_str)
+
+ def setUp(self):
+ """ Patch the time so that we can replay canned request/response pairs """
+ super(SAMLTest, self).setUp()
+
+ @staticmethod
+ def fixed_time():
+ return OneLogin_Saml2_Utils.parse_SAML_to_time("2015-05-09T03:57:22Z")
+ now_patch = patch.object(OneLogin_Saml2_Utils, 'now', fixed_time)
+ now_patch.start()
+ self.addCleanup(now_patch.stop)
+
+ def install_http_intercepts(self, start_url, return_url):
+ # When we request start_url (https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO...)
+ # we will eventually get a redirect back, with SAML assertion data in the query string.
+ # A pre-recorded correct response is kept in this .txt file:
+ with open(os.path.join(DATA_DIR, 'saml_response.txt'), 'r') as response_file:
+ response_url = response_file.read()
+ HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=response_url)
+ HTTPretty.register_uri(HTTPretty.GET, return_url, status=200, body='foobar')
+
+ def do_start(self):
+ # pretend we've started with a URL like /login/saml/?idp=testshib:
+ self.strategy.set_request_data({'idp': 'testshib'}, self.backend)
+ start_url = self.backend.start().url
+ # Modify the start URL to make the SAML request consistent from test to test:
+ start_url = self.modify_start_url(start_url)
+ # If the SAML Identity Provider recognizes the user, we will be redirected back to:
+ return_url = self.backend.redirect_uri
+ self.install_http_intercepts(start_url, return_url)
+ response = requests.get(start_url)
+ self.assertTrue(response.url.startswith(return_url))
+ self.assertEqual(response.text, 'foobar')
+ query_values = dict((k, v[0]) for k, v in parse_qs(urlparse(response.url).query).items())
+ self.assertNotIn(' ', query_values['SAMLResponse'])
+ self.strategy.set_request_data(query_values, self.backend)
+ return self.backend.complete()
+
+ def test_metadata_generation(self):
+ """ Test that we can generate the metadata without error """
+ xml, errors = self.backend.generate_metadata_xml()
+ self.assertEqual(len(errors), 0)
+ self.assertEqual(xml[0], '<')
+
+ def test_login(self):
+ """ Test that we can authenticate with a SAML IdP (TestShib) """
+ user = self.do_login()
+
+ def modify_start_url(self, start_url):
+ """
+ Given a SAML redirect URL, parse it and change the ID to
+ a consistent value, so the request is always identical.
+ """
+ # Parse the SAML Request URL to get the XML being sent to TestShib
+ url_parts = urlparse(start_url)
+ query = dict((k, v[0]) for (k, v) in parse_qs(url_parts.query).iteritems())
+ xml = OneLogin_Saml2_Utils.decode_base64_and_inflate(query['SAMLRequest'])
+ # Modify the XML:
+ xml, changed = re.subn(r'ID="[^"]+"', 'ID="TEST_ID"', xml)
+ self.assertEqual(changed, 1)
+ # Update the URL to use the modified query string:
+ query['SAMLRequest'] = OneLogin_Saml2_Utils.deflate_and_base64_encode(xml)
+ url_parts = list(url_parts)
+ url_parts[4] = urlencode(query)
+ return urlunparse(url_parts)
diff --git a/social/tests/models.py b/social/tests/models.py
index 7dae52f..80bf687 100644
--- a/social/tests/models.py
+++ b/social/tests/models.py
@@ -117,7 +117,7 @@ class TestUserSocialAuth(UserMixin, BaseModel):
@classmethod
def get_social_auth_for_user(cls, user, provider=None, id=None):
- return user.social
+ return [usa for usa in user.social if provider in (None, usa.provider) and id in (None, usa.id)]
@classmethod
def create_social_auth(cls, user, uid, provider):
diff --git a/social/tests/strategy.py b/social/tests/strategy.py
index 9ccd7d0..d88685f 100644
--- a/social/tests/strategy.py
+++ b/social/tests/strategy.py
@@ -50,6 +50,26 @@ class TestStrategy(BaseStrategy):
"""Return current host value"""
return TEST_HOST
+ def request_is_secure(self):
+ """ Is the request using HTTPS? """
+ return False
+
+ def request_path(self):
+ """ path of the current request """
+ return ''
+
+ def request_port(self):
+ """ Port in use for this request """
+ return 80
+
+ def request_get(self):
+ """ Request GET data """
+ return self._request_data.copy()
+
+ def request_post(self):
+ """ Request POST data """
+ return self._request_data.copy()
+
def session_get(self, name, default=None):
"""Return session value for given key"""
return self._session.get(name, default)
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-social-auth.git
More information about the Python-modules-commits
mailing list