[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