Skip to content

Commit acc6766

Browse files
committed
Add support for Subjects on AuthNRequests by the new name_id_value_req parameter
1 parent 02452df commit acc6766

File tree

6 files changed

+102
-16
lines changed

6 files changed

+102
-16
lines changed

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ If our environment requires sign or encrypt support, the certs folder may contai
151151
* sp.crt The public cert of the SP
152152
* sp.key The private key of the SP
153153

154-
Or also we can provide those data in the setting file at the ``X.509cert`` and the ``privateKey`` JSON parameters of the ``sp`` element.
154+
Or also we can provide those data in the setting file at the ``x509cert`` and the ``privateKey`` JSON parameters of the ``sp`` element.
155155

156156
Sometimes we could need a signature on the metadata published by the SP, in this case we could use the X.509 cert previously mentioned or use a new X.509 cert: ``metadata.crt`` and ``metadata.key``.
157157

@@ -161,7 +161,7 @@ publish that X.509 certificate on Service Provider metadata.
161161
If you want to create self-signed certs, you can do it at the https://www.samltool.com/self_signed_certs.php service, or using the command:
162162

163163
```bash
164-
openssl req -new -X.509 -days 3652 -nodes -out sp.crt -keyout saml.key
164+
openssl req -new -x509 -days 3652 -nodes -out sp.crt -keyout saml.key
165165
```
166166

167167
#### demo-flask ####
@@ -264,7 +264,7 @@ This is the ``settings.json`` file:
264264
// represent the requested subject.
265265
// Take a look on src/onelogin/saml2/constants.py to see the NameIdFormat that are supported.
266266
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
267-
// Usually X.509cert and privateKey of the SP are provided by files placed at
267+
// Usually X.509 cert and privateKey of the SP are provided by files placed at
268268
// the certs folder. But we can also provide them with the following parameters
269269
"x509cert": "",
270270
"privateKey": ""
@@ -310,7 +310,7 @@ This is the ``settings.json`` file:
310310
* But take in mind that the fingerprint, is a hash, so at the end is open to a collision attack that can end on a signature validation bypass,
311311
* that why we don't recommend it use for production environments.
312312
*
313-
* (openssl X.509 -noout -fingerprint -in "idp.crt" to generate it,
313+
* (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
314314
* or add for example the -sha256 , -sha384 or -sha512 parameter)
315315
*
316316
* If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
@@ -343,7 +343,7 @@ This is the ``settings.json`` file:
343343
}
344344
```
345345

346-
In addition to the required settings data (IdP, SP), extra settings can be defined in `advanced_settings.json`:
346+
In addition to the required settings data (idp, sp), extra settings can be defined in `advanced_settings.json`:
347347

348348
```javascript
349349
{
@@ -865,7 +865,7 @@ else:
865865

866866
### SP Key rollover ###
867867

868-
If you plan to update the SP ``X.509cert`` and ``privateKey`` you can define the new ``X.509cert`` as ``settings['sp']['X.509certNew']`` and it will be
868+
If you plan to update the SP ``x509cert`` and ``privateKey`` you can define the new ``x509cert`` as ``settings['sp']['x509certNew']`` and it will be
869869
published on the SP metadata so Identity Providers can read them and get ready for rollover.
870870

871871

@@ -874,11 +874,11 @@ published on the SP metadata so Identity Providers can read them and get ready f
874874
In some scenarios the IdP uses different certificates for
875875
signing/encryption, or is under key rollover phase and more than one certificate is published on IdP metadata.
876876

877-
In order to handle that the toolkit offers the ``settings['idp']['X.509certMulti']`` parameter.
877+
In order to handle that the toolkit offers the ``settings['idp']['x509certMulti']`` parameter.
878878

879-
When that parameter is used, ``X.509cert`` and ``certFingerprint`` values will be ignored by the toolkit.
879+
When that parameter is used, ``x509cert`` and ``certFingerprint`` values will be ignored by the toolkit.
880880

881-
The ``X.509certMulti`` is an array with 2 keys:
881+
The ``x509certMulti`` is an array with 2 keys:
882882
- ``signing``: An array of certs that will be used to validate IdP signature
883883
- ``encryption``: An array with one unique cert that will be used to encrypt data to be sent to the IdP.
884884

src/onelogin/saml2/auth.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ def get_last_authn_contexts(self):
327327
"""
328328
return self.__last_authn_contexts
329329

330-
def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True):
330+
def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True, name_id_value_req=None):
331331
"""
332332
Initiates the SSO process.
333333
@@ -343,10 +343,13 @@ def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_
343343
:param set_nameid_policy: Optional argument. When true the AuthNRequest will set a nameIdPolicy element.
344344
:type set_nameid_policy: bool
345345
346+
:param name_id_value_req: Optional argument. Indicates to the IdP the subject that should be authenticated
347+
:type name_id_value_req: string
348+
346349
:returns: Redirection URL
347350
:rtype: string
348351
"""
349-
authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy)
352+
authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy, name_id_value_req)
350353
self.__last_request = authn_request.get_xml()
351354
self.__last_request_id = authn_request.get_id()
352355

src/onelogin/saml2/authn_request.py

Lines changed: 13 additions & 1 deletion
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

@@ -71,6 +74,14 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
7174
if is_passive is True:
7275
is_passive_str = "\n" + ' IsPassive="true"'
7376

77+
subject_str = ''
78+
if name_id_value_req:
79+
subject_str = """
80+
<saml:Subject>
81+
<saml:NameID Format="%s">%s</saml:NameID>
82+
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"></saml:SubjectConfirmation>
83+
</saml:Subject>""" % (sp_data['NameIDFormat'], name_id_value_req)
84+
7485
nameid_policy_str = ''
7586
if set_nameid_policy:
7687
name_id_policy_format = sp_data['NameIDFormat']
@@ -112,6 +123,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
112123
'destination': destination,
113124
'assertion_url': sp_data['assertionConsumerService']['url'],
114125
'entity_id': sp_data['entityId'],
126+
'subject_str': subject_str,
115127
'nameid_policy_str': nameid_policy_str,
116128
'requested_authn_context_str': requested_authn_context_str,
117129
'attr_consuming_service_str': attr_consuming_service_str,

src/onelogin/saml2/xml_templates.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class OneLogin_Saml2_Templates(object):
2929
Destination="%(destination)s"
3030
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
3131
AssertionConsumerServiceURL="%(assertion_url)s"%(attr_consuming_service_str)s>
32-
<saml:Issuer>%(entity_id)s</saml:Issuer>%(nameid_policy_str)s
32+
<saml:Issuer>%(entity_id)s</saml:Issuer>%(subject_str)s%(nameid_policy_str)s
3333
%(requested_authn_context_str)s
3434
</samlp:AuthnRequest>"""
3535

tests/src/OneLogin/saml2_tests/auth_test.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ def testLoginSigned(self):
609609
def testLoginForceAuthN(self):
610610
"""
611611
Tests the login method of the OneLogin_Saml2_Auth class
612-
Case Logout with no parameters. A AuthN Request is built with ForceAuthn and redirect executed
612+
Case AuthN Request is built with ForceAuthn and redirect executed
613613
"""
614614
settings_info = self.loadSettingsJSON()
615615
return_to = u'http://example.com/returnto'
@@ -642,7 +642,7 @@ def testLoginForceAuthN(self):
642642
def testLoginIsPassive(self):
643643
"""
644644
Tests the login method of the OneLogin_Saml2_Auth class
645-
Case Logout with no parameters. A AuthN Request is built with IsPassive and redirect executed
645+
Case AuthN Request is built with IsPassive and redirect executed
646646
"""
647647
settings_info = self.loadSettingsJSON()
648648
return_to = u'http://example.com/returnto'
@@ -676,7 +676,7 @@ def testLoginIsPassive(self):
676676
def testLoginSetNameIDPolicy(self):
677677
"""
678678
Tests the login method of the OneLogin_Saml2_Auth class
679-
Case Logout with no parameters. A AuthN Request is built with and without NameIDPolicy
679+
Case AuthN Request is built with and without NameIDPolicy
680680
"""
681681
settings_info = self.loadSettingsJSON()
682682
return_to = u'http://example.com/returnto'
@@ -707,6 +707,46 @@ def testLoginSetNameIDPolicy(self):
707707
request_3 = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query_3['SAMLRequest'][0]))
708708
self.assertNotIn('<samlp:NameIDPolicy', request_3)
709709

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

