diff --git a/djangosaml2/tests/__init__.py b/djangosaml2/tests/__init__.py
index 672ffa74..5a987a77 100644
--- a/djangosaml2/tests/__init__.py
+++ b/djangosaml2/tests/__init__.py
@@ -37,6 +37,7 @@
from djangosaml2.conf import get_config
from djangosaml2.signals import post_authenticated
from djangosaml2.tests import conf
+from djangosaml2.tests.utils import SAMLPostFormParser
from djangosaml2.tests.auth_response import auth_response
from djangosaml2.views import finish_logout
from saml2.config import SPConfig
@@ -108,6 +109,35 @@ def render_template(self, text):
def b64_for_post(self, xml_text, encoding='utf-8'):
return base64.b64encode(xml_text.encode(encoding)).decode('ascii')
+ def test_unsigned_post_authn_request(self):
+ """
+ Test that unsigned authentication requests via POST binding
+ does not error.
+
+ https://github.com/knaperek/djangosaml2/issues/168
+ """
+ settings.SAML_CONFIG = conf.create_conf(
+ sp_host='sp.example.com',
+ idp_hosts=['idp.example.com'],
+ metadata_file='remote_metadata_post_binding.xml',
+ authn_requests_signed=False
+ )
+ response = self.client.get(reverse('saml2_login'))
+
+ self.assertEqual(response.status_code, 200)
+
+ # Using POST-binding returns a page with form containing the SAMLRequest
+ response_parser = SAMLPostFormParser()
+ response_parser.feed(response.content.decode('utf-8'))
+ saml_request = response_parser.saml_request_value
+ expected_request = """http://sp.example.com/saml2/metadata/"""
+
+ self.assertIsNotNone(saml_request)
+ self.assertSAMLRequestsEquals(
+ base64.b64decode(saml_request).decode('utf-8'),
+ expected_request
+ )
+
def test_login_evil_redirect(self):
"""
Make sure that if we give an URL other than our own host as the next
diff --git a/djangosaml2/tests/conf.py b/djangosaml2/tests/conf.py
index 2c1bd92e..a2f59cc7 100644
--- a/djangosaml2/tests/conf.py
+++ b/djangosaml2/tests/conf.py
@@ -19,7 +19,7 @@
def create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com'],
- metadata_file='remote_metadata.xml'):
+ metadata_file='remote_metadata.xml', authn_requests_signed=None):
try:
from saml2.sigver import get_xmlsec_binary
@@ -90,6 +90,9 @@ def create_conf(sp_host='sp.example.com', idp_hosts=['idp.example.com'],
'valid_for': 24,
}
+ if authn_requests_signed is not None:
+ config['service']['sp']['authn_requests_signed'] = authn_requests_signed
+
for idp in idp_hosts:
entity_id = 'https://%s/simplesaml/saml2/idp/metadata.php' % idp
config['service']['sp']['idp'][entity_id] = {
diff --git a/djangosaml2/tests/remote_metadata_post_binding.xml b/djangosaml2/tests/remote_metadata_post_binding.xml
new file mode 100644
index 00000000..941f9580
--- /dev/null
+++ b/djangosaml2/tests/remote_metadata_post_binding.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+ MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYDVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xiZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2ZlaWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LONoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHISKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2QarQ4/67OZfHd7R+POBXhophSMv1ZOo
+
+
+
+
+
+
+ MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYDVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xiZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2ZlaWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LONoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHISKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2QarQ4/67OZfHd7R+POBXhophSMv1ZOo
+
+
+
+
+ urn:oasis:names:tc:SAML:2.0:nameid-format:transient
+
+
+
+ Lorenzo's test IdP
+ idp.example.com IdP
+ http://idp.example.com/
+
+
+ Administrator
+ lgs@yaco.es
+
+
+
diff --git a/djangosaml2/tests/utils.py b/djangosaml2/tests/utils.py
new file mode 100644
index 00000000..58331ca4
--- /dev/null
+++ b/djangosaml2/tests/utils.py
@@ -0,0 +1,16 @@
+from html.parser import HTMLParser
+
+
+class SAMLPostFormParser(HTMLParser):
+ """
+ Parses the SAML Post binding form page for the SAMLRequest value.
+ """
+
+ saml_request_value = None
+
+ def handle_starttag(self, tag, attrs):
+ attrs_dict = dict(attrs)
+
+ if tag != "input" or attrs_dict.get("name") != "SAMLRequest":
+ return
+ self.saml_request_value = attrs_dict.get("value")
diff --git a/djangosaml2/views.py b/djangosaml2/views.py
index bbb0b5d0..6ef2fab8 100644
--- a/djangosaml2/views.py
+++ b/djangosaml2/views.py
@@ -44,6 +44,7 @@
)
from saml2.mdstore import SourceNotFound
from saml2.sigver import MissingKey
+from saml2.samlp import AuthnRequest
from saml2.validate import ResponseLifetimeExceed, ToEarly
from saml2.xmldsig import ( # support for SHA1 is required by spec
SIG_RSA_SHA1, SIG_RSA_SHA256)
@@ -228,6 +229,9 @@ def login(request,
binding=binding,
**kwargs)
try:
+ if isinstance(request_xml, AuthnRequest):
+ # request_xml will be an instance of AuthnRequest if the message is not signed
+ request_xml = str(request_xml)
saml_request = base64.b64encode(bytes(request_xml, 'UTF-8')).decode('utf-8')
http_response = render(request, post_binding_form_template, {