diff --git a/.gitignore b/.gitignore index f5552467..f1fadf08 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,5 @@ idp.xml # Custom IDE files .vscode/ - +.pre-commit-config.yaml +.flake8 diff --git a/bin/setup-ckan.bash b/bin/setup-ckan.bash index 4c45aec5..3ace4257 100755 --- a/bin/setup-ckan.bash +++ b/bin/setup-ckan.bash @@ -5,6 +5,7 @@ echo "This is setup-ckan.bash..." echo "Installing the packages that CKAN requires..." sudo apt-get update -qq +sudo apt-get install xmlsec1 libxmlsec1-dev echo "Installing CKAN and its Python dependencies..." git clone https://github.com/ckan/ckan diff --git a/ckanext/saml2auth/tests/extras/provider0/idp.xml b/ckanext/saml2auth/tests/extras/provider0/idp.xml new file mode 100644 index 00000000..6faea392 --- /dev/null +++ b/ckanext/saml2auth/tests/extras/provider0/idp.xml @@ -0,0 +1,89 @@ + + + + + example.com + + + + Consortium Company Test IdP + + + Consortium Company Test IdP + + + + This Identity Provider gives support for the Consortium Company Test's user community + + + Questo Identity Provider di test fornisce supporto alla comunita' utenti Company Test + + + + + + + + + MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB + BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe + Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t + cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP + ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS + v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN + iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece + byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz + cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v + dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX + gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w + dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW + BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu + 9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL + qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU + duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU + yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p + V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e + Cq53OZt9ISjHEw== + + + + + + + + + + + Consortium Company Test + + + Consortium Company Test + + + + Consortium Company Test + + + Consortium Company Test + + + + http://www.company-test.com + + + https://www.company-test.com.ar + + + + + mailto:technical.contact@example.com + + + \ No newline at end of file diff --git a/ckanext/saml2auth/tests/extras/provider1/idp_cert_template.xml b/ckanext/saml2auth/tests/extras/provider1/idp_cert_template.xml new file mode 100644 index 00000000..83d14fd2 --- /dev/null +++ b/ckanext/saml2auth/tests/extras/provider1/idp_cert_template.xml @@ -0,0 +1,45 @@ + + + + + + + {{ certificate }} + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress + + + + + + + + {{ certificate }} + + + + + {{ org_name }} + {{ org_name }} + {{ org_url }} + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress + + + {{ org_name }} + {{ org_name }} + {{ org_url }} + + + diff --git a/ckanext/saml2auth/tests/responses/test-signed-encrypted-example.xml b/ckanext/saml2auth/tests/responses/test-signed-encrypted-example.xml new file mode 100644 index 00000000..a96a2f3c --- /dev/null +++ b/ckanext/saml2auth/tests/responses/test-signed-encrypted-example.xml @@ -0,0 +1,149 @@ + + + urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity + + + + + + + + + + + 4M6bvWvJ12UY+/g3eUq8ZoLk5LGJjcqWVFLfenx5/dY= + + + lb+Bt293tx82qQLCJA9Gxjn0VQmUeqHdQExyVeq/w/8JNE5myib2pkUqfGlv1WX7 YSqNb6C8Tgkh/ZG2c3tmOgvL026JKtYX8LOBD7CVzLzCF4lhMBUGvPHooq9pO5k8 A6Eiv2dpPvq+UG4Ah0dMNleLf2+JrEoqR+/MfK9isfk= + + + MIICQTCCAaoCAQEwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCc2UxCzAJBgNVBAgMAmFjMQ0wCwYDVQQHDAR1bWVhMRgwFgYDVQQKDA9UZXN0IFVuaXZlcnNpdHkxDTALBgNVBAsMBERlY2ExFTATBgNVBAMMDGxvY2FsaG9zdC5jYTAeFw0yMTAxMTUxOTIwMjNaFw0yMTAxMTUxOTIwMjRaMGkxCzAJBgNVBAYTAnNlMQswCQYDVQQIDAJhYzENMAsGA1UEBwwEdW1lYTEYMBYGA1UECgwPVGVzdCBVbml2ZXJzaXR5MQ0wCwYDVQQLDAREZWNhMRUwEwYDVQQDDAxsb2NhbGhvc3QuY2EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKvHgRbUwLtmgkwUtGit4NXPMpXtoCLQJaK8uDKhsGg9UC8ShSK67ci5tzOP/DbQ3W6S4vLpOG7zl1cadhsGHdGYCHkl/Z1L6a1rLhIJHoGNyYZpPlASfugZ5Jbl3BSc9d/Ht1zeO/SBBraAiXNwR36B9fdH5L4vJ7Yd/+ZsQPPxAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAn8dJxfsRbtFk6LXcn6i5OKZwfKVQHeyMUsUTwTjevqyCO7kK7xV3geJSCb5hWnHYOls4bj+NKpEEsay+cekJL7dgDe8HMB1Pk22/X8Vg8tRhgZAJKG+3yySiCSGDqgq/0PkmAWVZghjjUGHlan16roUue2T9Y7UbyzIE1bo9TVM= + + + + + + + + + + + + + + my-rsa-key + + + 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 +Q7r6v8WvuEjpBYdtVzzMZBKVmJj8zEIm + + + + diff --git a/ckanext/saml2auth/tests/responses/test-signed-example.xml b/ckanext/saml2auth/tests/responses/test-signed-example.xml new file mode 100644 index 00000000..59b3b1eb --- /dev/null +++ b/ckanext/saml2auth/tests/responses/test-signed-example.xml @@ -0,0 +1,88 @@ + + + urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity + + + + + + + + + + + 61ljft1X0eYnJmhlF2zFE7Guwxfn56mZQkKz6wRMLxg= + + + FeIuwIrarxsQI9g4ipHmbDcBG8RgzsTkZUzPA5zIarfi269z0AwJnrEROElsd2Z6 JoZRsh8jGWPHMkWNvmZl/QdQ0ha2l8ibCYPDOs/l5lMorXUi/SA7fwB7r4IF0w83 7NBXRF8RYK/5pRq8nr0k8kjc3pRLpVNxsdCLtFyE3WU= + + + MIICQTCCAaoCAQEwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCc2UxCzAJBgNVBAgMAmFjMQ0wCwYDVQQHDAR1bWVhMRgwFgYDVQQKDA9UZXN0IFVuaXZlcnNpdHkxDTALBgNVBAsMBERlY2ExFTATBgNVBAMMDGxvY2FsaG9zdC5jYTAeFw0yMTAxMTUxOTI0MDlaFw0yMTAxMTUxOTI0MTBaMGkxCzAJBgNVBAYTAnNlMQswCQYDVQQIDAJhYzENMAsGA1UEBwwEdW1lYTEYMBYGA1UECgwPVGVzdCBVbml2ZXJzaXR5MQ0wCwYDVQQLDAREZWNhMRUwEwYDVQQDDAxsb2NhbGhvc3QuY2EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJhQJRLBZHIqdd410PIpZ6FR52+Qwp6HPN+PTGw2z0+QAAA4prKgO36DiJLdnooBxYKGjGU3SMQ+KmWoTW+5ryd31Nv0q1H0QhtIig7FEGJEHsm+Qwvy879deF8JbmwapO7Fso9xHQPdmb/MY9QSsm6qAQKsB28HXH1OEsdLgwi3AgMBAAEwDQYJKoZIhvcNAQELBQADgYEABPb1Lnn5/B7EvXX1svM+MukIA37h0zGFUvKBugnNeWla1YE2ktlX0ZHo72rHefjzCD7QxCOTVur6+DhkcmHf8BEWbdaWjqQOviO41PKR8t1fqbg6LbpxxCsGqHtzV6wllj8lEKoU5I9n0DTG8YPQi7I+e1EFdpi9WuRgbk28KFc= + + + + + + + + urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity + + + + + + + + + + + Erin1+5ucRiA+ynqqwB/B0nhjErrQQh7RGlacJcd3iI= + + + ZHDgqCVUFXZ6V9LCMks1T1YhqV51OxUYgzCMkV2spmOgdllGKzaTgE6YgNK3p3uC bCnoIE+YdznubyLUUMA12Fa4nDoINfGMEUYWPhJUca1z6ReWVS0ovCkwgvNqcuCv 9in7/OsgMfSBTWXFlehZxvlayzM6naR02ZY4CA/ZbNk= + + + MIICQTCCAaoCAQEwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCc2UxCzAJBgNVBAgMAmFjMQ0wCwYDVQQHDAR1bWVhMRgwFgYDVQQKDA9UZXN0IFVuaXZlcnNpdHkxDTALBgNVBAsMBERlY2ExFTATBgNVBAMMDGxvY2FsaG9zdC5jYTAeFw0yMTAxMTUxOTI0MDlaFw0yMTAxMTUxOTI0MTBaMGkxCzAJBgNVBAYTAnNlMQswCQYDVQQIDAJhYzENMAsGA1UEBwwEdW1lYTEYMBYGA1UECgwPVGVzdCBVbml2ZXJzaXR5MQ0wCwYDVQQLDAREZWNhMRUwEwYDVQQDDAxsb2NhbGhvc3QuY2EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJhQJRLBZHIqdd410PIpZ6FR52+Qwp6HPN+PTGw2z0+QAAA4prKgO36DiJLdnooBxYKGjGU3SMQ+KmWoTW+5ryd31Nv0q1H0QhtIig7FEGJEHsm+Qwvy879deF8JbmwapO7Fso9xHQPdmb/MY9QSsm6qAQKsB28HXH1OEsdLgwi3AgMBAAEwDQYJKoZIhvcNAQELBQADgYEABPb1Lnn5/B7EvXX1svM+MukIA37h0zGFUvKBugnNeWla1YE2ktlX0ZHo72rHefjzCD7QxCOTVur6+DhkcmHf8BEWbdaWjqQOviO41PKR8t1fqbg6LbpxxCsGqHtzV6wllj8lEKoU5I9n0DTG8YPQi7I+e1EFdpi9WuRgbk28KFc= + + + + + 4bac270a2a532bd69e390a1ce3a347056235b9c87f8cfdeac011bc7da0b2a101 + + + + + + + urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword + http://www.example.com/login + + + + + staff + member + + + Derek + + + Jeter + + + foo@gmail.com + + + shortstop + + + + diff --git a/ckanext/saml2auth/tests/responses/test-simple-example.xml b/ckanext/saml2auth/tests/responses/test-simple-example.xml new file mode 100644 index 00000000..9799c073 --- /dev/null +++ b/ckanext/saml2auth/tests/responses/test-simple-example.xml @@ -0,0 +1,31 @@ + + urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity + + + + + https://organization.com/saml + + 44444444-4444-4444-4444-444444444444 + + + + + + + urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + + + + + example@email.com + + + + \ No newline at end of file diff --git a/ckanext/saml2auth/tests/responses/unsigned0.xml b/ckanext/saml2auth/tests/responses/unsigned0.xml new file mode 100644 index 00000000..4192560f --- /dev/null +++ b/ckanext/saml2auth/tests/responses/unsigned0.xml @@ -0,0 +1,48 @@ + + + http://idp.example.com/metadata.php + + + + + http://idp.example.com/metadata.php + + _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 + + + + + + + {{ entity_id }} + + + + + urn:oasis:names:tc:SAML:2.0:ac:classes:Password + + + + + test + + + test@example.com + + + users + examplerole1 + + + + \ No newline at end of file diff --git a/ckanext/saml2auth/tests/test_blueprint_get_request.py b/ckanext/saml2auth/tests/test_blueprint_get_request.py new file mode 100644 index 00000000..d7987ac8 --- /dev/null +++ b/ckanext/saml2auth/tests/test_blueprint_get_request.py @@ -0,0 +1,340 @@ +# encoding: utf-8 +import base64 +from datetime import datetime +from jinja2 import Template +from nose.tools import assert_equal, assert_in +import os +import pytest + +from saml2.xmldsig import SIG_RSA_SHA256 +from saml2.xmldsig import DIGEST_SHA256 +from saml2.saml import NAMEID_FORMAT_ENTITY +from saml2.saml import Issuer +from saml2.server import Server +from saml2.authn_context import INTERNETPROTOCOLPASSWORD + + +here = os.path.dirname(os.path.abspath(__file__)) +extras_folder = os.path.join(here, 'extras') +responses_folder = os.path.join(here, 'responses') + + +@pytest.mark.usefixtures(u'clean_db', u'clean_index') +@pytest.mark.ckan_config(u'ckan.plugins', u'saml2auth') +class TestGetRequest: + """ test getting request from external source """ + + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.location', u'local') + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.local_path', os.path.join(extras_folder, 'provider0', 'idp.xml')) + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_response_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_or_response_signed', u'False') + def test_empty_request(self, app): + + url = '/acs' + data = { + 'SAMLResponse': '' + } + response = app.post(url=url, status=400, params=data) + assert_equal(400, response.status_code) + assert_in(u'Empty login request', response) + + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.location', u'local') + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.local_path', os.path.join(extras_folder, 'provider0', 'idp.xml')) + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_response_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_or_response_signed', u'False') + def test_bad_request(self, app): + + url = '/acs' + data = { + 'SAMLResponse': '' + } + response = app.post(url=url, status=400, params=data) + assert_equal(400, response.status_code) + assert_in(u'Bad login request', response) + + def _b4_encode_string(self, message): + message_bytes = message.encode('ascii') + base64_bytes = base64.b64encode(message_bytes) + return base64_bytes.decode('ascii') + + @pytest.mark.ckan_config(u'ckanext.saml2auth.entity_id', u'urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity') + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.location', u'local') + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.local_path', os.path.join(extras_folder, 'provider0', 'idp.xml')) + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_response_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_or_response_signed', u'False') + def test_unsigned_request(self, app): + + # read about saml2 responses: https://www.samltool.com/generic_sso_res.php + unsigned_response_file = os.path.join(responses_folder, 'unsigned0.xml') + unsigned_response = open(unsigned_response_file).read() + # parse values + context = { + 'entity_id': 'urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity', + 'destination': 'http://test.ckan.net/acs', + 'recipient': 'http://test.ckan.net/acs', + 'issue_instant': datetime.now().isoformat() + } + t = Template(unsigned_response) + final_response = t.render(**context) + + encoded_response = self._b4_encode_string(final_response) + url = '/acs' + + data = { + 'SAMLResponse': encoded_response + } + response = app.post(url=url, params=data) + assert_equal(200, response.status_code) + + def render_file(self, path, context, save_as=None): + """ open file and render contect values """ + txt = open(path).read() + t = Template(txt) + response = t.render(**context) + + if save_as is not None: + f = open(save_as, 'w') + f.write(response) + f.close() + + return response + + def _load_base( + self, + destination='http://test.ckan.net/acs', + issuer_url='https://organization.com/saml/', + entity_id='urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity' + ): + + cert_file = os.path.join(extras_folder, 'provider1', 'mycert.pem') + f = open(cert_file) + cert = f.read() + f.close() + + x509_cert = cert.replace('\x0D', '') + x509_cert = x509_cert.replace('\r', '') + x509_cert = x509_cert.replace('\n', '') + x509_cert = x509_cert.replace('-----BEGIN CERTIFICATE-----', '') + x509_cert = x509_cert.replace('-----END CERTIFICATE-----', '') + x509_cert = x509_cert.replace(' ', '') + + self.context = { + 'entity_id': entity_id, + 'entity_session_id': '_session_ID_44444', + 'issuer_url': issuer_url, + 'destination': destination, + 'org_name': 'IDP Organization', + 'org_url': 'https://idp.organization.com', + 'redirect_login_url': 'https://idp.organization.com/auth', + 'attributes_url': 'https://idp.organization.com/attributes', + 'certificate': x509_cert + } + + self.render_file( + path=os.path.join(extras_folder, 'provider1', 'idp_cert_template.xml'), + context=self.context, + save_as=os.path.join(extras_folder, 'provider1', 'idp.xml') + ) + + key_file = os.path.join(extras_folder, 'provider1', 'mykey.pem') + cert_file = os.path.join(extras_folder, 'provider1', 'mycert.pem') + self.config = { + 'description': 'CKAN saml2 Service Provider', + 'service': { + 'sp': { + 'name_id_format': [ + 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', + 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', + 'urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress' + ], + 'want_response_signed': False, + 'name': 'CKAN SP', + 'want_assertions_signed': True, + 'allow_unsolicited': True, + 'endpoints': { + 'assertion_consumer_service': ['http://ckan:5000/acs', 'http://test.ckan.net/acs'] + }, + 'want_assertions_or_response_signed': True, + 'name_id_policy_format': [ + 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', + 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient', + 'urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress' + ] + } + }, + 'name_form': 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri', + 'debug': 0, + 'entityid': entity_id, + 'allow_unknown_attributes': 'true', + 'metadata': { + 'local': [os.path.join(extras_folder, 'provider1', 'idp.xml')] + }, + 'key_file': key_file, + 'cert_file': cert_file, + 'encryption_keypairs': [ + {'key_file': key_file, 'cert_file': cert_file} + ] + } + + def _generate_cert(self): + from saml2.cert import OpenSSLWrapper + + cert_info_ca = { + "cn": "localhost.ca", + "country_code": "se", + "state": "ac", + "city": "umea", + "organization": "Test University", + "organization_unit": "Deca" + } + + osw = OpenSSLWrapper() + ca_cert, ca_key = osw.create_certificate( + cert_info_ca, + request=False, + write_to_file=False + ) + + cert_str, key_str = osw.create_certificate(cert_info_ca, request=True) + re_cert_str = osw.create_cert_signed_certificate( + ca_cert, + ca_key, + cert_str, + valid_from=0, + valid_to=1 + ) + + f = open(os.path.join(extras_folder, 'provider1', 'mycert.pem'), 'w') + f.write(re_cert_str) + f.close() + + f = open(os.path.join(extras_folder, 'provider1', 'mykey.pem'), 'wb') + f.write(key_str) + f.close() + + self.key_str = key_str + self.cert_str = re_cert_str + + @pytest.mark.ckan_config(u'ckanext.saml2auth.entity_id', u'urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity') + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.location', u'local') + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.local_path', os.path.join(extras_folder, 'provider1', 'idp.xml')) + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_response_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_or_response_signed', u'True') + @pytest.mark.ckan_config(u'ckanext.saml2auth.key_file_path', os.path.join(extras_folder, 'provider1', 'mykey.pem')) + @pytest.mark.ckan_config(u'ckanext.saml2auth.cert_file_path', os.path.join(extras_folder, 'provider1', 'mycert.pem')) + def test_encrypted_assertion(self, app): + + self._generate_cert() + self._load_base() + + # define the user identity + IDENTITY = { + "eduPersonAffiliation": ["staff", "member"], + "surName": ["Jeter"], "givenName": ["Derek"], + "email": ["foo@gmail.com"], + "title": ["shortstop"] + } + + # start a server to generate the expected response + server = Server(self.config) + name_id = server.ident.transient_nameid(self.context['entity_id'], "id12") + issuer = Issuer(text=self.context['entity_id'], format=NAMEID_FORMAT_ENTITY) + authn = { + "class_ref": INTERNETPROTOCOLPASSWORD, + "authn_auth": "http://www.example.com/login" + } + response = server.create_authn_response( + identity=IDENTITY, + in_response_to="id12", + destination=self.context['destination'], + sp_entity_id=self.context['entity_id'], + name_id=name_id, + sign_assertion=True, + sign_response=True, + issuer=issuer, + sign_alg=SIG_RSA_SHA256, + digest_alg=DIGEST_SHA256, + encrypt_assertion=True, + encrypt_cert_assertion=self.cert_str, + encrypt_assertion_self_contained=True, + authn=authn + ) + + # finishe the response and b64 encode to send to our /acs endpoint + final_signed_response = response # .to_string() + + # To check the response + f = open(os.path.join(extras_folder, 'provider1', 'test-signed-encrypted.xml'), 'w') + f.write(final_signed_response) + f.close() + encoded_response = self._b4_encode_string(final_signed_response) + url = '/acs' + + data = { + 'SAMLResponse': encoded_response + } + response = app.post(url=url, params=data) + assert_equal(200, response.status_code) + + @pytest.mark.ckan_config(u'ckanext.saml2auth.entity_id', u'urn:gov:gsa:SAML:2.0.profiles:sp:sso:test:entity') + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.location', u'local') + @pytest.mark.ckan_config(u'ckanext.saml2auth.idp_metadata.local_path', os.path.join(extras_folder, 'provider1', 'idp.xml')) + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_response_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_signed', u'False') + @pytest.mark.ckan_config(u'ckanext.saml2auth.want_assertions_or_response_signed', u'True') + @pytest.mark.ckan_config(u'ckanext.saml2auth.key_file_path', os.path.join(extras_folder, 'provider1', 'mykey.pem')) + @pytest.mark.ckan_config(u'ckanext.saml2auth.cert_file_path', os.path.join(extras_folder, 'provider1', 'mycert.pem')) + def test_signed_not_encrypted_assertion(self, app): + + self._generate_cert() + self._load_base() + + # define the user identity + IDENTITY = { + "eduPersonAffiliation": ["staff", "member"], + "surName": ["Jeter"], "givenName": ["Derek"], + "email": ["foo@gmail.com"], + "title": ["shortstop"] + } + + # start a server to generate the expected response + server = Server(self.config) + name_id = server.ident.transient_nameid(self.context['entity_id'], "id12") + issuer = Issuer(text=self.context['entity_id'], format=NAMEID_FORMAT_ENTITY) + authn = { + "class_ref": INTERNETPROTOCOLPASSWORD, + "authn_auth": "http://www.example.com/login" + } + response = server.create_authn_response( + identity=IDENTITY, + in_response_to="id12", + destination=self.context['destination'], + sp_entity_id=self.context['entity_id'], + name_id=name_id, + sign_assertion=True, + sign_response=True, + issuer=issuer, + sign_alg=SIG_RSA_SHA256, + digest_alg=DIGEST_SHA256, + authn=authn + ) + + # finishe the response and b64 encode to send to our /acs endpoint + final_signed_response = response # .to_string() + + # To check the response + f = open(os.path.join(extras_folder, 'provider1', 'test-signed.xml'), 'w') + f.write(final_signed_response) + f.close() + encoded_response = self._b4_encode_string(final_signed_response) + url = '/acs' + + data = { + 'SAMLResponse': encoded_response + } + response = app.post(url=url, params=data) + assert_equal(200, response.status_code) diff --git a/ckanext/saml2auth/views/saml2auth.py b/ckanext/saml2auth/views/saml2auth.py index 1ea3a2eb..def90d05 100644 --- a/ckanext/saml2auth/views/saml2auth.py +++ b/ckanext/saml2auth/views/saml2auth.py @@ -1,4 +1,5 @@ # encoding: utf-8 +import logging from flask import Blueprint from saml2 import entity @@ -15,15 +16,12 @@ from ckanext.saml2auth import helpers as h +log = logging.getLogger(__name__) saml2auth = Blueprint(u'saml2auth', __name__) -def acs(): - u'''The location where the SAML assertion is sent with a HTTP POST. - This is often referred to as the SAML Assertion Consumer Service (ACS) URL. - ''' - g.user = None - g.userobj = None +def process_user(email, saml_id, firstname, lastname): + """ Check if CKAN-SAML user exists for the current SAML login """ context = { u'ignore_auth': True, @@ -31,76 +29,12 @@ def acs(): u'model': model } - saml_user_firstname = \ - config.get(u'ckanext.saml2auth.user_firstname') - saml_user_lastname = \ - config.get(u'ckanext.saml2auth.user_lastname') - saml_user_email = \ - config.get(u'ckanext.saml2auth.user_email') - - client = h.saml_client(sp_config()) - auth_response = client.parse_authn_request_response( - request.form.get(u'SAMLResponse', None), - entity.BINDING_HTTP_POST) - auth_response.get_identity() - user_info = auth_response.get_subject() - - # SAML username - unique - saml_id = user_info.text - # Required user attributes for user creation - email = auth_response.ava[saml_user_email][0] - firstname = auth_response.ava[saml_user_firstname][0] - lastname = auth_response.ava[saml_user_lastname][0] - - # Check if CKAN-SAML user exists for the current SAML login saml_user = model.Session.query(model.User) \ .filter(model.User.plugin_extras[(u'saml2auth', u'saml_id')].astext == saml_id) \ .first() # First we check if there is a SAML-CKAN user - if not saml_user: - # If there is no SAML user but there is a regular CKAN - # user with the same email as the current login, - # make that user a SAML-CKAN user and change - # it's pass so the user can use only SSO - ckan_user = model.User.by_email(email) - if ckan_user: - # If account exists and is deleted, reactivate it. - h.activate_user_if_deleted(ckan_user[0]) - - ckan_user_dict = model_dictize.user_dictize(ckan_user[0], context) - try: - ckan_user_dict[u'password'] = h.generate_password() - ckan_user_dict[u'plugin_extras'] = { - u'saml2auth': { - # Store the saml username - # in the corresponding CKAN user - u'saml_id': saml_id - } - } - g.user = logic.get_action(u'user_update')(context, ckan_user_dict)[u'name'] - except logic.ValidationError as e: - error_message = (e.error_summary or e.message or e.error_dict) - base.abort(400, error_message) - else: - data_dict = {u'name': _get_random_username_from_email(email), - u'fullname': u'{0} {1}'.format(firstname, lastname), - u'email': email, - u'password': h.generate_password(), - u'plugin_extras': { - u'saml2auth': { - # Store the saml username - # in the corresponding CKAN user - u'saml_id': saml_id - } - }} - try: - g.user = logic.get_action(u'user_create')(context, data_dict)[u'name'] - except logic.ValidationError as e: - error_message = (e.error_summary or e.message or e.error_dict) - base.abort(400, error_message) - - else: + if saml_user: # If account exists and is deleted, reactivate it. h.activate_user_if_deleted(saml_user) @@ -117,7 +51,97 @@ def acs(): except logic.ValidationError as e: error_message = (e.error_summary or e.message or e.error_dict) base.abort(400, error_message) - g.user = user_dict['name'] + return user_dict['name'] + + # If there is no SAML user but there is a regular CKAN + # user with the same email as the current login, + # make that user a SAML-CKAN user and change + # it's pass so the user can use only SSO + ckan_user = model.User.by_email(email) + if ckan_user: + # If account exists and is deleted, reactivate it. + h.activate_user_if_deleted(ckan_user[0]) + + ckan_user_dict = model_dictize.user_dictize(ckan_user[0], context) + try: + ckan_user_dict[u'password'] = h.generate_password() + ckan_user_dict[u'plugin_extras'] = { + u'saml2auth': { + # Store the saml username + # in the corresponding CKAN user + u'saml_id': saml_id + } + } + return logic.get_action(u'user_update')(context, ckan_user_dict)[u'name'] + except logic.ValidationError as e: + error_message = (e.error_summary or e.message or e.error_dict) + base.abort(400, error_message) + + data_dict = { + u'name': _get_random_username_from_email(email), + u'fullname': u'{0} {1}'.format(firstname, lastname), + u'email': email, + u'password': h.generate_password(), + u'plugin_extras': { + u'saml2auth': { + # Store the saml username + # in the corresponding CKAN user + u'saml_id': saml_id + } + } + } + try: + return logic.get_action(u'user_create')(context, data_dict)[u'name'] + except logic.ValidationError as e: + error_message = (e.error_summary or e.message or e.error_dict) + base.abort(400, error_message) + + +def acs(): + u'''The location where the SAML assertion is sent with a HTTP POST. + This is often referred to as the SAML Assertion Consumer Service (ACS) URL. + ''' + g.user = None + g.userobj = None + + saml_user_firstname = \ + config.get(u'ckanext.saml2auth.user_firstname') + saml_user_lastname = \ + config.get(u'ckanext.saml2auth.user_lastname') + saml_user_email = \ + config.get(u'ckanext.saml2auth.user_email') + + client = h.saml_client(sp_config()) + saml_response = request.form.get(u'SAMLResponse', None) + + error = None + try: + auth_response = client.parse_authn_request_response( + saml_response, + entity.BINDING_HTTP_POST) + except Exception as e: + error = 'Bad login request: {}'.format(e) + else: + if auth_response is None: + error = 'Empty login request' + + if error is not None: + log.error(error) + extra_vars = {u'code': [400], u'content': error} + return base.render(u'error_document_template.html', extra_vars), 400 + + auth_response.get_identity() + user_info = auth_response.get_subject() + + # SAML username - unique + saml_id = user_info.text + # Required user attributes for user creation + email = auth_response.ava[saml_user_email][0] + + firstname = auth_response.ava.get(saml_user_firstname, [email.split('@')[0]])[0] + lastname = auth_response.ava.get(saml_user_lastname, [email.split('@')[1]])[0] + + g.user = process_user(email, saml_id, firstname, lastname) # Check if the authenticated user email is in given list of emails # and make that user sysadmin and opposite