diff --git a/README.rst b/README.rst
index a7ccdadb..71b02c74 100644
--- a/README.rst
+++ b/README.rst
@@ -85,13 +85,13 @@ in the form of URL string which we use to redirect the user to our identity
provider--OneLogin::
from BaseHTTPServer import BaseHTTPRequestHandler
- from onelogin.saml import AuthnRequest
+ from onelogin.saml import AuthRequest
...
class SampleAppHTTPRequestHandler(BaseHTTPRequestHandler):
...
def do_GET(self):
...
- url = AuthnRequest.create(**self.settings)
+ url = AuthRequest.create(**self.settings)
self.send_response(301)
self.send_header("Location", url)
self.end_headers()
@@ -135,7 +135,7 @@ of the public certificate originally obtained from OneLogin::
valid = res.is_valid()
name_id = res.name_id
if valid:
- msg = 'The identity of {name_id} has been verified'.format(
+ msg = 'The identify of {name_id} has been verified'.format(
name_id=name_id,
)
self._serve_msg(200, msg)
diff --git a/example.py b/example.py
index bfcc58c8..b8a969d6 100644
--- a/example.py
+++ b/example.py
@@ -9,7 +9,7 @@
from BaseHTTPServer import BaseHTTPRequestHandler
from BaseHTTPServer import HTTPServer
-from onelogin.saml import AuthnRequest, Response
+from onelogin.saml import AuthRequest, Response
__version__ = '0.1'
@@ -57,7 +57,7 @@ def do_GET(self):
self._bad_request()
return
- url = AuthnRequest.create(**self.settings)
+ url = AuthRequest.create(**self.settings)
self.send_response(301)
self.send_header("Location", url)
self.end_headers()
@@ -78,7 +78,7 @@ def do_POST(self):
valid = res.is_valid()
name_id = res.name_id
if valid:
- msg = 'The identity of {name_id} has been verified'.format(
+ msg = 'The identify of {name_id} has been verified'.format(
name_id=name_id,
)
self._serve_msg(200, msg)
diff --git a/onelogin/saml/AuthRequest.py b/onelogin/saml/AuthRequest.py
index ae5387c0..c2f3f6df 100644
--- a/onelogin/saml/AuthRequest.py
+++ b/onelogin/saml/AuthRequest.py
@@ -2,6 +2,7 @@
import base64
import uuid
import urllib
+import urlparse
from datetime import datetime
from lxml import etree
@@ -13,6 +14,7 @@ def create(
_zlib=None,
_base64=None,
_urllib=None,
+ _as_url=True,
**kwargs
):
"""Create a URL string which can be used to redirect a samlp:AuthnRequest to the identity provider.
@@ -35,9 +37,15 @@ def create(
if _urllib is None:
_urllib = urllib
- assertion_consumer_service_url = kwargs.pop(
- 'assertion_consumer_service_url',
- )
+ assertion_consumer_service_url = None
+ assertion_consumer_service_index = "1"
+ if 'assertion_consumer_service_url' in kwargs:
+ assertion_consumer_service_url = kwargs.pop(
+ 'assertion_consumer_service_url',
+ )
+ elif 'assertion_consumer_service_index' in kwargs:
+ assertion_consumer_service_index = "%s" % kwargs.pop('assertion_consumer_service_index')
+
issuer = kwargs.pop('issuer')
name_identifier_format = kwargs.pop('name_identifier_format')
idp_sso_target_url = kwargs.pop('idp_sso_target_url')
@@ -47,10 +55,11 @@ def create(
# http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf Section
# 1.3.3
now = now.replace(microsecond=0)
- now_iso = now.isoformat()
+ # Fix for ADFS parsing bug. Add 'Z'
+ now_iso = now.isoformat() + 'Z'
unique_id = _uuid()
- unique_id = unique_id.hex
+ unique_id = '_' + unique_id.hex[:-1]
samlp_maker = ElementMaker(
namespace='urn:oasis:names:tc:SAML:2.0:protocol',
@@ -61,14 +70,23 @@ def create(
nsmap=dict(saml='urn:oasis:names:tc:SAML:2.0:assertion'),
)
- authn_request = samlp_maker.AuthnRequest(
- ProtocolBinding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
- Version='2.0',
- IssueInstant=now_iso,
- ID=unique_id,
- AssertionConsumerServiceURL=assertion_consumer_service_url,
- )
-
+ if assertion_consumer_service_url:
+ authn_request = samlp_maker.AuthnRequest(
+ ProtocolBinding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
+ Version='2.0',
+ IssueInstant=now_iso,
+ ID=unique_id,
+ AssertionConsumerServiceURL=assertion_consumer_service_url,
+ )
+ elif assertion_consumer_service_index:
+ authn_request = samlp_maker.AuthnRequest(
+ ProtocolBinding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
+ Version='2.0',
+ IssueInstant=now_iso,
+ ID=unique_id,
+ AssertionConsumerServiceIndex=assertion_consumer_service_index,
+ )
+
saml_issuer = saml_maker.Issuer()
saml_issuer.text = issuer
authn_request.append(saml_issuer)
@@ -77,7 +95,7 @@ def create(
Format=name_identifier_format,
AllowCreate='true',
)
- authn_request.append(name_id_policy)
+ #authn_request.append(name_id_policy)
request_authn_context = samlp_maker.RequestedAuthnContext(
Comparison='exact',
@@ -90,15 +108,20 @@ def create(
)
request_authn_context.append(authn_context_class_ref)
- compressed_request = _zlib.compress(etree.tostring(authn_request))
- # Strip the first 2 bytes (header) and the last 4 bytes (checksum) to get the raw deflate
- deflated_request = compressed_request[2:-4]
- encoded_request = _base64.b64encode(deflated_request)
- urlencoded_request = _urllib.urlencode(
- [('SAMLRequest', encoded_request)],
- )
+ if _as_url:
+ compressed_request = _zlib.compress(etree.tostring(authn_request))
+ pu = urlparse.urlparse(idp_sso_target_url)
+ qsl = urlparse.parse_qsl(pu.query)
+ # Strip the first 2 bytes (header) and the last 4 bytes (checksum) to get the raw deflate
+ deflated_request = compressed_request[2:-4]
+ encoded_request = _base64.b64encode(deflated_request)
+ qsl.append(('SAMLRequest', encoded_request))
+ qs = _urllib.urlencode(qsl)
+ pul = list(pu)
+ pul[4] = qs
+ return urlparse.urlunparse(pul)
+ else:
+ encoded_request = _base64.b64encode(etree.tostring(authn_request))
+ return encoded_request
+
- return '{url}?{query}'.format(
- url=idp_sso_target_url,
- query=urlencoded_request,
- )
diff --git a/onelogin/saml/Response.py b/onelogin/saml/Response.py
index 20fb98c4..0113ff38 100644
--- a/onelogin/saml/Response.py
+++ b/onelogin/saml/Response.py
@@ -58,7 +58,10 @@ def __init__(
self._signature = signature
def _parse_datetime(self, dt):
- return datetime.strptime(dt, '%Y-%m-%dT%H:%M:%SZ')
+ try:
+ return datetime.strptime(dt, '%Y-%m-%dT%H:%M:%SZ')
+ except ValueError:
+ return datetime.strptime(dt, '%Y-%m-%dT%H:%M:%S.%fZ')
def _get_name_id(self):
result = self._document.xpath(
diff --git a/onelogin/saml/SignatureVerifier.py b/onelogin/saml/SignatureVerifier.py
index 30731f6b..9dd9e8fd 100644
--- a/onelogin/saml/SignatureVerifier.py
+++ b/onelogin/saml/SignatureVerifier.py
@@ -120,6 +120,8 @@ def verify(
cert_filename,
'--id-attr:ID',
'urn:oasis:names:tc:SAML:2.0:assertion:Assertion',
+ '--id-attr:ID',
+ 'urn:oasis:names:tc:SAML:2.0:protocol:Response',
xml_filename,
]
diff --git a/onelogin/saml/test/TestAuthRequest.py b/onelogin/saml/test/TestAuthRequest.py
index a3ede75d..72e9c501 100644
--- a/onelogin/saml/test/TestAuthRequest.py
+++ b/onelogin/saml/test/TestAuthRequest.py
@@ -3,9 +3,9 @@
from datetime import datetime
from nose.tools import eq_ as eq
-from onelogin.saml import AuthnRequest
+from onelogin.saml import AuthRequest
-class TestAuthnRequest(object):
+class TestAuthRequest(object):
def setUp(self):
fudge.clear_expectations()
@@ -24,7 +24,7 @@ def fake_clock():
fake_zlib.remember_order()
fake_compress = fake_zlib.expects('compress')
fake_compress.with_args(
-"""foo_issuerurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"""
+"""foo_issuerurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"""
)
fake_compress.returns('HDfoo_compressedCHCK')
@@ -42,7 +42,7 @@ def fake_clock():
)
fake_urlencode.returns('foo_urlencoded')
- req = AuthnRequest.create(
+ req = AuthRequest.create(
_clock=fake_clock,
_uuid=fake_uuid_func,
_zlib=fake_zlib,