Skip to content

Commit bc7bac6

Browse files
committed
Implement a more specific exception class for handling some validation errors. Improve tests
1 parent 0cd21ec commit bc7bac6

File tree

12 files changed

+410
-127
lines changed

12 files changed

+410
-127
lines changed

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: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class OneLogin_Saml2_Error(Exception):
2525
SETTINGS_INVALID_SYNTAX = 1
2626
SETTINGS_INVALID = 2
2727
METADATA_SP_INVALID = 3
28-
SP_CERTS_NOT_FOUND = 4
28+
CERT_NOT_FOUND = 4
2929
REDIRECT_INVALID_URL = 5
3030
PUBLIC_CERT_FILE_NOT_FOUND = 6
3131
PRIVATE_KEY_FILE_NOT_FOUND = 7
@@ -34,6 +34,8 @@ class OneLogin_Saml2_Error(Exception):
3434
SAML_LOGOUTREQUEST_INVALID = 10
3535
SAML_LOGOUTRESPONSE_INVALID = 11
3636
SAML_SINGLE_LOGOUT_NOT_SUPPORTED = 12
37+
PRIVATE_KEY_NOT_FOUND = 13
38+
UNSUPPORTED_SETTINGS_OBJECT = 14
3739

3840
def __init__(self, message, code=0, errors=None):
3941
"""
@@ -51,3 +53,76 @@ def __init__(self, message, code=0, errors=None):
5153

5254
Exception.__init__(self, message)
5355
self.code = code
56+
57+
58+
class OneLogin_Saml2_ValidationError(Exception):
59+
"""
60+
61+
This class implements another custom Exception handler, related
62+
to exceptions that happens during validation process.
63+
Defines custom error codes .
64+
65+
"""
66+
67+
# Validation Errors
68+
UNSUPPORTED_SAML_VERSION = 0
69+
MISSING_ID = 1
70+
WRONG_NUMBER_OF_ASSERTIONS = 2
71+
MISSING_STATUS = 3
72+
MISSING_STATUS_CODE = 4
73+
STATUS_CODE_IS_NOT_SUCCESS = 5
74+
WRONG_SIGNED_ELEMENT = 6
75+
ID_NOT_FOUND_IN_SIGNED_ELEMENT = 7
76+
DUPLICATED_ID_IN_SIGNED_ELEMENTS = 8
77+
INVALID_SIGNED_ELEMENT = 9
78+
DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS = 10
79+
UNEXPECTED_SIGNED_ELEMENTS = 11
80+
WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE = 12
81+
WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION = 13
82+
INVALID_XML_FORMAT = 14
83+
WRONG_INRESPONSETO = 15
84+
NO_ENCRYPTED_ASSERTION = 16
85+
NO_ENCRYPTED_NAMEID = 17
86+
MISSING_CONDITIONS = 18
87+
ASSERTION_TOO_EARLY = 19
88+
ASSERTION_EXPIRED = 20
89+
WRONG_NUMBER_OF_AUTHSTATEMENTS = 21
90+
NO_ATTRIBUTESTATEMENT = 22
91+
ENCRYPTED_ATTRIBUTES = 23
92+
WRONG_DESTINATION = 24
93+
EMPTY_DESTINATION = 25
94+
WRONG_AUDIENCE = 26
95+
ISSUER_NOT_FOUND_IN_RESPONSE = 27
96+
ISSUER_NOT_FOUND_IN_ASSERTION = 28
97+
WRONG_ISSUER = 29
98+
SESSION_EXPIRED = 30
99+
WRONG_SUBJECTCONFIRMATION = 31
100+
NO_SIGNED_RESPONSE = 32
101+
NO_SIGNED_ASSERTION = 33
102+
NO_SIGNATURE_FOUND = 34
103+
KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA = 35
104+
CHILDREN_NODE_NOT_FOIND_IN_KEYINFO = 36
105+
UNSUPPORTED_RETRIEVAL_METHOD = 37
106+
NO_NAMEID = 38
107+
EMPTY_NAMEID = 39
108+
SP_NAME_QUALIFIER_NAME_MISMATCH = 40
109+
DUPLICATED_ATTRIBUTE_NAME_FOUND = 41
110+
INVALID_SIGNATURE = 42
111+
WRONG_NUMBER_OF_SIGNATURES = 43
112+
113+
def __init__(self, message, code=0, errors=None):
114+
"""
115+
Initializes the Exception instance.
116+
117+
Arguments are:
118+
* (str) message. Describes the error.
119+
* (int) code. The code error (defined in the error class).
120+
"""
121+
assert isinstance(message, basestring)
122+
assert isinstance(code, int)
123+
124+
if errors is not None:
125+
message = message % errors
126+
127+
Exception.__init__(self, message)
128+
self.code = code

src/onelogin/saml2/logout_request.py

Lines changed: 29 additions & 7 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+
'Not NameID found in the Logout Request',
200+
OneLogin_Saml2_ValidationError.NO_NAMEID
201+
)
195202

196203
name_id_data = {
197204
'Value': name_id.text
@@ -289,7 +296,10 @@ def is_valid(self, request_data, raise_exceptions=False):
289296
if self.__settings.is_strict():
290297
res = OneLogin_Saml2_Utils.validate_xml(dom, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
291298
if not isinstance(res, Document):
292-
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+
)
293303

294304
security = self.__settings.get_security_data()
295305

@@ -318,11 +328,17 @@ def is_valid(self, request_data, raise_exceptions=False):
318328
# Check issuer
319329
issuer = OneLogin_Saml2_Logout_Request.get_issuer(dom)
320330
if issuer is not None and issuer != idp_entity_id:
321-
raise Exception('Invalid issuer in the Logout Request')
331+
raise OneLogin_Saml2_ValidationError(
332+
'Invalid issuer in the Logout Request',
333+
OneLogin_Saml2_ValidationError.WRONG_ISSUER
334+
)
322335

323336
if security['wantMessagesSigned']:
324337
if 'Signature' not in get_data:
325-
raise Exception('The Message of the Logout Request is not signed and the SP require it')
338+
raise OneLogin_Saml2_ValidationError(
339+
'The Message of the Logout Request is not signed and the SP require it',
340+
OneLogin_Saml2_ValidationError.NO_SIGNED_RESPONSE
341+
)
326342

327343
if 'Signature' in get_data:
328344
if 'SigAlg' not in get_data:
@@ -336,11 +352,17 @@ def is_valid(self, request_data, raise_exceptions=False):
336352
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))
337353

338354
if 'x509cert' not in idp_data or not idp_data['x509cert']:
339-
raise Exception('In order to validate the sign on the Logout Request, the x509cert of the IdP is required')
355+
raise OneLogin_Saml2_Error(
356+
'In order to validate the sign on the Logout Request, the x509cert of the IdP is required',
357+
OneLogin_Saml2_Error.CERT_NOT_FOUND
358+
)
340359
cert = idp_data['x509cert']
341360

342361
if not OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg):
343-
raise Exception('Signature validation failed. Logout Request rejected')
362+
raise OneLogin_Saml2_ValidationError(
363+
'Signature validation failed. Logout Request rejected',
364+
OneLogin_Saml2_ValidationError.INVALID_SIGNATURE
365+
)
344366

345367
return True
346368
except Exception as err:

src/onelogin/saml2/logout_response.py

Lines changed: 29 additions & 7 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):
@@ -91,20 +92,29 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
9192
if self.__settings.is_strict():
9293
res = OneLogin_Saml2_Utils.validate_xml(self.document, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active())
9394
if not isinstance(res, Document):
94-
raise Exception('Invalid SAML Logout Response. 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+
)
9599

96100
security = self.__settings.get_security_data()
97101

98102
# Check if the InResponseTo of the Logout Response matches the ID of the Logout Request (requestId) if provided
99103
if request_id is not None and self.document.documentElement.hasAttribute('InResponseTo'):
100104
in_response_to = self.document.documentElement.getAttribute('InResponseTo')
101105
if request_id != in_response_to:
102-
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+
)
103110

104111
# Check issuer
105112
issuer = self.get_issuer()
106113
if issuer is not None and issuer != idp_entity_id:
107-
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+
)
108118

109119
current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data)
110120

@@ -113,11 +123,17 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
113123
destination = self.document.documentElement.getAttribute('Destination')
114124
if destination != '':
115125
if current_url not in destination:
116-
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+
)
117130

118131
if security['wantMessagesSigned']:
119132
if 'Signature' not in get_data:
120-
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_RESPONSE
136+
)
121137

122138
if 'Signature' in get_data:
123139
if 'SigAlg' not in get_data:
@@ -131,11 +147,17 @@ def is_valid(self, request_data, request_id=None, raise_exceptions=False):
131147
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))
132148

133149
if 'x509cert' not in idp_data or not idp_data['x509cert']:
134-
raise Exception('In order to validate the sign on the Logout Response, the x509cert of the IdP is required')
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+
)
135154
cert = idp_data['x509cert']
136155

137156
if not OneLogin_Saml2_Utils.validate_binary_sign(signed_query, b64decode(get_data['Signature']), cert, sign_alg):
138-
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+
)
139161

140162
return True
141163
# pylint: disable=R0801

0 commit comments

Comments
 (0)