tests/src/OneLogin/saml2_tests/authn_request_test.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,37 @@ def testCreateRequestSetNameIDPolicy(self):
257257
self.assertRegex(inflated_3, '^<samlp:AuthnRequest')
258258
self.assertNotIn('<samlp:NameIDPolicy', inflated_3)
259259

260+
def testCreateRequestSubject(self):
261+
"""
262+
Tests the OneLogin_Saml2_Authn_Request Constructor.
263+
The creation of a deflated SAML Request with and without Subject
264+
"""
265+
saml_settings = self.loadSettingsJSON()
266+
settings = OneLogin_Saml2_Settings(saml_settings)
267+
authn_request = OneLogin_Saml2_Authn_Request(settings)
268+
authn_request_encoded = authn_request.get_request()
269+
inflated = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(authn_request_encoded))
270+
self.assertRegex(inflated, '^<samlp:AuthnRequest')
271+
self.assertNotIn('<saml:Subject>', inflated)
272+
273+
authn_request_2 = OneLogin_Saml2_Authn_Request(settings, name_id_value_req='[email protected]')
274+
authn_request_encoded_2 = authn_request_2.get_request()
275+
inflated_2 = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(authn_request_encoded_2))
276+
self.assertRegex(inflated_2, '^<samlp:AuthnRequest')
277+
self.assertIn('<saml:Subject>', inflated_2)
278+
self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">[email protected]</saml:NameID>', inflated_2)
279+
self.assertIn('<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">', inflated_2)
280+
281+
saml_settings['sp']['NameIDFormat'] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
282+
settings = OneLogin_Saml2_Settings(saml_settings)
283+
authn_request_3 = OneLogin_Saml2_Authn_Request(settings, name_id_value_req='[email protected]')
284+
authn_request_encoded_3 = authn_request_3.get_request()
285+
inflated_3 = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(authn_request_encoded_3))
286+
self.assertRegex(inflated_3, '^<samlp:AuthnRequest')
287+
self.assertIn('<saml:Subject>', inflated_3)
288+
self.assertIn('Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">[email protected]</saml:NameID>', inflated_3)
289+
self.assertIn('<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">', inflated_3)
290+
260291
def testCreateDeflatedSAMLRequestURLParameter(self):
261292
"""
262293
Tests the OneLogin_Saml2_Authn_Request Constructor.

0 commit comments

Comments
 (0)