Skip to content

Commit 8a98992

Browse files
committed
Add support for Subjects on AuthNRequests by the new name_id_value_req parameeter.Fix testshib test. Improve README: Added inline markup to important references
1 parent 2e436f2 commit 8a98992

File tree

6 files changed

+262
-178
lines changed

6 files changed

+262
-178
lines changed

README.md

Lines changed: 150 additions & 149 deletions
Large diffs are not rendered by default.

src/onelogin/saml2/auth.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def get_last_assertion_id(self):
323323
"""
324324
return self.__last_assertion_id
325325

326-
def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True):
326+
def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None):
327327
"""
328328
Initiates the SSO process.
329329
@@ -339,10 +339,13 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
339339
:param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element.
340340
:type set_nameid_policy: bool
341341
342+
:param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated
343+
:type name_id_value_req: string
344+
342345
:returns: Redirection URL
343346
:rtype: string
344347
"""
345-
authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy)
348+
authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy, name_id_value_req)
346349
self.__last_request = authn_request.get_xml()
347350
self.__last_request_id = authn_request.get_id()
348351
saml_request = authn_request.get_request()

src/onelogin/saml2/authn_request.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class OneLogin_Saml2_Authn_Request(object):
2222
2323
"""
2424

25-
def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True):
25+
def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None):
2626
"""
2727
Constructs the AuthnRequest object.
2828
@@ -37,6 +37,9 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
3737
3838
:param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element.
3939
:type set_nameid_policy: bool
40+
41+
:param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated
42+
:type name_id_value_req: string
4043
"""
4144
self.__settings = settings
4245

@@ -69,6 +72,14 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
6972
if is_passive is True:
7073
is_passive_str = "\n" + ' IsPassive="true"'
7174

75+
subject_str = ''
76+
if name_id_value_req:
77+
subject_str = """
78+
<saml:Subject>
79+
<saml:NameID Format="%s">%s</saml:NameID>
80+
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"></saml:SubjectConfirmation>
81+
</saml:Subject>""" % (sp_data['NameIDFormat'], name_id_value_req)
82+
7283
nameid_policy_str = ''
7384
if set_nameid_policy:
7485
name_id_policy_format = sp_data['NameIDFormat']
@@ -110,7 +121,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
110121
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
111122
AssertionConsumerServiceURL="%(assertion_url)s"
112123
%(attr_consuming_service_str)s>
113-
<saml:Issuer>%(entity_id)s</saml:Issuer>%(nameid_policy_str)s%(requested_authn_context_str)s
124+
<saml:Issuer>%(entity_id)s</saml:Issuer>%(subject_str)s%(nameid_policy_str)s%(requested_authn_context_str)s
114125
</samlp:AuthnRequest>""" % \
115126
{
116127
'id': uid,
@@ -121,6 +132,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
121132
'destination': destination,
122133
'assertion_url': sp_data['assertionConsumerService']['url'],
123134
'entity_id': sp_data['entityId'],
135+
'subject_str': subject_str,
124136
'nameid_policy_str': nameid_policy_str,
125137
'requested_authn_context_str': requested_authn_context_str,
126138
'attr_consuming_service_str': attr_consuming_service_str

tests/data/metadata/testshib-providers.xml

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,23 @@
3737
<ds:KeyInfo>
3838
<ds:X509Data>
3939
<ds:X509Certificate>
40-
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
56+
MLRKhDgdmA==
6257
</ds:X509Certificate>
6358
</ds:X509Data>
6459
</ds:KeyInfo>

tests/src/OneLogin/saml2_tests/auth_test.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ def testLoginSigned(self):
615615
def testLoginForceAuthN(self):
616616
"""
617617
Tests the login method of the OneLogin_Saml2_Auth class
618-
Case Login with no parameters. A AuthN Request is built with ForceAuthn and redirect executed
618+
Case AuthN Request is built with ForceAuthn and redirect executed
619619
"""
620620
settings_info = self.loadSettingsJSON()
621621
return_to = u'http://example.com/returnto'
@@ -649,7 +649,7 @@ def testLoginForceAuthN(self):
649649
def testLoginIsPassive(self):
650650
"""
651651
Tests the login method of the OneLogin_Saml2_Auth class
652-
Case Login with no parameters. A AuthN Request is built with IsPassive and redirect executed
652+
Case AuthN Request is built with IsPassive and redirect executed
653653
"""
654654
settings_info = self.loadSettingsJSON()
655655
return_to = u'http://example.com/returnto'
@@ -683,7 +683,7 @@ def testLoginIsPassive(self):
683683
def testLoginSetNameIDPolicy(self):
684684
"""
685685
Tests the login method of the OneLogin_Saml2_Auth class
686-
Case Logout with no parameters. A AuthN Request is built with and without NameIDPolicy
686+
Case AuthN Request is built with and without NameIDPolicy
687687
"""
688688
settings_info = self.loadSettingsJSON()
689689
return_to = u'http://example.com/returnto'
@@ -714,6 +714,47 @@ def testLoginSetNameIDPolicy(self):
714714
request_3 = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query_3['SAMLRequest'][0])
715715
self.assertNotIn('<samlp:NameIDPolicy', request_3)
716716

717+
def testLoginWithSubject(self):
718+
"""
719+
Tests the login method of the OneLogin_Saml2_Auth class
720+
Case AuthN Request is built with and without Subject
721+
"""
722+
settings_info = self.loadSettingsJSON()
723+
return_to = u'http://example.com/returnto'
724+
sso_url = settings_info['idp']['singleSignOnService']['url']
725+
726+
auth = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
727+
target_url = auth.login(return_to)
728+
parsed_query = parse_qs(urlparse(target_url)[4])
729+
self.assertIn(sso_url, target_url)
730+
self.assertIn('SAMLRequest', parsed_query)
731+
request = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query['SAMLRequest'][0])
732+
self.assertNotIn('<saml:Subject>', request)
733+
self.assertNotIn('<saml:NameID', request)
734+
self.assertNotIn('<saml:saml:SubjectConfirmation', request)
735+
736+
auth_2 = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
737+
target_url_2 = auth_2.login(return_to, name_id_value_req='testuser@example.com')
738+
parsed_query_2 = parse_qs(urlparse(target_url_2)[4])
739+
self.assertIn(sso_url, target_url_2)
740+
self.assertIn('SAMLRequest', parsed_query_2)
741+
request_2 = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query_2['SAMLRequest'][0])
742+
self.assertIn('<saml:Subject>', request_2)
743+
self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">testuser@example.com</saml:NameID>', request_2)
744+
self.assertIn('<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">', request_2)
745+
746+
settings_info['sp']['NameIDFormat'] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
747+
auth_3 = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
748+
target_url_3 = auth_3.login(return_to, name_id_value_req='testuser@example.com')
749+
parsed_query_3 = parse_qs(urlparse(target_url_3)[4])
750+
self.assertIn(sso_url, target_url_3)
751+
self.assertIn('SAMLRequest', parsed_query_3)
752+
request_3 = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query_3['SAMLRequest'][0])
753+
self.assertIn('<saml:Subject>', request_3)
754+
self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">testuser@example.com</saml:NameID>', request_3)
755+
self.assertIn('<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">', request_3)
756+
757+
717758
def testLogout(self):
718759
"""
719760
Tests the logout method of the OneLogin_Saml2_Auth class

