Skip to content

Commit 414e05e

Browse files
committed
Fix #119. Allow AuthnRequest with no NameIDPolicy
1 parent 7582e4f commit 414e05e

6 files changed

Lines changed: 93 additions & 20 deletions

File tree

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -518,10 +518,11 @@ We can set a 'return_to' url parameter to the login function and that will be co
518518
target_url = 'https://example.com'
519519
auth.login(return_to=target_url)
520520
```
521-
The login method can recieve 2 more optional parameters:
521+
The login method can recieve 3 more optional parameters:
522522

523-
* force_authn When true the AuthNReuqest will set the ForceAuthn='true'
524-
* is_passive When true the AuthNReuqest will set the Ispassive='true'
523+
* force_authn When true the AuthNReuqest will set the ForceAuthn='true'
524+
* is_passive When true the AuthNReuqest will set the Ispassive='true'
525+
* set_nameid_policy When true the AuthNReuqest will set a nameIdPolicy element.
525526

526527
If a match on the future SAMLResponse ID and the AuthNRequest ID to be sent is required, that AuthNRequest ID must to be extracted and stored for future validation, we can get that ID by
527528

src/onelogin/saml2/auth.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,22 +267,26 @@ def get_last_request_id(self):
267267
"""
268268
return self.__last_request_id
269269

270-
def login(self, return_to=None, force_authn=False, is_passive=False):
270+
def login(self, return_to=None, force_authn=False, is_passive=False, set_nameid_policy=True):
271271
"""
272272
Initiates the SSO process.
273273
274274
:param return_to: Optional argument. The target URL the user should be redirected to after login.
275275
:type return_to: string
276276
277277
:param force_authn: Optional argument. When true the AuthNReuqest will set the ForceAuthn='true'.
278-
:type force_authn: string
278+
:type force_authn: bool
279279
280280
:param is_passive: Optional argument. When true the AuthNReuqest will set the Ispassive='true'.
281-
:type is_passive: string
281+
:type is_passive: bool
282+
283+
:param set_nameid_policy: Optional argument. When true the AuthNReuqest will set a nameIdPolicy element.
284+
:type set_nameid_policy: bool
282285
283286
:returns: Redirection url
287+
:rtype: string
284288
"""
285-
authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive)
289+
authn_request = OneLogin_Saml2_Authn_Request(self.__settings, force_authn, is_passive, set_nameid_policy)
286290

287291
self.__last_request_id = authn_request.get_id()
288292

src/onelogin/saml2/authn_request.py

Lines changed: 17 additions & 9 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):
25+
def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_policy=True):
2626
"""
2727
Constructs the AuthnRequest object.
2828
@@ -34,6 +34,9 @@ def __init__(self, settings, force_authn=False, is_passive=False):
3434
3535
:param is_passive: Optional argument. When true the AuthNReuqest will set the Ispassive='true'.
3636
:type is_passive: bool
37+
38+
:param set_nameid_policy: Optional argument. When true the AuthNReuqest will set a nameIdPolicy element.
39+
:type set_nameid_policy: bool
3740
"""
3841
self.__settings = settings
3942

@@ -47,10 +50,6 @@ def __init__(self, settings, force_authn=False, is_passive=False):
4750

4851
destination = idp_data['singleSignOnService']['url']
4952

50-
name_id_policy_format = sp_data['NameIDFormat']
51-
if 'wantNameIdEncrypted' in security and security['wantNameIdEncrypted']:
52-
name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED
53-
5453
provider_name_str = ''
5554
organization_data = settings.get_organization()
5655
if isinstance(organization_data, dict) and organization_data:
@@ -70,6 +69,17 @@ def __init__(self, settings, force_authn=False, is_passive=False):
7069
if is_passive is True:
7170
is_passive_str = 'IsPassive="true"'
7271

72+
nameid_policy_str = ''
73+
if set_nameid_policy:
74+
name_id_policy_format = sp_data['NameIDFormat']
75+
if 'wantNameIdEncrypted' in security and security['wantNameIdEncrypted']:
76+
name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED
77+
78+
nameid_policy_str = """
79+
<samlp:NameIDPolicy
80+
Format="%s"
81+
AllowCreate="true" />""" % name_id_policy_format
82+
7383
requested_authn_context_str = ''
7484
if 'requestedAuthnContext' in security.keys() and security['requestedAuthnContext'] is not False:
7585
authn_comparison = 'exact'
@@ -104,9 +114,7 @@ def __init__(self, settings, force_authn=False, is_passive=False):
104114
AssertionConsumerServiceURL="%(assertion_url)s"
105115
%(attr_consuming_service_str)s>
106116
<saml:Issuer>%(entity_id)s</saml:Issuer>
107-
<samlp:NameIDPolicy
108-
Format="%(name_id_policy)s"
109-
AllowCreate="true" />
117+
%(nameid_policy_str)s
110118
%(requested_authn_context_str)s
111119
</samlp:AuthnRequest>""" % \
112120
{
@@ -118,7 +126,7 @@ def __init__(self, settings, force_authn=False, is_passive=False):
118126
'destination': destination,
119127
'assertion_url': sp_data['assertionConsumerService']['url'],
120128
'entity_id': sp_data['entityId'],
121-
'name_id_policy': name_id_policy_format,
129+
'nameid_policy_str': nameid_policy_str,
122130
'requested_authn_context_str': requested_authn_context_str,
123131
'attr_consuming_service_str': attr_consuming_service_str
124132
}

src/onelogin/saml2/utils.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,14 +1051,12 @@ def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, finger
10511051
if fingerprint == x509_fingerprint_value:
10521052
cert = OneLogin_Saml2_Utils.format_cert(x509_cert_value)
10531053

1054-
10551054
# Check if Reference URI is empty
10561055
# reference_elem = OneLogin_Saml2_Utils.query(signature_node, '//ds:Reference')
10571056
# if len(reference_elem) > 0:
10581057
# if reference_elem[0].get('URI') == '':
10591058
# reference_elem[0].set('URI', '#%s' % signature_node.getparent().get('ID'))
10601059

1061-
10621060
if cert is None or cert == '':
10631061
return False
10641062

tests/src/OneLogin/saml2_tests/auth_test.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ def testLoginSigned(self):
608608
def testLoginForceAuthN(self):
609609
"""
610610
Tests the login method of the OneLogin_Saml2_Auth class
611-
Case Logout with no parameters. A AuthN Request is built with ForceAuthn and redirect executed
611+
Case Login with no parameters. A AuthN Request is built with ForceAuthn and redirect executed
612612
"""
613613
settings_info = self.loadSettingsJSON()
614614
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 Login with no parameters. A AuthN Request is built with IsPassive and redirect executed
646646
"""
647647
settings_info = self.loadSettingsJSON()
648648
return_to = u'http://example.com/returnto'
@@ -673,6 +673,40 @@ def testLoginIsPassive(self):
673673
request_3 = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query_3['SAMLRequest'][0])
674674
self.assertIn('IsPassive="true"', request_3)
675675

676+
def testLoginSetNameIDPolicy(self):
677+
"""
678+
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
680+
"""
681+
settings_info = self.loadSettingsJSON()
682+
return_to = u'http://example.com/returnto'
683+
sso_url = settings_info['idp']['singleSignOnService']['url']
684+
685+
auth = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
686+
target_url = auth.login(return_to)
687+
parsed_query = parse_qs(urlparse(target_url)[4])
688+
sso_url = settings_info['idp']['singleSignOnService']['url']
689+
self.assertIn(sso_url, target_url)
690+
self.assertIn('SAMLRequest', parsed_query)
691+
request = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query['SAMLRequest'][0])
692+
self.assertIn('<samlp:NameIDPolicy', request)
693+
694+
auth_2 = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
695+
target_url_2 = auth_2.login(return_to, False, False, True)
696+
parsed_query_2 = parse_qs(urlparse(target_url_2)[4])
697+
self.assertIn(sso_url, target_url_2)
698+
self.assertIn('SAMLRequest', parsed_query_2)
699+
request_2 = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query_2['SAMLRequest'][0])
700+
self.assertIn('<samlp:NameIDPolicy', request_2)
701+
702+
auth_3 = OneLogin_Saml2_Auth(self.get_request(), old_settings=settings_info)
703+
target_url_3 = auth_3.login(return_to, False, False, False)
704+
parsed_query_3 = parse_qs(urlparse(target_url_3)[4])
705+
self.assertIn(sso_url, target_url_3)
706+
self.assertIn('SAMLRequest', parsed_query_3)
707+
request_3 = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query_3['SAMLRequest'][0])
708+
self.assertNotIn('<samlp:NameIDPolicy', request_3)
709+
676710
def testLogout(self):
677711
"""
678712
Tests the logout method of the OneLogin_Saml2_Auth class

tests/src/OneLogin/saml2_tests/authn_request_test.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,34 @@ def testCreateRequestIsPassive(self):
208208
self.assertRegexpMatches(inflated_3, '^<samlp:AuthnRequest')
209209
self.assertIn('IsPassive="true"', inflated_3)
210210

211+
def testCreateRequestSetNameIDPolicy(self):
212+
"""
213+
Tests the OneLogin_Saml2_Authn_Request Constructor.
214+
The creation of a deflated SAML Request with and without NameIDPolicy
215+
"""
216+
saml_settings = self.loadSettingsJSON()
217+
settings = OneLogin_Saml2_Settings(saml_settings)
218+
authn_request = OneLogin_Saml2_Authn_Request(settings)
219+
authn_request_encoded = authn_request.get_request()
220+
decoded = b64decode(authn_request_encoded)
221+
inflated = decompress(decoded, -15)
222+
self.assertRegexpMatches(inflated, '^<samlp:AuthnRequest')
223+
self.assertIn('<samlp:NameIDPolicy', inflated)
224+
225+
authn_request_2 = OneLogin_Saml2_Authn_Request(settings, False, False, True)
226+
authn_request_encoded_2 = authn_request_2.get_request()
227+
decoded_2 = b64decode(authn_request_encoded_2)
228+
inflated_2 = decompress(decoded_2, -15)
229+
self.assertRegexpMatches(inflated_2, '^<samlp:AuthnRequest')
230+
self.assertIn('<samlp:NameIDPolicy', inflated_2)
231+
232+
authn_request_3 = OneLogin_Saml2_Authn_Request(settings, False, False, False)
233+
authn_request_encoded_3 = authn_request_3.get_request()
234+
decoded_3 = b64decode(authn_request_encoded_3)
235+
inflated_3 = decompress(decoded_3, -15)
236+
self.assertRegexpMatches(inflated_3, '^<samlp:AuthnRequest')
237+
self.assertNotIn('<samlp:NameIDPolicy', inflated_3)
238+
211239
def testCreateDeflatedSAMLRequestURLParameter(self):
212240
"""
213241
Tests the OneLogin_Saml2_Authn_Request Constructor.

0 commit comments

Comments
 (0)