Skip to content

Commit fef7b81

Browse files
committed
Single Log Out Fix, Add nameID & sessionIndex support Related to #38
1 parent 93af32b commit fef7b81

File tree

3 files changed

+88
-9
lines changed

3 files changed

+88
-9
lines changed

src/onelogin/saml2/auth.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def login(self, return_to=None):
272272
parameters['Signature'] = self.build_request_signature(saml_request, parameters['RelayState'])
273273
return self.redirect_to(self.get_sso_url(), parameters)
274274

275-
def logout(self, return_to=None):
275+
def logout(self, return_to=None, name_id=None, session_index=None):
276276
"""
277277
Initiates the SLO process.
278278
@@ -288,7 +288,10 @@ def logout(self, return_to=None):
288288
OneLogin_Saml2_Error.SAML_SINGLE_LOGOUT_NOT_SUPPORTED
289289
)
290290

291-
logout_request = OneLogin_Saml2_Logout_Request(self.__settings)
291+
if name_id is None and self.__nameid is not None:
292+
name_id = self.__nameid
293+
294+
logout_request = OneLogin_Saml2_Logout_Request(self.__settings, name_id=name_id, session_index=session_index)
292295

293296
saml_request = logout_request.get_request()
294297

src/onelogin/saml2/logout_request.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class OneLogin_Saml2_Logout_Request(object):
2929
3030
"""
3131

32-
def __init__(self, settings, request=None):
32+
def __init__(self, settings, request=None, name_id=None, session_index=None):
3333
"""
3434
Constructs the Logout Request object.
3535
@@ -45,20 +45,30 @@ def __init__(self, settings, request=None):
4545
security = self.__settings.get_security_data()
4646

4747
uid = OneLogin_Saml2_Utils.generate_unique_id()
48-
name_id_value = OneLogin_Saml2_Utils.generate_unique_id()
4948
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
5049

5150
cert = None
5251
if 'nameIdEncrypted' in security and security['nameIdEncrypted']:
5352
cert = idp_data['x509cert']
5453

55-
name_id = OneLogin_Saml2_Utils.generate_name_id(
56-
name_id_value,
54+
if name_id is not None:
55+
nameIdFormat = sp_data['NameIDFormat']
56+
else:
57+
name_id = idp_data['entityId']
58+
nameIdFormat = OneLogin_Saml2_Constants.NAMEID_ENTITY
59+
60+
name_id_obj = OneLogin_Saml2_Utils.generate_name_id(
61+
name_id,
5762
sp_data['entityId'],
58-
sp_data['NameIDFormat'],
63+
nameIdFormat,
5964
cert
6065
)
6166

67+
if session_index:
68+
session_index_str = '<samlp:SessionIndex>%s</samlp:SessionIndex>' % session_index
69+
else:
70+
session_index_str = ''
71+
6272
logout_request = """<samlp:LogoutRequest
6373
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
6474
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
@@ -68,13 +78,15 @@ def __init__(self, settings, request=None):
6878
Destination="%(single_logout_url)s">
6979
<saml:Issuer>%(entity_id)s</saml:Issuer>
7080
%(name_id)s
81+
%(session_index)s
7182
</samlp:LogoutRequest>""" % \
7283
{
7384
'id': uid,
7485
'issue_instant': issue_instant,
7586
'single_logout_url': idp_data['singleLogoutService']['url'],
7687
'entity_id': sp_data['entityId'],
77-
'name_id': name_id,
88+
'name_id': name_id_obj,
89+
'session_index': session_index_str,
7890
}
7991
else:
8092
decoded = b64decode(request)

tests/src/OneLogin/saml2_tests/auth_test.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from onelogin.saml2.constants import OneLogin_Saml2_Constants
1414
from onelogin.saml2.settings import OneLogin_Saml2_Settings
1515
from onelogin.saml2.utils import OneLogin_Saml2_Utils
16+
from onelogin.saml2.logout_request import OneLogin_Saml2_Logout_Request
1617

1718

1819
class OneLogin_Saml2_Auth_Test(unittest.TestCase):
@@ -95,9 +96,25 @@ def testGetSessionIndex(self):
9596
auth2.process_response()
9697
self.assertEqual('_6273d77b8cde0c333ec79d22a9fa0003b9fe2d75cb', auth2.get_session_index())
9798

99+
def testGetLastErrorReason(self):
100+
"""
101+
Tests the get_last_error_reason method of the OneLogin_Saml2_Auth class
102+
Case Invalid Response
103+
"""
104+
request_data = self.get_request()
105+
message = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
106+
del request_data['get_data']
107+
request_data['post_data'] = {
108+
'SAMLResponse': message
109+
}
110+
auth = OneLogin_Saml2_Auth(request_data, old_settings=self.loadSettingsJSON())
111+
auth.process_response()
112+
113+
self.assertEqual(auth.get_last_error_reason(), 'Signature validation failed. SAML Response rejected')
114+
98115
def testProcessNoResponse(self):
99116
"""
100-
Tests the processResponse method of the OneLogin_Saml2_Auth class
117+
Tests the process_response method of the OneLogin_Saml2_Auth class
101118
Case No Response, An exception is throw
102119
"""
103120
auth = OneLogin_Saml2_Auth(self.get_request(), old_settings=self.loadSettingsJSON())
@@ -645,6 +662,53 @@ def testLogoutNoSLO(self):
645662
except Exception as e:
646663
self.assertIn('The IdP does not support Single Log Out', e.message)
647664

665+
def testLogoutNameIDandSessionIndex(self):
666+
"""
667+
Tests the logout method of the OneLogin_Saml2_Auth class
668+
Case nameID and sessionIndex as parameters.
669+
"""
670+
settings_info = self.loadSettingsJSON()
671+
request_data = self.get_request()
672+
auth = OneLogin_Saml2_Auth(request_data, old_settings=settings_info)
673+
674+
name_id = 'name_id_example'
675+
session_index = 'session_index_example'
676+
target_url = auth.logout(name_id=name_id, session_index=session_index)
677+
parsed_query = parse_qs(urlparse(target_url)[4])
678+
slo_url = settings_info['idp']['singleLogoutService']['url']
679+
self.assertIn(slo_url, target_url)
680+
self.assertIn('SAMLRequest', parsed_query)
681+
682+
logout_request = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query['SAMLRequest'][0])
683+
name_id_from_request = OneLogin_Saml2_Logout_Request.get_nameid(logout_request)
684+
sessions_index_in_request = OneLogin_Saml2_Logout_Request.get_session_indexes(logout_request)
685+
self.assertIn(session_index, sessions_index_in_request)
686+
self.assertEqual(name_id, name_id_from_request)
687+
688+
def testLogoutNameID(self):
689+
"""
690+
Tests the logout method of the OneLogin_Saml2_Auth class
691+
Case nameID loaded after process SAML Response
692+
"""
693+
request_data = self.get_request()
694+
message = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64'))
695+
del request_data['get_data']
696+
request_data['post_data'] = {
697+
'SAMLResponse': message
698+
}
699+
auth = OneLogin_Saml2_Auth(request_data, old_settings=self.loadSettingsJSON())
700+
auth.process_response()
701+
702+
name_id_from_response = auth.get_nameid()
703+
704+
target_url = auth.logout()
705+
parsed_query = parse_qs(urlparse(target_url)[4])
706+
self.assertIn('SAMLRequest', parsed_query)
707+
logout_request = OneLogin_Saml2_Utils.decode_base64_and_inflate(parsed_query['SAMLRequest'][0])
708+
709+
name_id_from_request = OneLogin_Saml2_Logout_Request.get_nameid(logout_request)
710+
self.assertEqual(name_id_from_response, name_id_from_request)
711+
648712
def testSetStrict(self):
649713
"""
650714
Tests the set_strict method of the OneLogin_Saml2_Auth

0 commit comments

Comments
 (0)