tests/src/OneLogin/saml2_tests/authn_request_test.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,38 @@ def testCreateRequestSetNameIDPolicy(self):
262262
self.assertRegexpMatches(inflated_3, '^<samlp:AuthnRequest')
263263
self.assertNotIn('<samlp:NameIDPolicy', inflated_3)
264264

265+
def testCreateRequestSubject(self):
266+
"""
267+
Tests the OneLogin_Saml2_Authn_Request Constructor.
268+
The creation of a deflated SAML Request with and without Subject
269+
"""
270+
saml_settings = self.loadSettingsJSON()
271+
settings = OneLogin_Saml2_Settings(saml_settings)
272+
authn_request = OneLogin_Saml2_Authn_Request(settings)
273+
authn_request_encoded = authn_request.get_request()
274+
decoded = b64decode(authn_request_encoded)
275+
inflated = decompress(decoded, -15)
276+
self.assertRegexpMatches(inflated, '^<samlp:AuthnRequest')
277+
self.assertNotIn('<saml:Subject>', inflated)
278+
279+
authn_request_2 = OneLogin_Saml2_Authn_Request(settings, name_id_value_req='testuser@example.com')
280+
authn_request_encoded_2 = authn_request_2.get_request()
281+
decoded_2 = b64decode(authn_request_encoded_2)
282+
inflated_2 = decompress(decoded_2, -15)
283+
self.assertRegexpMatches(inflated_2, '^<samlp:AuthnRequest')
284+
self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">testuser@example.com</saml:NameID>', inflated_2)
285+
self.assertIn('<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">', inflated_2)
286+
287+
saml_settings['sp']['NameIDFormat'] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
288+
settings = OneLogin_Saml2_Settings(saml_settings)
289+
authn_request_3 = OneLogin_Saml2_Authn_Request(settings, name_id_value_req='testuser@example.com')
290+
authn_request_encoded_3 = authn_request_3.get_request()
291+
decoded_3 = b64decode(authn_request_encoded_3)
292+
inflated_3 = decompress(decoded_3, -15)
293+
self.assertRegexpMatches(inflated_3, '^<samlp:AuthnRequest')
294+
self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">testuser@example.com</saml:NameID>', inflated_3)
295+
self.assertIn('<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">', inflated_3)
296+
265297
def testCreateDeflatedSAMLRequestURLParameter(self):
266298
"""
267299
Tests the OneLogin_Saml2_Authn_Request Constructor.

0 commit comments

Comments
 (0)