Commit cd941ead by Braden MacDonald

New SAML/Shibboleth tests - PR 8518

parent b4904adc
......@@ -368,7 +368,7 @@ def signin_user(request):
for msg in messages.get_messages(request):
if msg.extra_tags.split()[0] == "social-auth":
# msg may or may not be translated. Try translating [again] in case we are able to:
third_party_auth_error = _(msg) # pylint: disable=translation-of-non-string
third_party_auth_error = _(unicode(msg)) # pylint: disable=translation-of-non-string
break
context = {
......
......@@ -60,7 +60,7 @@ class Command(BaseCommand):
self.stdout.write("\n→ Fetching {}\n".format(url))
if not url.lower().startswith('https'):
self.stdout.write("→ WARNING: This URL is not secure! It should use HTTPS.\n")
response = requests.get(url, verify=True) # May raise HTTPError or SSLError
response = requests.get(url, verify=True) # May raise HTTPError or SSLError or ConnectionError
response.raise_for_status() # May raise an HTTPError
try:
......@@ -75,7 +75,7 @@ class Command(BaseCommand):
public_key, sso_url, expires_at = self._parse_metadata_xml(xml, entity_id)
self._update_data(entity_id, public_key, sso_url, expires_at)
except Exception as err: # pylint: disable=broad-except
self.stderr.write("→ ERROR: {}\n\n".format(err.message))
self.stderr.write(u"→ ERROR: {}\n\n".format(err.message))
@classmethod
def _parse_metadata_xml(cls, xml, entity_id):
......
......@@ -8,6 +8,7 @@ from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext as _
import json
import logging
from social.backends.base import BaseAuth
......@@ -53,7 +54,7 @@ class AuthNotConfigured(SocialAuthBaseException):
self.provider_name = provider_name
def __str__(self):
return 'Authentication with {} is currently unavailable.'.format(
return _('Authentication with {} is currently unavailable.').format(
self.provider_name
)
......@@ -313,10 +314,20 @@ class SAMLConfiguration(ConfigurationModel):
self.org_info_str = clean_json(self.org_info_str, dict)
self.other_config_str = clean_json(self.other_config_str, dict)
self.private_key = self.private_key.replace("-----BEGIN PRIVATE KEY-----", "").strip()
self.private_key = self.private_key.replace("-----END PRIVATE KEY-----", "").strip()
self.public_key = self.public_key.replace("-----BEGIN CERTIFICATE-----", "").strip()
self.public_key = self.public_key.replace("-----END CERTIFICATE-----", "").strip()
self.private_key = (
self.private_key
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END RSA PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.strip()
)
self.public_key = (
self.public_key
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----", "")
.strip()
)
def get_setting(self, name):
""" Get the value of a setting, or raise KeyError """
......
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDM+Nf7IeRdIIgYUke6sR3n7osHVYXwH6pb+Ovq8j3hUoy8kzT9
kJF0RB3h3Q2VJ3ZWiQtT94fZX2YYorVdoGVK2NWzjLwgpHUsgfeJq5pCjP0d2OQu
9Qvjg6YOtYP6PN3j7eK7pUcxQvIcaY9APDF57ua/zPsm3UzbjhRlJZQUewIDAQAB
AoGADWBsD/qdQaqe1x9/iOKINhuuPRNKw2n9nzT2iIW4nhzaDHB689VceL79SEE5
4rMJmQomkBtGZVxBeHgd5/dQxNy3bC9lPN1uoMuzjQs7UMk+lvy0MoHfiJcuIxPX
RdyZTV9LKN8vq+ZpVykVu6pBdDlne4psPZeQ76ynxke/24ECQQD3NX7JeluZ64la
tC6b3VHzA4Hd1qTXDWtEekh2WaR2xuKzcLyOWhqPIWprylBqVc1m+FA/LRRWQ9y6
vJMiXMk7AkEA1ELWj9DtZzk9BV1JxsDUUP0/IMAiYliVac3YrvQfys8APCY1xr9q
BAGurH4VWXuEnbx1yNXK89HqFI7kDrMtwQJAVTXtVAmHFZEosUk2X6d0He3xj8Py
4eXQObRk0daoaAC6F9weQnsweHGuOyVrfpvAx2OEVaJ2Rh3yMbPai5esDQJAS9Yh
gLqdx26M3bjJ3igQ82q3vkTHRCnwICA6la+FGFnC9LqWJg9HmmzbcqeNiy31YMHv
tzSjUV+jaXrwAkyEQQJAK/SCIVsWRhFe/ssr8hS//V+hZC4kvCv4b3NqzZK1x+Xm
7GaGMV0xEWN7shqVSRBU4O2vn/RWD6/6x3sHkU57qg==
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIICsDCCAhmgAwIBAgIJAJrENr8EPgpcMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTUwNjEzMDEwNTE0WhcNMjUwNjEyMDEwNTE0WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQDM+Nf7IeRdIIgYUke6sR3n7osHVYXwH6pb+Ovq8j3hUoy8kzT9kJF0RB3h3Q2V
J3ZWiQtT94fZX2YYorVdoGVK2NWzjLwgpHUsgfeJq5pCjP0d2OQu9Qvjg6YOtYP6
PN3j7eK7pUcxQvIcaY9APDF57ua/zPsm3UzbjhRlJZQUewIDAQABo4GnMIGkMB0G
A1UdDgQWBBTjOyPvAuej5q4C80jlFrQmOlszmzB1BgNVHSMEbjBsgBTjOyPvAuej
5q4C80jlFrQmOlszm6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt
U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrENr8E
PgpcMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAV5w0SxjUTFWfL3ZG
6sgA0gKf8aV8w3AlihLt9tKCRgrK4sBK9xmfwp/fnbdxkHU58iozI894HqmrRzCi
aRLWmy3W8640E/XCa6P+i8ET7RksgNJ5cD9WtISHkGc2dnW76+2nv8d24JKeIx2w
oJAtspMywzr0SoxDIJr42N6Kvjk=
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMoR8CP+HlvsPRwi
VCCuWxZOdNjYa4Qre3JEWPkqlUwpci1XGTBqH7DK9b2hmBXMjYoDKOnF5pL7Y453
3JSJ2+AG7D4AJGSotA3boKF18EDgeMzAWjAhDVhTprGz+/1G+W0R4SSyY5QGyBhL
Z36xF2w5HyeiqN/Iiq3QKGl2CFORAgMBAAECgYEAwH2CAudqSCqstAZHmbI99uva
B09ybD93owxUrVbRTfIVX/eeeS4+7g0JNxGebPWkxxnneXoaAV4UIn0v1RfWKMs3
QGiBsOSup1DWWwkBfvQ1hNlJfVCqgZH1QVbhPpw9M9gxhLZQaSZoI/qY/8n/54L0
zU4S6VYBH6hnkgZZmiECQQDpYUS8HgnkMUX/qcDNBJT23qHewHsZOe6uqC+7+YxQ
xKT8iCxybDbZU7hmZ1Av8Ns4iF7EvZ0faFM8Ls76wFX1AkEA3afLUMLHfTx40XwO
oU7GWrYFyLNCc3/7JeWi6ZKzwzQqiGvFderRf/QGQsCtpLQ8VoLz/knF9TkQdSh6
yuIprQJATfcmxUmruEYVwnFtbZBoS4jYvtfCyAyohkS9naiijaEEFTFQ1/D66eOk
KOG+0iU+t0YnksZdpU5u8B4bG34BuQJAXv6FhTQk+MhM40KupnUzTzcJXY1t4kAs
K36yBjZoMjWOMO83LiUX6iVz9XHMOXVBEraGySlm3IS7R+q0TXUF9QJAQ69wautf
8q1OQiLcg5WTFmSFBEXqAvVwX6FcDSxor9UnI0iHwyKBss3a2IXY9LoTPTjR5SHh
GDq2lXmP+kmbnQ==
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIICWDCCAcGgAwIBAgIJAMlM2wrOvplkMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTUwNjEzMDEyMTAwWhcNMjUwNjEyMDEyMTAwWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQDKEfAj/h5b7D0cIlQgrlsWTnTY2GuEK3tyRFj5KpVMKXItVxkwah+wyvW9oZgV
zI2KAyjpxeaS+2OOd9yUidvgBuw+ACRkqLQN26ChdfBA4HjMwFowIQ1YU6axs/v9
RvltEeEksmOUBsgYS2d+sRdsOR8noqjfyIqt0ChpdghTkQIDAQABo1AwTjAdBgNV
HQ4EFgQUU0TNPc1yGas/W4HJl/Hgtrmdu6MwHwYDVR0jBBgwFoAUU0TNPc1yGas/
W4HJl/Hgtrmdu6MwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCE4BqJ
v2s99DS16NbZtR7tpqXDxiDaCg59VtgcHQwxN4qXcixZi5N4yRvzjYschAQN5tQ6
bofXdIK3tJY9Ynm0KPO+5l0RCv7CkhNgftTww0bWC91xaHJ/y66AqONuLpaP6s43
SZYG2D6ric57ZY4kQ6ZlUv854TPzmvapnGG7Hw==
-----END CERTIFICATE-----
<!-- Cached and simplified copy of https://www.testshib.org/metadata/testshib-providers.xml -->
<EntitiesDescriptor Name="urn:mace:shibboleth:testshib:two"
xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:mdalg="urn:oasis:names:tc:SAML:metadata:algsupport" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<EntityDescriptor entityID="https://idp.testshib.org/idp/shibboleth">
<Extensions>
<mdalg:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512" />
<mdalg:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#sha384" />
<mdalg:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<mdalg:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<mdalg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" />
<mdalg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha384" />
<mdalg:SigningMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<mdalg:SigningMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
</Extensions>
<IDPSSODescriptor
protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
<Extensions>
<shibmd:Scope regexp="false">testshib.org</shibmd:Scope>
<mdui:UIInfo>
<mdui:DisplayName xml:lang="en">TestShib Test IdP</mdui:DisplayName>
<mdui:Description xml:lang="en">TestShib IdP. Use this as a source of attributes
for your test SP.</mdui:Description>
<mdui:Logo height="88" width="253"
>https://www.testshib.org/testshibtwo.jpg</mdui:Logo>
</mdui:UIInfo>
</Extensions>
<KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzEV
MBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMREwDwYD
VQQKEwhUZXN0U2hpYjEZMBcGA1UEAxMQaWRwLnRlc3RzaGliLm9yZzAeFw0wNjA4
MzAyMTEyMjVaFw0xNjA4MjcyMTEyMjVaMGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQI
EwxQZW5uc3lsdmFuaWExEzARBgNVBAcTClBpdHRzYnVyZ2gxETAPBgNVBAoTCFRl
c3RTaGliMRkwFwYDVQQDExBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEArYkCGuTmJp9eAOSGHwRJo1SNatB5ZOKqDM9ysg7C
yVTDClcpu93gSP10nH4gkCZOlnESNgttg0r+MqL8tfJC6ybddEFB3YBo8PZajKSe
3OQ01Ow3yT4I+Wdg1tsTpSge9gEz7SrC07EkYmHuPtd71CHiUaCWDv+xVfUQX0aT
NPFmDixzUjoYzbGDrtAyCqA8f9CN2txIfJnpHE6q6CmKcoLADS4UrNPlhHSzd614
kR/JYiks0K4kbRqCQF0Dv0P5Di+rEfefC6glV8ysC8dB5/9nb0yh/ojRuJGmgMWH
gWk6h0ihjihqiu4jACovUZ7vVOCgSE5Ipn7OIwqd93zp2wIDAQABo4HEMIHBMB0G
A1UdDgQWBBSsBQ869nh83KqZr5jArr4/7b+QazCBkQYDVR0jBIGJMIGGgBSsBQ86
9nh83KqZr5jArr4/7b+Qa6FrpGkwZzELMAkGA1UEBhMCVVMxFTATBgNVBAgTDFBl
bm5zeWx2YW5pYTETMBEGA1UEBxMKUGl0dHNidXJnaDERMA8GA1UEChMIVGVzdFNo
aWIxGTAXBgNVBAMTEGlkcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zAN
BgkqhkiG9w0BAQUFAAOCAQEAjR29PhrCbk8qLN5MFfSVk98t3CT9jHZoYxd8QMRL
I4j7iYQxXiGJTT1FXs1nd4Rha9un+LqTfeMMYqISdDDI6tv8iNpkOAvZZUosVkUo
93pv1T0RPz35hcHHYq2yee59HJOco2bFlcsH8JBXRSRrJ3Q7Eut+z9uo80JdGNJ4
/SJy5UorZ8KazGj16lfJhOBXldgrhppQBb0Nq6HKHguqmwRfJ+WkxemZXzhediAj
Geka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr
8K/qhmFT2nIQi538n6rVYLeWj8Bbnl+ev0peYzxFyF5sQA==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc" />
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
</KeyDescriptor>
<ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
Location="https://idp.testshib.org:8443/idp/profile/SAML1/SOAP/ArtifactResolution"
index="1"/>
<ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="https://idp.testshib.org:8443/idp/profile/SAML2/SOAP/ArtifactResolution"
index="2"/>
<NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
Location="https://idp.testshib.org/idp/profile/Shibboleth/SSO"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://idp.testshib.org/idp/profile/SAML2/POST/SSO"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="https://idp.testshib.org/idp/profile/SAML2/SOAP/ECP"/>
</IDPSSODescriptor>
<AttributeAuthorityDescriptor
protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzEV
MBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMREwDwYD
VQQKEwhUZXN0U2hpYjEZMBcGA1UEAxMQaWRwLnRlc3RzaGliLm9yZzAeFw0wNjA4
MzAyMTEyMjVaFw0xNjA4MjcyMTEyMjVaMGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQI
EwxQZW5uc3lsdmFuaWExEzARBgNVBAcTClBpdHRzYnVyZ2gxETAPBgNVBAoTCFRl
c3RTaGliMRkwFwYDVQQDExBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEArYkCGuTmJp9eAOSGHwRJo1SNatB5ZOKqDM9ysg7C
yVTDClcpu93gSP10nH4gkCZOlnESNgttg0r+MqL8tfJC6ybddEFB3YBo8PZajKSe
3OQ01Ow3yT4I+Wdg1tsTpSge9gEz7SrC07EkYmHuPtd71CHiUaCWDv+xVfUQX0aT
NPFmDixzUjoYzbGDrtAyCqA8f9CN2txIfJnpHE6q6CmKcoLADS4UrNPlhHSzd614
kR/JYiks0K4kbRqCQF0Dv0P5Di+rEfefC6glV8ysC8dB5/9nb0yh/ojRuJGmgMWH
gWk6h0ihjihqiu4jACovUZ7vVOCgSE5Ipn7OIwqd93zp2wIDAQABo4HEMIHBMB0G
A1UdDgQWBBSsBQ869nh83KqZr5jArr4/7b+QazCBkQYDVR0jBIGJMIGGgBSsBQ86
9nh83KqZr5jArr4/7b+Qa6FrpGkwZzELMAkGA1UEBhMCVVMxFTATBgNVBAgTDFBl
bm5zeWx2YW5pYTETMBEGA1UEBxMKUGl0dHNidXJnaDERMA8GA1UEChMIVGVzdFNo
aWIxGTAXBgNVBAMTEGlkcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zAN
BgkqhkiG9w0BAQUFAAOCAQEAjR29PhrCbk8qLN5MFfSVk98t3CT9jHZoYxd8QMRL
I4j7iYQxXiGJTT1FXs1nd4Rha9un+LqTfeMMYqISdDDI6tv8iNpkOAvZZUosVkUo
93pv1T0RPz35hcHHYq2yee59HJOco2bFlcsH8JBXRSRrJ3Q7Eut+z9uo80JdGNJ4
/SJy5UorZ8KazGj16lfJhOBXldgrhppQBb0Nq6HKHguqmwRfJ+WkxemZXzhediAj
Geka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr
8K/qhmFT2nIQi538n6rVYLeWj8Bbnl+ev0peYzxFyF5sQA==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes192-cbc" />
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
</KeyDescriptor>
<AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
Location="https://idp.testshib.org:8443/idp/profile/SAML1/SOAP/AttributeQuery"/>
<AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
Location="https://idp.testshib.org:8443/idp/profile/SAML2/SOAP/AttributeQuery"/>
<NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
</AttributeAuthorityDescriptor>
<Organization>
<OrganizationName xml:lang="en">TestShib Two Identity Provider</OrganizationName>
<OrganizationDisplayName xml:lang="en">TestShib Two</OrganizationDisplayName>
<OrganizationURL xml:lang="en">http://www.testshib.org/testshib-two/</OrganizationURL>
</Organization>
<ContactPerson contactType="technical">
<GivenName>Nate</GivenName>
<SurName>Klingenstein</SurName>
<EmailAddress>ndk@internet2.edu</EmailAddress>
</ContactPerson>
</EntityDescriptor>
</EntitiesDescriptor>
RelayState=testshib&SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL2V4YW1wbGUubm9uZS9hdXRoL2NvbXBsZXRlL3RwYS1zYW1sLyIgSUQ9Il9hMDdmZDlhMDg0ODM3M2U1NTMyMGRjMzQyNDk0ZWY1ZCIgSW5SZXNwb25zZVRvPSJURVNUSUQiIElzc3VlSW5zdGFudD0iMjAxNS0wNi0xNVQwMDowNzoxNS4xODhaIiBWZXJzaW9uPSIyLjAiPjxzYW1sMjpJc3N1ZXIgeG1sbnM6c2FtbDI9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6ZW50aXR5Ij5odHRwczovL2lkcC50ZXN0c2hpYi5vcmcvaWRwL3NoaWJib2xldGg8L3NhbWwyOklzc3Vlcj48c2FtbDJwOlN0YXR1cz48c2FtbDJwOlN0YXR1c0NvZGUgVmFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIvPjwvc2FtbDJwOlN0YXR1cz48c2FtbDI6RW5jcnlwdGVkQXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj48eGVuYzpFbmNyeXB0ZWREYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIgSWQ9Il9kYzc3ODI3YmY1ZGMzYjZmNGQzNjkzZWUzMTU2YmE1MiIgVHlwZT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjRWxlbWVudCI%2BPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI2FlczEyOC1jYmMiIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyIvPjxkczpLZXlJbmZvIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj48eGVuYzpFbmNyeXB0ZWRLZXkgSWQ9Il85NzhhN2I2NDE5YTMxOGQ4NmUzMzE0Y2Y5YjFjOTEzZiIgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIj48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjcnNhLW9hZXAtbWdmMXAiIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIiB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyIvPjwveGVuYzpFbmNyeXB0aW9uTWV0aG9kPjxkczpLZXlJbmZvPjxkczpYNTA5RGF0YT48ZHM6WDUwOUNlcnRpZmljYXRlPk1JSUNzRENDQWhtZ0F3SUJBZ0lKQUpyRU5yOEVQZ3BjTUEwR0NTcUdTSWIzRFFFQkJRVUFNRVV4Q3pBSkJnTlZCQVlUQWtGVk1STXcKRVFZRFZRUUlFd3BUYjIxbExWTjBZWFJsTVNFd0h3WURWUVFLRXhoSmJuUmxjbTVsZENCWGFXUm5hWFJ6SUZCMGVTQk1kR1F3SGhjTgpNVFV3TmpFek1ERXdOVEUwV2hjTk1qVXdOakV5TURFd05URTBXakJGTVFzd0NRWURWUVFHRXdKQlZURVRNQkVHQTFVRUNCTUtVMjl0ClpTMVRkR0YwWlRFaE1COEdBMVVFQ2hNWVNXNTBaWEp1WlhRZ1YybGtaMmwwY3lCUWRIa2dUSFJrTUlHZk1BMEdDU3FHU0liM0RRRUIKQVFVQUE0R05BRENCaVFLQmdRRE0rTmY3SWVSZElJZ1lVa2U2c1Izbjdvc0hWWVh3SDZwYitPdnE4ajNoVW95OGt6VDlrSkYwUkIzaAozUTJWSjNaV2lRdFQ5NGZaWDJZWW9yVmRvR1ZLMk5XempMd2dwSFVzZ2ZlSnE1cENqUDBkMk9RdTlRdmpnNllPdFlQNlBOM2o3ZUs3CnBVY3hRdkljYVk5QVBERjU3dWEvelBzbTNVemJqaFJsSlpRVWV3SURBUUFCbzRHbk1JR2tNQjBHQTFVZERnUVdCQlRqT3lQdkF1ZWoKNXE0QzgwamxGclFtT2xzem16QjFCZ05WSFNNRWJqQnNnQlRqT3lQdkF1ZWo1cTRDODBqbEZyUW1PbHN6bTZGSnBFY3dSVEVMTUFrRwpBMVVFQmhNQ1FWVXhFekFSQmdOVkJBZ1RDbE52YldVdFUzUmhkR1V4SVRBZkJnTlZCQW9UR0VsdWRHVnlibVYwSUZkcFpHZHBkSE1nClVIUjVJRXgwWklJSkFKckVOcjhFUGdwY01Bd0dBMVVkRXdRRk1BTUJBZjh3RFFZSktvWklodmNOQVFFRkJRQURnWUVBVjV3MFN4alUKVEZXZkwzWkc2c2dBMGdLZjhhVjh3M0FsaWhMdDl0S0NSZ3JLNHNCSzl4bWZ3cC9mbmJkeGtIVTU4aW96STg5NEhxbXJSekNpYVJMVwpteTNXODY0MEUvWENhNlAraThFVDdSa3NnTko1Y0Q5V3RJU0hrR2MyZG5XNzYrMm52OGQyNEpLZUl4MndvSkF0c3BNeXd6cjBTb3hECklKcjQyTjZLdmprPTwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE%2BPC9kczpLZXlJbmZvPjx4ZW5jOkNpcGhlckRhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIj48eGVuYzpDaXBoZXJWYWx1ZT5sWEEvSGI2SlIxaW1UM2M1citrQU9taHVieVYvOUpqTUNzdkRJYlBEckxVR1g0aWFVbGl6c2d0dkdzRzdYOVpQWUxhc281U2ZlK1dTbVpKeW9tMGc0UU9HOWd6R3FIVGwybzFGMlJib0ZKS2FzaDZoQ011c2dSRmpJWElSUzdvTWJJTGxmcGhvcUN2c0pGdUpKY1FldU9SeWwyZmlrcUJSclhjNmwyMks2YzA9PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWRLZXk%2BPC9kczpLZXlJbmZvPjx4ZW5jOkNpcGhlckRhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIj48eGVuYzpDaXBoZXJWYWx1ZT5UQWdqL1d3ait2b0ppdHNhUE9IdmFSUHN3WE5oaHhEMmx2Q0t6MXJzQmtYbmJheVhlNmJucE1CMHg5aWpFMVdqOFV4YmtJQmhBMTEwRHhhTjZabkhReUs4amI3U29VU09jQkovUGxrT1NJc21zcS94dTVZTDJrdC9qdTdwRDR6K3BpcUlJbWRSVXdkcEhPdXFSVUxzUUJNU1NCRU5QT2sxQkRjcWZxb091N1g1VFIyOHE2eEwwUlFoV2NqMHBoTW9BMHdZS2ZFU0tacmRDQVRLcXVFcUJTRGJzRThNekh1cndiUkVkcjM5YnpBMGRNTm1zVFBFWmpsdkdqSEJmdVRaS3VPWXBoR3lFV1FVa29PbEtMQ1ROQ3VQYnhaWjFZNi80SGhlTGpQL3pnSU9ieWdLVkdadmNBSWpyTmZXNi9iQkJ0MVJjbndKV3pTZU5XR3hnd2hnZTJDank4RVFTUHJxbWZYK29VSVY3NExmT1NhV2JONXRwK3dtbzgveGlzWFErRysyZzA1ZnBiZEVmcEhacFZhMmFZY24xYWRtYy9uK3p1U0w2LzRTVWtXcTIvRHpRTTBWQVNQN2o0MXNIc0wwZEt2ZkhiQk5BcFB3NzdyTWdXRkwxcjVRajZiUmVjSElkQ3ozamZLcDZtdzBURzFQaUxCc2FXZ2wzYnZVR0lJNmxOWTg1TStFWHpoUnNCY29uVjgwUWFnKzBoTks3OWNteFkvOFByOGZkdXErMmgva3UxUzN2REo0cTlpR0FPaG1MUUFrWEQwWGF5Q1hXa0x1MEFyTmdOalNjaU55UXY2c3RmOUx1NGdUd1lKWUsyV0lWYUEvTzdMbUl2WmxkL0thL2VSaHdJQnV5YnpmemxWSVA0a2VVVmpldDVZMnZUVzA5bG02bWQzV1dmOTYrb21yNGVBQjllWjBmaFRxRkV4UzJkQVFqWi9JV21ZNnNXQXJCSkg4aEhya0IxdVBrQkZYTVFNbSs5T3ZKMWpnWXZtdVJUcHZzc1ZlL1MwWDhIVlVSdUpZVDVBY09YNHhuTWw0QkpXdnlLbzFFUms4bmRZOXJTTkJscGUrSXRUQXY5ZDgveVdJcGlYRVRLWGpKbTlVVWt6NnIyclhDUkN0VDhWa3J2M29jZmY0Z09nUjlTSWR0b21hU2w5RG9sTjVFL0ljTm1DUkhrbTR0U1J4bDNrNzNiMy8rNUxJNDhHc3l2c1VsNlNaZ1dCNmhSdDZaNDNuS2Iza3czOWd1cjk0VStrcURuUndub3lyeDVoc3N1NWRVVFNURkJPajhENGk0SjZwYi9sQ3FvWDVtR3lNODVJWnpxQk1Sa3IzeldPUzg3SVhBQm1uVDZ6aE1NdEpzN3EwRG9WZUhQbExUeUlVRHFMZWlaMjREQzN3Z1BjNmh0STVNeU1EZG5OM0hLczdnT3lhY3ppV280c3I1RGJCc3FqaFd1ZzJIanJEQ2hXZUszY1NuTlcwbkZCT1RJOFZMSFJXK2lhd3ZJWnUwZGdSenR1aEFjQ3lCelB0SHUrSzZlZmgyR0lNT0NhVVJCUzA3eGRETkw2MG9jOTBZZUlmOGV5WDFqV3ZDaDJZSnNxMDRtU3AySUFFQzFQSS9mc3lleGIrU2lMWTNzR1FyNEQ1bGx6ZHh6VEpKa2tYd2c4dHMxeklvSEx2TDdkQ29BRVNmNDJVVERFdWo4bGtadjYrM0JkNkwrM3Z6WFFPb2xwQ1kxeGRodVFOQjhsbG5KWHRNMmdtb1NEcmRwN1JzZWtTM2hkOWkxSkxkYWR1bW9oM0hmd0x6d0c4SkpCRkpmb3k0cGtzM05vMWFyT0lKRVBzZ3ZCMzZZUitScFFvc2hHM01PS0EwTUphcDNJbE5yQlVjdmw4WW5jSGkzbE9CUWpySFcrSURVVHhxZG9jdmw0NTBscmVUdkVoZXYraUFLU0J4alhNclNDQytaOWFIS1BJR2s4UzR2dWtudURIMUpsdTNLSFBwNFA5bnhSMnp3V3ZldktkYjNLdjZ3emhidmlxR0pPY05CeUZzRVJvNkEwSVVNVHQvOFJkQzJkZndyTjNBTHZ4dktrTmE0c3dEcElqYkRkb3RrZWgzT29FTVRETGE4M1R6Ym9ROFdWcDJLbEJiZDlWdXVNRm8xVUtrN2Q0dFh4VXRnWkZ3YlZQUWNzU21TV1dud1QvMFhxdDB0Yk8zQ0lnNkVZOHFXaUxoWU5naGw4eUhXTGIxbUUzVWNoSUhhVDBoWmNXTXVLMmtNbVpNMlJJeWVJY3o4M2NmY2lSWFJUb0g2TzFPenF5ZytBVUZiendibXQxR2d3V1hnMTdZQVJxSFViaDZwdktUMGJFRzdPUDRMZ0U4ajQvdTJoQjFraUFnUXpQSC84RHVCQlQwdkdOeElnKzhNWi9ObUFmUWRxODBlcVNQTDVpczlGbkZVei9GUzYzdEVCb0xYZnk5VzFRUjBsZ3VUY3o5b3l1dkdPdmVDZUt4RE9pZU9ZSktFTTlhVFVzVjRGc2c5M0NzMTQvRkhXQzJpclRKN1J3Ymg4eG1WUzBqUnVFQUdBejdETDNaSkFiRElLUUM5ZWZ2QUYxRHREVjZiVEI5cnNlczdsOWlDS3RnSnRWRE83Nmc5M2tWSGwvdXpYbVhwQ0NveS9XYStvVFErek5WL0lMbXAwVnorRnhJRHROUmcyL0V3eENyN042RW05ZmRibHgrMjYyWkdEVWVKcXcwNjB3L0RDTThiMDJmc2dyQTJ4NUVvcjF4a0tmcXMvaFMvVlo5azlVRHB0ZVFaMWdrSEU5TEN6TzY1azFRRTNENDNRK21ReFM2cGRJL1BpOUUxVkRoN3pMemdpOUVscXFLVHZNYjhPQUxmdSthTStPWlFaUjN4L21UVjQycTNmQlZya2lYRHpNd0pkclhjNW5UcmxLSDJTSUZsV2JaUzNXK0tHUVpoVzJ4dVRzdU9yS1FiVDY2OEVRbEpNTEhuckxLQTFrV0NvalJKV1hqZGFxSGVrWURDWjlFcDZaRnZ4NjJzWlRTdWMrVlNjK3UxSnZjT0w3NTZzNEFFWnNjR3ZnbXNxbUx5MldYOHpLbGw2MEdVUlE0YlBHMHd3YVUxRGF3dnJTejZqTCtyUjVBTTlGdTFlQi9WaUZSVVo4R0prR0VIOTdRWmNKcjV6RFpXNnZkOVBZRnlTd0Zqa21rOWxuZ1NFdFNPZFFITmZTZXRxbXZrcS9HbTB2enlTSWVyM3N5OCszR2IzNjJHaHd5MFNCekp6dEdHM0dQWTE3NUxRK3FWcGJCc1Y0MmgvOExPcnFaUGpVY0RrUk1NMU1LTHBtOEpSeVRGeWM5c0NxZ1lVMzNBbTByRzlkTHgxeVVKaTkwUncxc1pDa0lYUFIxYWFRUlkyNFEyekhVNjNBNHZDQVVaaVBOSHdVZlI4Qzk0cDdDeks5UTlreFpoaGZ6bjRBOFdadVZMSjkvWWI3d1RmNWQvNlNmNVFXWUM2anAvbUVWMDAxRnVBZnUzcUZNNmNuNXpXV2xkR2tjaU5RcUJ3SmZoWk9oUnk1VjBEaW5rMDBjSVlncDFmVnVnWHFkR1grV1ZCQkJ5M29va0toYW05S2RPc1N6aEI4NXZyN3h6R2JsREVXVGhFN0F5U3duRUVVNnBjcXpxR1E3Mk9KLytWS2I4ZVNPVWxzQW1LZnZ0czgzTHBYR2o0dkdRR1UwNVptK2grakdWeEpjMTJSQW5lbUhYK1FiNVhJdGk4ek1CazJkT2I4NUVPRUlvVnduWXpmSmhqQmtpOFhYMUtWaTVWbDE5dmV4OExxQ2pLdW9JeUsrSFpVQWtGMmpqY01WenUybXEvM3JPblJvTUhqVEszbFpGZm53S1E3WUxqd2dlVk45QnBmNm1Zem5Bb1RhVG1kQTUza29ocnMrVExuK0toUEpCRFc3Rml6L0ZDbVhzU2dJQ2tQcHAzVnJnQkY0N1ZDUEtPQi9yR2hPaklKd0V2bjgvZ0o1MU5qSmY3NkI4OUxHKzhLOXZpV2ZCeDRvMGxIczZLRmtCSk5RSTF4TCtVRGREWThURitlNXFtaVg3TTh6QmVsQjJlalZKaW9DRkhUcG9mRlZyT0kzTTlGcUk0Mk9KYVdrQytFTFJCaHR6dmJxaisrMWNOdlArcXVKQkRseTZNY2d3SE5BbzlhOHZIcjNjTmRHdmdjVGNFemx1aHpXOW9wb0dSTERPbHRUT0RqOUNQeUVXU2VablFxV2pHRGZiZkRkWm85bTRTWmUxTjQzNUNZYzJBY2VtR3JDdjAySUhyNmgvR1dZMTRFRlJ6T3crTFQ0Skg2TDBzM2w5V1JlZVlvR2NJc2RxYmhrN1Y0OXF5b1lBKzRlb3IvUi9VRzhZaFFYVzJJckQxaTVveDRGTEtXa0Q1UWtKRWU0VmpyNUVRTTBNTHJzNnE4YW5URklITGV3YXE4V0lnanJLS2FtRWloU2tGK2RnMHdScllTeUpuSzdySUsxWi9GQTlPakFUeGlIN0Y1TWcraGhlbXozYlFrU1FTaGN2T1lVSEdjU2sreHQrMXBuSG1lc1ZZTUlCb2d4S3JkUy9yLy9LM0lxdWR1Wko1bE5oVElXZ3dISkpiTVBHTG9mQUJybTlwZUFCVG5mVUFkcDIxK08wQmluVjdYZVg4RXFmVGhVejhrY2Z0MmgrSW5hajZwd1lDdGJ2MmE4dld5UXFKM0haNnBiZHV3bUJFVzhMa3Y2Qm44Yng5TllZMTdyMDliWDNCbmF6QWpGVW1EN3l6R3pLSnR2c1ZVOUw0RmMyU1B4ZUpQVjluQy91c0ZKc1ZlTEFMMTdqVERyV1k3NXhRdkpDVkJGNFlIS0JHaiszSk5WSWRudElOam5DTWhRak1CZU02V29RRkcyei9IQU9hU1lnaXlJSnlaNzd4MW1UYUtuTkNvSTlzZ3JNZkJzUE9mTXJUbkVGbTR5SThERjFGSTh4VnVONnpBcWp2dGkzNVczRGIzdG9Wa0pYVWk0OStKZzIxRWwxSkNqcjJoVmhBQjN5dE5kV2VnTXlTSFRnY0tRVVdRQmt3WUlEbUphdjY5am9udE9RdWpVbGFIM0lBRExHMUpPSWxuREduN0F2OUhkb0JzdnA3MDdLN20xZmJwOUxZK3NCdkwzcXdNbmpZbGhuZHBmYjdVbGxEajl2SCtuNDE5Z0FWMU9GUkRmVHVkVkRpdEFlQzI5ZWRjSmFGZXBYbmpKeHpvTzNqZHFrVTBiMWRmeEo2T1BCa01XSzJKcDZqTmllVlF6emwzRGJWMnRjcTNpekhQVmRySVZ2eEFqVWl3eENWK1VLTzZmMmlXaG9jQjhsWWE5U2xPOTRxd1Y2SkxSbDlIU3pFbDZtQUdRKzRCaW90aEhleDd2ODlGYnJ3eW00UjkwOGl4cU5odzNCc25wcHkyVzhlQXJtcENxMTRHdjlpM3R4em1mS1c3allIV2xWT0JQZFdoSnQ5NTZWbmliV2pWaVBBME9WOVNRWFZ6L2tpSit1WnZzT0FPY1h0YVRDaTZQL0dDMHJyRmhLc1paQW82ZE5paUF0N1BtVzduQjc5RDU5SzRBd3RNaW5iV016TjRQQzFGcHA5eklQTlEvU0laY1IwN0FjMnJ0ODdoQ0JPNUNIY0xhL2EwZDcxZDNZenEzNFlSWDZRYmdRY2taVytLN0FTbGpUcnFQczBHUXo3eFVRVjY1SnRBUTZvbjBxWGwwQ3drdUFCR0gvVVZ3TXpTalBuSnh5WmhQczN5NFhmdTVOUWhabkQvWWNtenR2Y0tkanBvMExSZzkxZnN3QjQybS8vL282VTd5K1hJNGlXMDMxRFQ0R3ViVWV4NW5mZTByWjdlRTRMaGJkaWJiWVRkTDFESHJuTGhYUVpDaEFRSno5SVV1OWYweEtMY3ovR0lubzRQRC9VK2hKOVkrb3FtelpGM3NhVXpKNTRJY1lGeEVROGo5L25nOTVpWHc0SWM5aXovZmY0Wm9hN2hJMTUzalBMNHFOcS91akphYXd2MXpxRlFhRVJYZUU1UyswZTMwaC9UKzByMmMxMTJkYndZdXljN2UwNi9RVnJpckdSRnQyZnRHUFN2VVRMdTJKVzRBUHVDN1NpNmpYWjQ1QlArenJTNzJteWdQQm9LTSt6N2RYTWMySmhQTnhQcmlGRVJlUmJnQlFFM1RSckpMSVpaR3NPczNJbFdBdGpLRTJaVFd0bjNJQmFQM0loY2sySFJ4MnRXRDJYRnYweTQ1bXhlQnkwMXY0cjAwQjJnMW9JVlYvZkgvajZUQnFLU2VENjBWRGZ3OXMxeXU1VUVhbzRicy9oZmFjWVpZZzg1Y2daS1QrTkZCeHBadG84M3E3bzRBeUNMQTl5dzQ2ZFRzdUlSYnBsMC81MVp0R0hDa29YMkdOa0JRc2pFWTVSNHVoZGRJNnBFSkxuaWNvVnpGS0dsTFErZDJMKy9odnBldHUxMGFTVHhEMmJqWlMrUVlHM0VLZ0VvbDZveThVTHl4bjRMb041bk1zV2d3N0p6NDJWT0V1ZHFENHY0ZVFUdXB0NkRpQzhvRzVzaVdWZER2amZpUHdwN1l5cEFZOE8reFBLOVgzWkU2bVR6aEV6TGxud0Jtc1RuQWpjMTRsYTVha1FBdHJRaXZIY1Vmb1pCcWtQKzFqTmdOZ0lYQlB3WlRBMHh5aEVuYlRMK3JPd3dzcUEycjdMbUlTaFpNa3V0cUQzdC9GbmNBTCtkaTJvQ2pBOVRNdnVwMnRqNm5FaXNwbXpYcmE1WWVnUlVjOE5UNjJxL1dXU25aKy9pb0hUMWFjeldJWG9sN1JhZ0VlQlRlMkFlVVEvam5wd2RwR3gzUldLeGIrajdtN0RuaVpoSmlzZUw5a2JleC9RQUFBV1VxajRBQldtdEptQ1QrUy9KVit6R2FGaU4ybXFzcUd0Mm9qN2w1UzRmMkozcXdxaEpNbXVDRGlteUpzQ0FzOVlNQVFmOXRFRVNSa3pKTy9wOHdEamlFbGRZRUdMNkl2RHJkWHFERzhSTGJkQnczTHJxeUJQOEYwU3lzWmlHYWdXY3BSZGY4NmJKdXgra2gxOXo4eldvSWM4OUVBK2JCVm9ON2M5TEFETEFPbzlYY0pqdllJcFRiWXlJTk1iOVpCRGZlb3d5ZkViZ2Q2bGxiK004MzB5SXFIWVEvaFl0dWpWaDhXazdseG1mMjJzL280eUp3aHNYQlV2OWVWQkFyTDVmbUxPM3NjRmdBejRsR2cvbEN2cThSS0JRaFFYd29CNTM2aDdkRWFnWGxqQk41WGhZcTVmNkZhK0xRRkFIRnlyM2VHT3RZNjJsU3NMUE9Sa2VGQXl6RTY3bk1BZ3lrb1hEUHRrUE9hd2x6bW4zdEVOT3FMMDVRWmEzTkFvZ1RtYVZLYmlUTk1RVVF5M2JQNElYdDE0RlNEa3pweU5pREptcE5yeDJ3cjlVNmJvT08xQTl5eHFMZ1ZHdVNXc1E5Y1ByTm4zV2VZVXJvTjhhZkk4b2w2bFNuODgxaEUrU2I2OU9lZ2UxY09RYUduTUJ4WlpiVThzVDJxd0JsVWFzc0dYWlBOMUU5b3M3UUh5bzJKcmxkU0xrWGpsWWpxV3I2SkJtYmxiRXpRWXg4clA0TDVDUG5RME96WjA0MDV6MklUZXBzSUpHMW90Wng4VmIyWGwvSDNqajNja2F0TUZLU1ZrNTdBQkVIRUp3a3pmbXB4Y3Z0SzBjK081MGpPZUpTOGpJRUhnZUJJSkJTNzRRb0F2VGcvbW5NeTVHVTRGYllHSHZHMDBzYXdBbkNVbW90cm13R2dzcGhkM2orNFNuYm9HZ2Mvd0NzN2FTM0NJcHBBbC8wNTFCbWZhYk8rV2JpRDl4c3dzaElxSjZua3RMSk9mWmM0NjZVZmVpTkVJeG04cU5yQncxTlBrc1lXOWtjK2FtM3diTk1PMVp0N3hIYXU1M01odDVFRExIUjZ6a0wwcGFjY0ZuSndCNXFsS1hSN01Lb3huSmo5VTQyS1FOTFRQditUL2NlNG5oS2dIc2dua00wbU1YelF6Tkx0MDBDdkdGWDRuNnE4Q1JCRnY2SHFSVVdwWGFGdWlrc3Mwa1M3RDc0TThTQ2VnTmRuWit4Z3BLQ0IzOWZWWDEvYmMwUjlDbWN4M050d0hFYXNkZllpUlRIcFI0bGJndG1RUUcrYWNTdVhJWVRaSWFTRzlLWlZVZ3oxeFI4TVhMQXAveDRHbGMzaGNCMURnZ0cybE82RnNBTUhBTDExU3NySk1RUmZsUmxJOU0zNFB0SHRTY2pqSGcwcHRMT3JCOUI4c0NqMEk5bG1aWHU4cW1pMEQzMmg2VC96MnRvOFJMcmExVldZblp5NW9nYmhiQ2E4Sk5JZmNGQmJDTytpUXZlN2xGQ0RXZGJncDZJallGcXR4amdGUmkzVURaMExtQjVqSk9lejA1VWRNT3c3SjFudkhvSDM3RGZFclE5VWZKeUlpRjdGNzR4c2ZMSFIxSXpGenB6YnIyM2QwU3c5OXlCNjlDa1ZtdERCaXo3aWFmaVpXYzBZU2svajkrWDR4NENwU1diOVRMRTY5djI1MStjS0xzZzRPT2ppUHVSYTRnL25oaFB6eGN5bGE4WjNYb2s1ZTdJRy9BbWtCNkEvL29pRFdEKzBzbXJGOUI4VlNnVENiRHRNdFNyUStGUVBzZHBMdG52UndOY0pGcHJEaVVHN25FUTdMQnhoZHhraTZ3dXRuTnY1b2dsUkdheHV4Tk9XYXZaNGUxOTVEaXlVQkN4cjM5MlRjUkxKNExIOE1rTUpnNml2dmlNYllqeTBwSVpLVGJGSjRzOXZ6WlFwVjZVd1FlWEM0TGRWd1E2dlltb05Mb2JCaUlsSE9hUUUxM1hpTEliVTU2aEJVRnJqSHhiNzNRUm1SQ1poeWZpVUNSdUg4YWZjOEpyMFFkMXlJYTJNczd4bU9FczlCWmFwejdTR01OeURXUXlIY0Q5VzgvQ0d1OFlhUldRaWlBQTU4MDRlQWF6MmJnYUNuR0wrNHdZeXpYOXNvWnVGWFZ2eklDbVk3bVF5c1pVMllqUVZoRjdHZWFGTDNKL2ZGTTdqOVdiaThjZG1MNzJENGdLVDVXUmpKTGNVY01HNHNXZTJoNkJncWVybDNJeDFmMzRteGZOSUpFVnlLTTVzVW54a0kvTTFORDU5M2g0UWJGYWVUTjZkc2NLenRrK084OW82Rnhma1dvUFZYRlZJTmF0WFhoSVJHU0cvKzhSQXNNYi81QUZMb0Y5U2x6YytXaWkxQkQ5RGhwZFQvd01ya0lDUWdzVklMbUt4bnhobXZhS3pvcGEyaG9GdXBiL3A2Z2hMQVZpY2RROHZJa3Y4U0hBeGZJc0sxaFZXcHNRMUl0SGw5N3lnSHNtQ0lkR1NJcVNiaGwzdW9CNXhWWWVGV0FObXpYQ3g0OE5oTlc2SnNHZ1c5RXp1NjdlY2Z3VUZlUEZpYXpvbWFPUTFRc3ZjYVhHVGIxWjA0UzdQay9OZmlCWWFJSkoxbGlIMnBaeDhMZGZwZTVuRENtbTlYTVZHbUY2SVdWVjM0QW1uK1B5TlNCcWxzZDZyQlNjWVRISm43ZElUZm9Zd3JvZjZvNVBUR2lwUHNlYXNHbDJoWFpCMWRYc1U0aTJKVDNLenVTeUl1RVU2Y2Z5M242T3duNmNjV0NyUFNLc3Vwb2Z0QithenZpZzd0bUFvRHlFTHN4eUE1dzQrZ0RmQm1BeStwNG02NnVNWjFUWEJiSTZhZ2RzVEpteUtHMm1NNFZrUmo0Z1cvcW0yMDNISno0YlVJN2dlcXNHQktNbGdPR1ZHRDRGZGlPaWxPbkNxeGo5NmFiY0Q5SnVmeVJUYUFmSC9QbGJaaU5mOTVORWtxaXZmNC9CN2lqaEdIZlUwcXFNblJlYVdackFuK2M3RlByc2h6UWIrMC9OUSt1dWRMbGJ6czZHd1IyUG5sNmlNdjRsUFZ3d1UyTWprYzdMUzY5ZzZpSnBZZytsbDM1N0toSWhPUTRpMkNzVXhXWHlVZy9VZCtaQmhSTDFhWWxWQ1NaK1VRZlJodVNCREdkLzNrYjR0azNvUVMzaVgrS09EVFB5RGwxaTUweXhZdjJQZjRPdG1QR1RVYkdUeUJQd1RPUGN4TUR5dDBjWjAzdVdtY0MwblVrMllnWnlLeFFXTTBSaHJVK2pRUVZwL1BWY1NRdGYwREtTSDZzUEh2M1RlbmJMV25sdzNKaUlveEhmRjRJTGZhY255M0xPTGFxcXN1QU95REJmMnQxdlF2UTdkMmtZMGhwdUF4Y091c0I4dXpmdmQyTWtramZwVHFNWGN4TndNbUxWYXRobGVpQUUwRVBFaTFDanZuWVgyQUE5a2pwLy9oRlZaaklvK0ZYRzlQRE5ObVdDTVRPSXQwcmZoRzFxNTFDbG1sQW41Mm1vQ016d0lNOGlLVXk0MDNPdElBQkgrSzhTbHQ4aFpEaHRmRDBoR0xVOXg0TVBaUmhxMmdRd0tCL3Fpd3BnVnNWbDhrNmVxUnZpMjFjeWFJRklIRVQ3L0ljT25zWU1rYnh2azBQdGtIL0VUSHhsQjVqMUJ6TmJhQ0hJZ291bWJwVDdadlEyWjRESFhXNXJQdU55YVN6MEF2bTZ3dHljMDVxVTJVbXRWMXFOL1NOb203SkFkbms5ajg1TFUydUh1bVdqZHNLbVdFNXFLdWprcW16N3pEOE8yVGhuVzc3SWZQRngveG8yWXlaZUU2OXRFVlJCZ0dHUFA3R1NyaTVMaUl2TzZwTkZMak96QnRJTERnMUxFQUo1VUlqNjd4U0VzUjRIRW9CVEM1NHZKSUFoLzMxak0vUE96VFdkZ01YcXhlSlhGenE3ZHRqTWcrLzNSY0hIVm9LRGd3NjlrMmNnYXpwRzIwdVZuMTZkUUJYN0Jpdk12TVc3OStUb0xPcm0ydXhrN0VtUkNTVFUzMlFBbVZ6Z09mZHRKUDF2TWk0SU93aDVSYW51YWh2ekhIWDNHR3pRTkx4a1RiUXFFUjZmaHV0cEVVcEFOWmVMbnA2UzVaMkIwZGtVZ1BSeGc2NUpXY05OS1BSc0NrWkkxK1NTU1haeVMrOWFrUzhtd3c0NXRzdHRaTlZSR003RVh4YjUrU0FkMkwybFpLbnlNRll1M2lSZWcrSnZtMUIxVFZRL3lKejY0MFlLUzNMYitIQ3hoSmhTTGlhYk5Kb1Y3V1VuZTBGekt6bXVDdGRtR3BIWkM5cXh1SGdDcHRuVTJVb2oxNWF6Y2dBWHAyZjF6OHVUVCs2dXpxTm5lOVdzUHlwdE9NdlFhNDJzdmtZNlU5TkcvSi9VUTdRRmUrL2VUNnp6ZGQ2a2lSNzA1ZXBVeTA5MkpIekQrOWJDMmxtYWk4RGo0U3o0MVhUWmEvTHYzdXQwOTk1L3ptb3Z1R2M2VTdXMmFTSG9LcUs4dXRKeEliVmtKb3hYbFFuckVIMlM1YXc3WG1lREtwbzVwYXlLTU56eXhJeGxoTmxsRUVqbVdnODFBajRLbmFsOUR3N09sVVhrWE02eWFqWjNqaUN2Uk9RQVRVbGVid2gzWFppTnJtc1J4b3lWMVg5OWEzaWN1THlpRUJBVkRYQ0kwcTJqYWdCc1h4L3Nrci9oa2lhYkZqSCs3MUVvVWVjRm11RmhvcGxMak1td0tsSWFwTk02NENUaGRpdGdoUklUTFVDb2ZGYUQxOHd6bldlaEZrSFlVUW1JWXRFdzJYb005V2FMN1Fod0ZoeHVhL3FScFRLUTMxOXBNWk1qN2UrMlhaYk93Y0VLYnE2MFhRSllHaDNGMTUxSENYVW9lRHY1Nm42Yk52ckU2YVdkdkFEa3F1RWw3RTNYSmdueTlXOVJCSnMvMEo4QkxjNnlPOWk3V3ZIVVFwN3JRZkhGWklMbEIwSHgrcXhVWG1LWG5KZTczcGhSY0tTNjVIb3d4WjYreHppclFHTXhtci85R05VUEd5TVROR1ArRGlKbW1La3FMT09jd0NFSFNuZnl1NmExN24rM1l3U1g5NitNbkVmdTIwckhPb1pqVVZuT1ptRkNFRFFYZ0s3NWR5b3BzclZlM0pLZHBmYVFOVDllT0dvNk1qZDNiS3UrTVZDR3g4TitrNWE3ajJ3cGxtTVhuTVB6Q1JmcEkzd1pQeTRQc1VhVTEyS2xvaUZFT3poZTJGMk9EYUwxU3lGY2RueWhyRTgvcnZSc3pVV2R3UjZEai9LaVh0OC9vSm5aY2R5bDdjKzZSNi9HenRlSHZqY0oya1BnUk9nTURpUDFlTmVkNzJ0UmtzQjllNXZsczNZRnNrNWlBa1hiNEw5MjRibGx6c1VXQXBCeFRkMXFLS1hYZzhQVFhqanNCYTQ5dnNBNUVvaFZmbUUwMnR2NHNjQkZMaU5VcXdZY0dxZ25KbEFQQ3FaOGdnT1E3VnhWZFZuMVlRZkJPMm1vVGdaU0wrQ0NUeE9qRXlwSkR2RnM5Z01JZ0l3V0hBSFhNOGdQanZucXBsN1d3bXh0a2l3aUdEU1V6bjFscHViTEV2czdxYWxNZ2hieXVuVzRlcU5WdlBmUmlNMlllSGhheUF6dFF5MXFXaTJ6cGg1VWExWjFKQVREaW9FanBFMHNTN0FlZEdPNmovVzFmZDV6R1pibHlIN1M5VDY2ZEU0OHRjaFo0R0YxVmsvZlFYSGRRT0lkb1pabUE1T05yS0FuQWVWOEh2QUFxa29Nd3J4ZGFXN1lYMWNHSitXTUdOR08xU0VLVWczMldmUG1pY1hLTG9JWDBEUW1OYmd3TjNFenVtMm1uNjNNUTN2amVqckVaRnpsUmhKbStDYVMwTGJxVDVIRkRWWVBOb2xOZjl3aEFxMzAvSFZaaWt6UHhWM3lJQmlrdmE1QTFuSmZWcFhZRXFNa2IvaDR0V05iNXN3U2pucUVEOHViOU5Pc3RIcmVZaE9RYnBLelVHU2RjTkF1K1IrVDJzUlJBQzQ2TlZadmZOcVZ5UU90YVh0czhRdzRXdEJCaXpKS0xtLzBYd1oxMXdNZmtqci9nckg0cmllRGEwZTd1S2tQV3pZOXJqc2lpQ2MwL1ozUjBIM1hLNTVTOTB1bjVIVitRbkt1eHlyYk03UGhFUXVnRVE2TlQ3cVhvV1U1b3BSL1p2NmloSEhYMzB1ZStFYW53SmdlUUN4WGpsTlZEUGY2WEpFNXQ4eWxLMFd5andOQWZpdzdiZ2F3MVk5YVU1MU5ZZUc3QXo5RldXSWhHVGpmSXZMMThVY0pQWndib1hqNHZHMkdyc01YRDNMUjFKV2xJZGIwL29uTndvOENZYncrOFlhc3RtOUw0dzlDQUQzTHZRVzBBQ3dkS3hMQ1NycW5UWmw0YStTbHUvNXFTN2RIdzlBSGpMVnFFRGtLWU9qNnFEVHdpTWFzNkR4bEt0RmxXa2wyaWZFaFl0UGVERmg5d2ZOdHFhUjdBRFZtcWQyL3p0aFpzcmlaTVpvdmluMlJSWGptSTZsRGRkMFhScFhNV2hWQkUrN1JKZ0VRZTNzV28yS3d2TUtZMS9PcWVXYkxndUZZSVZTZ0w4NkFTU3g8L3hlbmM6Q2lwaGVyVmFsdWU%2BPC94ZW5jOkNpcGhlckRhdGE%2BPC94ZW5jOkVuY3J5cHRlZERhdGE%2BPC9zYW1sMjpFbmNyeXB0ZWRBc3NlcnRpb24%2BPC9zYW1sMnA6UmVzcG9uc2U%2B
\ No newline at end of file
"""
Third_party_auth integration tests using a mock version of the TestShib provider
"""
from django.core.management import call_command
from django.core.urlresolvers import reverse
import httpretty
from mock import patch
import StringIO
from student.tests.factories import UserFactory
from third_party_auth.tests import testutil
import unittest
TESTSHIB_ENTITY_ID = 'https://idp.testshib.org/idp/shibboleth'
TESTSHIB_METADATA_URL = 'https://mock.testshib.org/metadata/testshib-providers.xml'
TESTSHIB_SSO_URL = 'https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO'
TPA_TESTSHIB_LOGIN_URL = '/auth/login/tpa-saml/?auth_entry=login&next=%2Fdashboard&idp=testshib'
TPA_TESTSHIB_REGISTER_URL = '/auth/login/tpa-saml/?auth_entry=register&next=%2Fdashboard&idp=testshib'
TPA_TESTSHIB_COMPLETE_URL = '/auth/complete/tpa-saml/'
@unittest.skipUnless(testutil.AUTH_FEATURE_ENABLED, 'third_party_auth not enabled')
class TestShibIntegrationTest(testutil.SAMLTestCase):
"""
TestShib provider Integration Test, to test SAML functionality
"""
def setUp(self):
super(TestShibIntegrationTest, self).setUp()
self.login_page_url = reverse('signin_user')
self.register_page_url = reverse('register_user')
self.enable_saml(
private_key=self._get_private_key(),
public_key=self._get_public_key(),
entity_id="https://saml.example.none",
)
# Mock out HTTP requests that may be made to TestShib:
httpretty.enable()
def metadata_callback(_request, _uri, headers):
""" Return a cached copy of TestShib's metadata by reading it from disk """
return (200, headers, self._read_data_file('testshib_metadata.xml'))
httpretty.register_uri(httpretty.GET, TESTSHIB_METADATA_URL, content_type='text/xml', body=metadata_callback)
self.addCleanup(httpretty.disable)
self.addCleanup(httpretty.reset)
# Configure the SAML library to use the same request ID for every request.
# Doing this and freezing the time allows us to play back recorded request/response pairs
uid_patch = patch('onelogin.saml2.utils.OneLogin_Saml2_Utils.generate_unique_id', return_value='TESTID')
uid_patch.start()
self.addCleanup(uid_patch.stop)
def test_login_before_metadata_fetched(self):
self._configure_testshib_provider(fetch_metadata=False)
# The user goes to the login page, and sees a button to login with TestShib:
self._check_login_page()
# The user clicks on the TestShib button:
try_login_response = self.client.get(TPA_TESTSHIB_LOGIN_URL)
# The user should be redirected to back to the login page:
self.assertEqual(try_login_response.status_code, 302)
self.assertEqual(try_login_response['Location'], self.url_prefix + self.login_page_url)
# When loading the login page, the user will see an error message:
response = self.client.get(self.login_page_url)
self.assertEqual(response.status_code, 200)
self.assertIn('Authentication with TestShib is currently unavailable.', response.content)
# Note: the following patch is only needed until https://github.com/edx/edx-platform/pull/8262 is merged
@patch.dict("django.conf.settings.FEATURES", {"AUTOMATIC_AUTH_FOR_TESTING": True})
def test_register(self):
self._configure_testshib_provider()
self._freeze_time(timestamp=1434326820) # This is the time when the saved request/response was recorded.
# The user goes to the register page, and sees a button to register with TestShib:
self._check_register_page()
# The user clicks on the TestShib button:
try_login_response = self.client.get(TPA_TESTSHIB_REGISTER_URL)
# The user should be redirected to TestShib:
self.assertEqual(try_login_response.status_code, 302)
self.assertTrue(try_login_response['Location'].startswith(TESTSHIB_SSO_URL))
# Now the user will authenticate with the SAML provider
testshib_response = self._fake_testshib_login_and_return()
# We should be redirected to the register screen since this account is not linked to an edX account:
self.assertEqual(testshib_response.status_code, 302)
self.assertEqual(testshib_response['Location'], self.url_prefix + self.register_page_url)
register_response = self.client.get(self.register_page_url)
# We'd now like to see if the "You've successfully signed into TestShib" message is
# shown, but it's managed by a JavaScript runtime template, and we can't run JS in this
# type of test, so we just check for the variable that triggers that message.
self.assertIn('&#34;currentProvider&#34;: &#34;TestShib&#34;', register_response.content)
self.assertIn('&#34;errorMessage&#34;: null', register_response.content)
# Now do a crude check that the data (e.g. email) from the provider is displayed in the form:
self.assertIn('&#34;defaultValue&#34;: &#34;myself@testshib.org&#34;', register_response.content)
self.assertIn('&#34;defaultValue&#34;: &#34;Me Myself And I&#34;', register_response.content)
# Now complete the form:
ajax_register_response = self.client.post(
reverse('user_api_registration'),
{
'email': 'myself@testshib.org',
'name': 'Myself',
'username': 'myself',
'honor_code': True,
}
)
self.assertEqual(ajax_register_response.status_code, 200)
# Then the AJAX will finish the third party auth:
continue_response = self.client.get(TPA_TESTSHIB_COMPLETE_URL)
# And we should be redirected to the dashboard:
self.assertEqual(continue_response.status_code, 302)
self.assertEqual(continue_response['Location'], self.url_prefix + reverse('dashboard'))
# Now check that we can login again:
self.client.logout()
self._test_return_login()
def test_login(self):
self._configure_testshib_provider()
self._freeze_time(timestamp=1434326820) # This is the time when the saved request/response was recorded.
user = UserFactory.create()
# The user goes to the login page, and sees a button to login with TestShib:
self._check_login_page()
# The user clicks on the TestShib button:
try_login_response = self.client.get(TPA_TESTSHIB_LOGIN_URL)
# The user should be redirected to TestShib:
self.assertEqual(try_login_response.status_code, 302)
self.assertTrue(try_login_response['Location'].startswith(TESTSHIB_SSO_URL))
# Now the user will authenticate with the SAML provider
testshib_response = self._fake_testshib_login_and_return()
# We should be redirected to the login screen since this account is not linked to an edX account:
self.assertEqual(testshib_response.status_code, 302)
self.assertEqual(testshib_response['Location'], self.url_prefix + self.login_page_url)
login_response = self.client.get(self.login_page_url)
# We'd now like to see if the "You've successfully signed into TestShib" message is
# shown, but it's managed by a JavaScript runtime template, and we can't run JS in this
# type of test, so we just check for the variable that triggers that message.
self.assertIn('&#34;currentProvider&#34;: &#34;TestShib&#34;', login_response.content)
self.assertIn('&#34;errorMessage&#34;: null', login_response.content)
# Now the user enters their username and password.
# The AJAX on the page will log them in:
ajax_login_response = self.client.post(
reverse('user_api_login_session'),
{'email': user.email, 'password': 'test'}
)
self.assertEqual(ajax_login_response.status_code, 200)
# Then the AJAX will finish the third party auth:
continue_response = self.client.get(TPA_TESTSHIB_COMPLETE_URL)
# And we should be redirected to the dashboard:
self.assertEqual(continue_response.status_code, 302)
self.assertEqual(continue_response['Location'], self.url_prefix + reverse('dashboard'))
# Now check that we can login again:
self.client.logout()
self._test_return_login()
def _test_return_login(self):
""" Test logging in to an account that is already linked. """
# Make sure we're not logged in:
dashboard_response = self.client.get(reverse('dashboard'))
self.assertEqual(dashboard_response.status_code, 302)
# The user goes to the login page, and sees a button to login with TestShib:
self._check_login_page()
# The user clicks on the TestShib button:
try_login_response = self.client.get(TPA_TESTSHIB_LOGIN_URL)
# The user should be redirected to TestShib:
self.assertEqual(try_login_response.status_code, 302)
self.assertTrue(try_login_response['Location'].startswith(TESTSHIB_SSO_URL))
# Now the user will authenticate with the SAML provider
login_response = self._fake_testshib_login_and_return()
# There will be one weird redirect required to set the login cookie:
self.assertEqual(login_response.status_code, 302)
self.assertEqual(login_response['Location'], self.url_prefix + TPA_TESTSHIB_COMPLETE_URL)
# And then we should be redirected to the dashboard:
login_response = self.client.get(TPA_TESTSHIB_COMPLETE_URL)
self.assertEqual(login_response.status_code, 302)
self.assertEqual(login_response['Location'], self.url_prefix + reverse('dashboard'))
# Now we are logged in:
dashboard_response = self.client.get(reverse('dashboard'))
self.assertEqual(dashboard_response.status_code, 200)
def _freeze_time(self, timestamp):
""" Mock the current time for SAML, so we can replay canned requests/responses """
now_patch = patch('onelogin.saml2.utils.OneLogin_Saml2_Utils.now', return_value=timestamp)
now_patch.start()
self.addCleanup(now_patch.stop)
def _check_login_page(self):
""" Load the login form and check that it contains a TestShib button """
response = self.client.get(self.login_page_url)
self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content)
self.assertIn(TPA_TESTSHIB_LOGIN_URL.replace('&', '&amp;'), response.content)
return response
def _check_register_page(self):
""" Load the login form and check that it contains a TestShib button """
response = self.client.get(self.register_page_url)
self.assertEqual(response.status_code, 200)
self.assertIn("TestShib", response.content)
self.assertIn(TPA_TESTSHIB_REGISTER_URL.replace('&', '&amp;'), response.content)
return response
def _configure_testshib_provider(self, **kwargs):
""" Enable and configure the TestShib SAML IdP as a third_party_auth provider """
fetch_metadata = kwargs.pop('fetch_metadata', True)
kwargs.setdefault('name', 'TestShib')
kwargs.setdefault('enabled', True)
kwargs.setdefault('idp_slug', 'testshib')
kwargs.setdefault('entity_id', TESTSHIB_ENTITY_ID)
kwargs.setdefault('metadata_source', TESTSHIB_METADATA_URL)
kwargs.setdefault('icon_class', 'fa-university')
kwargs.setdefault('attr_email', 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6') # eduPersonPrincipalName
self.configure_saml_provider(**kwargs)
if fetch_metadata:
stdout = StringIO.StringIO()
stderr = StringIO.StringIO()
self.assertTrue(httpretty.is_enabled())
call_command('saml', 'pull', stdout=stdout, stderr=stderr)
stdout = stdout.getvalue().decode('utf-8')
stderr = stderr.getvalue().decode('utf-8')
self.assertEqual(stderr, '')
self.assertIn(u'Fetching {}'.format(TESTSHIB_METADATA_URL), stdout)
self.assertIn(u'Created new record for SAMLProviderData', stdout)
def _fake_testshib_login_and_return(self):
""" Mocked: the user logs in to TestShib and then gets redirected back """
# The SAML provider (TestShib) will authenticate the user, then get the browser to POST a response:
return self.client.post(
TPA_TESTSHIB_COMPLETE_URL,
content_type='application/x-www-form-urlencoded',
data=self._read_data_file('testshib_response.txt'),
)
"""
Test the views served by third_party_auth.
"""
# pylint: disable=no-member
import ddt
from lxml import etree
import unittest
from .testutil import AUTH_FEATURE_ENABLED, SAMLTestCase
# Define some XML namespaces:
SAML_XML_NS = 'urn:oasis:names:tc:SAML:2.0:metadata'
XMLDSIG_XML_NS = 'http://www.w3.org/2000/09/xmldsig#'
@unittest.skipUnless(AUTH_FEATURE_ENABLED, 'third_party_auth not enabled')
@ddt.ddt
class SAMLMetadataTest(SAMLTestCase):
"""
Test the SAML metadata view
"""
METADATA_URL = '/auth/saml/metadata.xml'
def test_saml_disabled(self):
""" When SAML is not enabled, the metadata view should return 404 """
self.enable_saml(enabled=False)
response = self.client.get(self.METADATA_URL)
self.assertEqual(response.status_code, 404)
@ddt.data('saml_key', 'saml_key_alt') # Test two slightly different key pair export formats
def test_metadata(self, key_name):
self.enable_saml(
private_key=self._get_private_key(key_name),
public_key=self._get_public_key(key_name),
entity_id="https://saml.example.none",
)
doc = self._fetch_metadata()
# Check the ACS URL:
acs_node = doc.find(".//{}".format(etree.QName(SAML_XML_NS, 'AssertionConsumerService')))
self.assertIsNotNone(acs_node)
self.assertEqual(acs_node.attrib['Location'], 'http://example.none/auth/complete/tpa-saml/')
def test_signed_metadata(self):
self.enable_saml(
private_key=self._get_private_key(),
public_key=self._get_public_key(),
entity_id="https://saml.example.none",
other_config_str='{"SECURITY_CONFIG": {"signMetadata": true} }',
)
doc = self._fetch_metadata()
sig_node = doc.find(".//{}".format(etree.QName(XMLDSIG_XML_NS, 'SignatureValue')))
self.assertIsNotNone(sig_node)
def _fetch_metadata(self):
""" Fetch and parse the metadata XML at self.METADATA_URL """
response = self.client.get(self.METADATA_URL)
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'], 'text/xml')
# The result should be valid XML:
try:
metadata_doc = etree.fromstring(response.content)
except etree.LxmlError:
self.fail('SAML metadata must be valid XML')
self.assertEqual(metadata_doc.tag, etree.QName(SAML_XML_NS, 'EntityDescriptor'))
return metadata_doc
......@@ -8,6 +8,7 @@ from contextlib import contextmanager
from django.conf import settings
import django.test
import mock
import os.path
from third_party_auth.models import OAuth2ProviderConfig, SAMLProviderConfig, SAMLConfiguration, cache as config_cache
......@@ -87,6 +88,33 @@ class TestCase(ThirdPartyAuthTestMixin, django.test.TestCase):
pass
class SAMLTestCase(TestCase):
"""
Base class for SAML-related third_party_auth tests
"""
def setUp(self):
super(SAMLTestCase, self).setUp()
self.client.defaults['SERVER_NAME'] = 'example.none' # The SAML lib we use doesn't like testserver' as a domain
self.url_prefix = 'http://example.none'
@classmethod
def _get_public_key(cls, key_name='saml_key'):
""" Get a public key for use in the test. """
return cls._read_data_file('{}.pub'.format(key_name))
@classmethod
def _get_private_key(cls, key_name='saml_key'):
""" Get a private key for use in the test. """
return cls._read_data_file('{}.key'.format(key_name))
@staticmethod
def _read_data_file(filename):
""" Read the contents of a file in the data folder """
with open(os.path.join(os.path.dirname(__file__), 'data', filename)) as f:
return f.read()
@contextmanager
def simulate_running_pipeline(pipeline_target, backend, email=None, fullname=None, username=None):
"""Simulate that a pipeline is currently running.
......
......@@ -198,7 +198,7 @@ def _third_party_auth_context(request, redirect_to):
for msg in messages.get_messages(request):
if msg.extra_tags.split()[0] == "social-auth":
# msg may or may not be translated. Try translating [again] in case we are able to:
context['errorMessage'] = _(msg) # pylint: disable=translation-of-non-string
context['errorMessage'] = _(unicode(msg)) # pylint: disable=translation-of-non-string
break
return context
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment