Skip to content

Commit f947feb

Browse files
authored
Merge pull request #175 from onelogin/improve_debug
Improve debug
2 parents d31952f + 74305d5 commit f947feb

File tree

15 files changed

+837
-721
lines changed

15 files changed

+837
-721
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ Main class of OneLogin Python Toolkit
833833
* ***get_settings*** Returns the settings info.
834834
* ***set_strict*** Set the strict mode active/disable.
835835
* ***get_last_request_xml*** Returns the most recently-constructed/processed XML SAML request (AuthNRequest, LogoutRequest)
836-
* ***get_last_response_xml*** Returns the most recently-constructed/processed XML SAML response (SAMLResponse, LogoutResponse). If the SAMLResponse was encrypted, by default tries to return the decrypted XML.
836+
* ***get_last_response_xml*** Returns the most recently-constructed/processed XML SAML response (SAMLResponse, LogoutResponse). If the SAMLResponse had an encrypted assertion, decrypts it.
837837

838838
####OneLogin_Saml2_Auth - authn_request.py####
839839

@@ -842,7 +842,7 @@ SAML 2 Authentication Request class
842842
* `__init__` This class handles an AuthNRequest. It builds an AuthNRequest object.
843843
* ***get_request*** Returns unsigned AuthnRequest.
844844
* ***get_id*** Returns the AuthNRequest ID.
845-
845+
* ***get_xml*** Returns the XML that will be sent as part of the request.
846846

847847
####OneLogin_Saml2_Response - response.py####
848848

@@ -861,6 +861,7 @@ SAML 2 Authentication Response class
861861
* ***validate_num_assertions*** Verifies that the document only contains a single Assertion (encrypted or not)
862862
* ***validate_timestamps*** Verifies that the document is valid according to Conditions Element
863863
* ***get_error*** After execute a validation process, if fails this method returns the cause
864+
* ***get_xml_document*** Returns the SAML Response document (If contains an encrypted assertion, decrypts it).
864865

865866
####OneLogin_Saml2_LogoutRequest - logout_request.py####
866867

@@ -875,6 +876,7 @@ SAML 2 Logout Request class
875876
* ***get_session_indexes*** Gets the SessionIndexes from the Logout Request.
876877
* ***is_valid*** Checks if the Logout Request recieved is valid.
877878
* ***get_error*** After execute a validation process, if fails this method returns the cause.
879+
* ***get_xml*** Returns the XML that will be sent as part of the request or that was received at the SP
878880

879881
####OneLogin_Saml2_LogoutResponse - logout_response.py####
880882

@@ -887,6 +889,7 @@ SAML 2 Logout Response class
887889
* ***build*** Creates a Logout Response object.
888890
* ***get_response*** Returns a Logout Response object.
889891
* ***get_error*** After execute a validation process, if fails this method returns the cause.
892+
* ***get_xml*** Returns the XML that will be sent as part of the response or that was received at the SP
890893

891894

892895
####OneLogin_Saml2_Settings - settings.py####

src/onelogin/saml2/auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ def __build_signature(self, saml_data, relay_state, saml_type, sign_algorithm=On
432432
if not key:
433433
raise OneLogin_Saml2_Error(
434434
"Trying to sign the %s but can't load the SP private key" % saml_type,
435-
OneLogin_Saml2_Error.SP_CERTS_NOT_FOUND
435+
OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND
436436
)
437437

438438
dsig_ctx = xmlsec.DSigCtx()

src/onelogin/saml2/errors.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ class OneLogin_Saml2_Error(Exception):
2525
SETTINGS_INVALID_SYNTAX = 1
2626
SETTINGS_INVALID = 2
2727
METADATA_SP_INVALID = 3
28+
# SP_CERTS_NOT_FOUND is deprecated, use CERT_NOT_FOUND instead
2829
SP_CERTS_NOT_FOUND = 4
30+
CERT_NOT_FOUND = 4
2931
REDIRECT_INVALID_URL = 5
3032
PUBLIC_CERT_FILE_NOT_FOUND = 6
3133
PRIVATE_KEY_FILE_NOT_FOUND = 7
@@ -34,6 +36,82 @@ class OneLogin_Saml2_Error(Exception):
3436
SAML_LOGOUTREQUEST_INVALID = 10
3537
SAML_LOGOUTRESPONSE_INVALID = 11
3638
SAML_SINGLE_LOGOUT_NOT_SUPPORTED = 12
39+
PRIVATE_KEY_NOT_FOUND = 13
40+
UNSUPPORTED_SETTINGS_OBJECT = 14
41+
42+
def __init__(self, message, code=0, errors=None):
43+
"""
44+
Initializes the Exception instance.
45+
46+
Arguments are:
47+
* (str) message. Describes the error.
48+
* (int) code. The code error (defined in the error class).
49+
"""
50+
assert isinstance(message, basestring)
51+
assert isinstance(code, int)
52+
53+
if errors is not None:
54+
message = message % errors
55+
56+
Exception.__init__(self, message)
57+
self.code = code
58+
59+
60+
class OneLogin_Saml2_ValidationError(Exception):
61+
"""
62+
63+
This class implements another custom Exception handler, related
64+
to exceptions that happens during validation process.
65+
Defines custom error codes .
66+
67+
"""
68+
69+
# Validation Errors
70+
UNSUPPORTED_SAML_VERSION = 0
71+
MISSING_ID = 1
72+
WRONG_NUMBER_OF_ASSERTIONS = 2
73+
MISSING_STATUS = 3
74+
MISSING_STATUS_CODE = 4
75+
STATUS_CODE_IS_NOT_SUCCESS = 5
76+
WRONG_SIGNED_ELEMENT = 6
77+
ID_NOT_FOUND_IN_SIGNED_ELEMENT = 7
78+
DUPLICATED_ID_IN_SIGNED_ELEMENTS = 8
79+
INVALID_SIGNED_ELEMENT = 9
80+
DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS = 10
81+
UNEXPECTED_SIGNED_ELEMENTS = 11
82+
WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE = 12
83+
WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION = 13
84+
INVALID_XML_FORMAT = 14
85+
WRONG_INRESPONSETO = 15
86+
NO_ENCRYPTED_ASSERTION = 16
87+
NO_ENCRYPTED_NAMEID = 17
88+
MISSING_CONDITIONS = 18
89+
ASSERTION_TOO_EARLY = 19
90+
ASSERTION_EXPIRED = 20
91+
WRONG_NUMBER_OF_AUTHSTATEMENTS = 21
92+
NO_ATTRIBUTESTATEMENT = 22
93+
ENCRYPTED_ATTRIBUTES = 23
94+
WRONG_DESTINATION = 24
95+
EMPTY_DESTINATION = 25
96+
WRONG_AUDIENCE = 26
97+
ISSUER_NOT_FOUND_IN_RESPONSE = 27
98+
ISSUER_NOT_FOUND_IN_ASSERTION = 28
99+
WRONG_ISSUER = 29
100+
SESSION_EXPIRED = 30
101+
WRONG_SUBJECTCONFIRMATION = 31
102+
NO_SIGNED_MESSAGE = 32
103+
NO_SIGNED_ASSERTION = 33
104+
NO_SIGNATURE_FOUND = 34
105+
KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA = 35
106+
CHILDREN_NODE_NOT_FOUND_IN_KEYINFO = 36
107+
UNSUPPORTED_RETRIEVAL_METHOD = 37
108+
NO_NAMEID = 38
109+
EMPTY_NAMEID = 39
110+
SP_NAME_QUALIFIER_NAME_MISMATCH = 40
111+
DUPLICATED_ATTRIBUTE_NAME_FOUND = 41
112+
INVALID_SIGNATURE = 42
113+
WRONG_NUMBER_OF_SIGNATURES = 43
114+
RESPONSE_EXPIRED = 44
37115

38116
def __init__(self, message, code=0, errors=None):
39117
"""

src/onelogin/saml2/logout_request.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from onelogin.saml2.constants import OneLogin_Saml2_Constants
1919
from onelogin.saml2.utils import OneLogin_Saml2_Utils
20+
from onelogin.saml2.errors import OneLogin_Saml2_Error, OneLogin_Saml2_ValidationError
2021

2122

2223
class OneLogin_Saml2_Logout_Request(object):
@@ -179,7 +180,10 @@ def get_nameid_data(request, key=None):
179180

180181
if len(encrypted_entries) == 1:
181182
if key is None:
182-
raise Exception('Key is required in order to decrypt the NameID')
183+
raise OneLogin_Saml2_Error(
184+
'Private Key is required in order to decrypt the NameID, check settings',
185+
OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND
186+
)
183187

184188
encrypted_data_nodes = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/saml:EncryptedID/xenc:EncryptedData')
185189
if len(encrypted_data_nodes) == 1:
@@ -191,7 +195,10 @@ def get_nameid_data(request, key=None):
191195
name_id = entries[0]
192196

193197
if name_id is None:
194-
raise Exception('Not NameID found in the Logout Request')
198+
raise OneLogin_Saml2_ValidationError(
199+
'NameID not found in the Logout Request',
200+
OneLogin_Saml2_ValidationError.NO_NAMEID
201+
)
195202

196203
name_id_data = {
197204
'Value': name_id.text
@@ -260,12 +267,13 @@ def get_session_indexes(request):
260267
session_indexes.append(session_index_node.text)
261268
return session_indexes
262269

263-
def is_valid(self, request_data):
270+
def is_valid(self, request_data, raise_exceptions=False):
264271
"""
265272
Checks if the Logout Request received is valid
266273
:param request_data: Request Data
267274
:type request_data: dict
268-
275+
:param raise_exceptions: Whether to return false on failure or raise an exception
276+
:type raise_exceptions: Boolean
269277
:return: If the Logout Request is or not valid
270278
:rtype: boolean
271279
"""
@@ -288,7 +296,10 @@ def is_valid(self, request_data):
288296
if self.__settings.is_strict():
289297
res = OneLogin_Saml2_Utils.validate_xml(dom, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
290298
if not isinstance(res, Document):
291-
raise Exception('Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd')
299+
raise OneLogin_Saml2_ValidationError(
300+
'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd',
301+
OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT
302+
)
292303

293304
security = self.__settings.get_security_data()
294305

@@ -298,7 +309,10 @@ def is_valid(self, request_data):
298309
if dom.get('NotOnOrAfter', None):
299310
na = OneLogin_Saml2_Utils.parse_SAML_to_time(dom.get('NotOnOrAfter'))
300311
if na <= OneLogin_Saml2_Utils.now():
301-
raise Exception('Timing issues (please check your clock settings)')
312+
raise OneLogin_Saml2_ValidationError(
313+
'Could not validate timestamp: expired. Check system clock.',
314+
OneLogin_Saml2_ValidationError.RESPONSE_EXPIRED
315+
)
302316

303317
# Check destination
304318
if dom.get('Destination', None):
@@ -311,17 +325,24 @@ def is_valid(self, request_data):
311325
{
312326
'currentURL': current_url,
313327
'destination': destination,
314-
}
328+
},
329+
OneLogin_Saml2_ValidationError.WRONG_DESTINATION
315330
)
316331

317332
# Check issuer
318333
issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom)
319334
if issuer is not None and issuer != idp_entity_id:
320-
raise Exception('Invalid issuer in the Logout Request')
335+
raise OneLogin_Saml2_ValidationError(
336+
'Invalid issuer in the Logout Request',
337+
OneLogin_Saml2_ValidationError.WRONG_ISSUER
338+
)
321339

322340
if security['wantMessagesSigned']:
323341
if 'Signature' not in get_data:
324-
raise Exception('The Message of the Logout Request is not signed and the SP require it')
342+
raise OneLogin_Saml2_ValidationError(
343+
'The Message of the Logout Request is not signed and the SP require it',
344+
OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE
345+
)
325346

326347
if 'Signature' in get_data:
327348
if 'SigAlg' not in get_data:
@@ -334,12 +355,18 @@ def is_valid(self, request_data):
334355
signed_query = '%s&RelayState=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'RelayState', lowercase_urlencoding=lowercase_urlencoding))
335356
signed_query = '%s&SigAlg=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1, lowercase_urlencoding=lowercase_urlencoding))
336357

337-
if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
338-
raise Exception('In order to validate the sign on the Logout Request, the x509cert of the IdP is required')
358+
if 'x509cert' not in idp_data or not idp_data['x509cert']:
359+
raise OneLogin_Saml2_Error(
360+
'In order to validate the sign on the Logout Request, the x509cert of the IdP is required',
361+
OneLogin_Saml2_Error.CERT_NOT_FOUND
362+
)
339363
cert = idp_data['x509cert']
340364

341365
if not OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg):
342-
raise Exception('Signature validation failed. Logout Request rejected')
366+
raise OneLogin_Saml2_ValidationError(
367+
'Signature validation failed. Logout Request rejected',
368+
OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
369+
)
343370

344371
return True
345372
except Exception as err:
@@ -348,6 +375,8 @@ def is_valid(self, request_data):
348375
debug = self.__settings.is_debug_active()
349376
if debug:
350377
print err.__str__()
378+
if raise_exceptions:
379+
raise err
351380
return False
352381

353382
def get_error(self):

src/onelogin/saml2/logout_response.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from onelogin.saml2.constants import OneLogin_Saml2_Constants
1919
from onelogin.saml2.utils import OneLogin_Saml2_Utils
20+
from onelogin.saml2.errors import OneLogin_Saml2_Error, OneLogin_Saml2_ValidationError
2021

2122

2223
class OneLogin_Saml2_Logout_Response(object):
@@ -68,11 +69,13 @@ def get_status(self):
6869
status = entries[0].attrib['Value']
6970
return status
7071

71-
def is_valid(self, request_data, request_id=None):
72+
def is_valid(self, request_data, request_id=None, raise_exceptions=False):
7273
"""
7374
Determines if the SAML LogoutResponse is valid
7475
:param request_id: The ID of the LogoutRequest sent by this SP to the IdP
7576
:type request_id: string
77+
:param raise_exceptions: Whether to return false on failure or raise an exception
78+
:type raise_exceptions: Boolean
7679
:return: Returns if the SAML LogoutResponse is or not valid
7780
:rtype: boolean
7881
"""
@@ -89,20 +92,29 @@ def is_valid(self, request_data, request_id=None):
8992
if self.__settings.is_strict():
9093
res = OneLogin_Saml2_Utils.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
9194
if not isinstance(res, Document):
92-
raise Exception('Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd')
95+
raise OneLogin_Saml2_ValidationError(
96+
'Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd',
97+
OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT
98+
)
9399

94100
security = self.__settings.get_security_data()
95101

96102
# Check if the InResponseTo of the Logout Response matches the ID of the Logout Request (requestId) if provided
97103
if request_id is not None and self.document.documentElement.hasAttribute('InResponseTo'):
98104
in_response_to = self.document.documentElement.getAttribute('InResponseTo')
99105
if request_id != in_response_to:
100-
raise Exception('The InResponseTo of the Logout Response: %s, does not match the ID of the Logout request sent by the SP: %s' % (in_response_to, request_id))
106+
raise OneLogin_Saml2_ValidationError(
107+
'The InResponseTo of the Logout Response: %s, does not match the ID of the Logout request sent by the SP: %s' % (in_response_to, request_id),
108+
OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO
109+
)
101110

102111
# Check issuer
103112
issuer = self.get_issuer()
104113
if issuer is not None and issuer != idp_entity_id:
105-
raise Exception('Invalid issuer in the Logout Request')
114+
raise OneLogin_Saml2_ValidationError(
115+
'Invalid issuer in the Logout Request',
116+
OneLogin_Saml2_ValidationError.WRONG_ISSUER
117+
)
106118

107119
current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
108120

@@ -111,11 +123,17 @@ def is_valid(self, request_data, request_id=None):
111123
destination = self.document.documentElement.getAttribute('Destination')
112124
if destination != '':
113125
if current_url not in destination:
114-
raise Exception('The LogoutRequest was received at $currentURL instead of $destination')
126+
raise OneLogin_Saml2_ValidationError(
127+
'The LogoutResponse was received at %s instead of %s' % (current_url, destination),
128+
OneLogin_Saml2_ValidationError.WRONG_DESTINATION
129+
)
115130

116131
if security['wantMessagesSigned']:
117132
if 'Signature' not in get_data:
118-
raise Exception('The Message of the Logout Response is not signed and the SP require it')
133+
raise OneLogin_Saml2_ValidationError(
134+
'The Message of the Logout Response is not signed and the SP require it',
135+
OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE
136+
)
119137

120138
if 'Signature' in get_data:
121139
if 'SigAlg' not in get_data:
@@ -128,12 +146,18 @@ def is_valid(self, request_data, request_id=None):
128146
signed_query = '%s&RelayState=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'RelayState', lowercase_urlencoding=lowercase_urlencoding))
129147
signed_query = '%s&SigAlg=%s' % (signed_query, OneLogin_Saml2_Utils.get_encoded_parameter(get_data, 'SigAlg', OneLogin_Saml2_Constants.RSA_SHA1, lowercase_urlencoding=lowercase_urlencoding))
130148

131-
if 'x509cert' not in idp_data or idp_data['x509cert'] is None:
132-
raise Exception('In order to validate the sign on the Logout Response, the x509cert of the IdP is required')
149+
if 'x509cert' not in idp_data or not idp_data['x509cert']:
150+
raise OneLogin_Saml2_Error(
151+
'In order to validate the sign on the Logout Response, the x509cert of the IdP is required',
152+
OneLogin_Saml2_Error.CERT_NOT_FOUND
153+
)
133154
cert = idp_data['x509cert']
134155

135156
if not OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg):
136-
raise Exception('Signature validation failed. Logout Response rejected')
157+
raise OneLogin_Saml2_ValidationError(
158+
'Signature validation failed. Logout Response rejected',
159+
OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
160+
)
137161

138162
return True
139163
# pylint: disable=R0801
@@ -142,6 +166,8 @@ def is_valid(self, request_data, request_id=None):
142166
debug = self.__settings.is_debug_active()
143167
if debug:
144168
print err.__str__()
169+
if raise_exceptions:
170+
raise err
145171
return False
146172

147173
def __query(self, query):

0 commit comments

Comments
 (0)