-
-
Notifications
You must be signed in to change notification settings - Fork 311
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve debug #42
Improve debug #42
Changes from 11 commits
a42554a
e7b17a7
d3549f9
2965e00
c3d92fb
5545e21
16e38be
f95abc5
7ac2640
b144b06
9ea32f5
73ee977
a510f16
37788e2
a096f36
acb88e0
fa6d3ea
cbbe084
c90a62d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,7 @@ class OneLogin_Saml2_Error(Exception): | |
SETTINGS_INVALID_SYNTAX = 1 | ||
SETTINGS_INVALID = 2 | ||
METADATA_SP_INVALID = 3 | ||
SP_CERTS_NOT_FOUND = 4 | ||
CERT_NOT_FOUND = 4 | ||
REDIRECT_INVALID_URL = 5 | ||
PUBLIC_CERT_FILE_NOT_FOUND = 6 | ||
PRIVATE_KEY_FILE_NOT_FOUND = 7 | ||
|
@@ -34,6 +34,8 @@ class OneLogin_Saml2_Error(Exception): | |
SAML_LOGOUTREQUEST_INVALID = 10 | ||
SAML_LOGOUTRESPONSE_INVALID = 11 | ||
SAML_SINGLE_LOGOUT_NOT_SUPPORTED = 12 | ||
PRIVATE_KEY_NOT_FOUND = 13 | ||
UNSUPPORTED_SETTINGS_OBJECT = 14 | ||
|
||
def __init__(self, message, code=0, errors=None): | ||
""" | ||
|
@@ -50,3 +52,73 @@ def __init__(self, message, code=0, errors=None): | |
|
||
Exception.__init__(self, message) | ||
self.code = code | ||
|
||
|
||
class OneLogin_Saml2_ValidationError(Exception): | ||
""" | ||
This class implements another custom Exception handler, related | ||
to exceptions that happens during validation process. | ||
Defines custom error codes . | ||
""" | ||
|
||
# Validation Errors | ||
UNSUPPORTED_SAML_VERSION = 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally don't like the use of "codes" in exceptions, since users of the library need to understand they need to inspect a specific property in the exception, but I do see how this is keeping consistency with the implementation of the existing A better solution would be to have an exception for each of these individually, that possibly inherit from a base class exception class There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also don't like exception codes. I would like to have Some code showing usage: class OneLogin_Saml2_Error(Exception):
....
class OneLogin_Saml2_ValidationError(OneLogin_Saml2_Error):
# For backwards compatibility
code = 123
class OneLogin_Saml2_ExpiredResponseError(OneLogin_Saml2_ValidationError):
# If we want to keep backwards compatibility
code = 123
# Usage:
try:
auth.process_sso(...)
except OneLogin_Saml2_ExpiredResponseError as e:
# Not important, tell user to retry
except OneLogin_Saml2_Error as e:
# Inform user about the error and log it somewhere With that said, I really liked the changes in this PR. 👍 I think that the exception handling can be changed to the way I showed later on without breaking any existing code, so that can be implemented later on. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My idea here is to have 2 different kind of exceptions:
I got your point of view, but then we need 50+ exception methods ... In real scenarios I don't think that you gonna need to catch many specific exception, rather than just check if there was an environment/settings error, or a validation error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, you are right that in most cases there is no need to many specific exceptions, and making separate class for each error would add lots of really small exception classes. I think that in many cases it is quite irrelevant what the exact error is, so one common validation error class makes sense. I am no longer sure if the validation error should be inherited from the |
||
MISSING_ID = 1 | ||
WRONG_NUMBER_OF_ASSERTIONS = 2 | ||
MISSING_STATUS = 3 | ||
MISSING_STATUS_CODE = 4 | ||
STATUS_CODE_IS_NOT_SUCCESS = 5 | ||
WRONG_SIGNED_ELEMENT = 6 | ||
ID_NOT_FOUND_IN_SIGNED_ELEMENT = 7 | ||
DUPLICATED_ID_IN_SIGNED_ELEMENTS = 8 | ||
INVALID_SIGNED_ELEMENT = 9 | ||
DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS = 10 | ||
UNEXPECTED_SIGNED_ELEMENTS = 11 | ||
WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE = 12 | ||
WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION = 13 | ||
INVALID_XML_FORMAT = 14 | ||
WRONG_INRESPONSETO = 15 | ||
NO_ENCRYPTED_ASSERTION = 16 | ||
NO_ENCRYPTED_NAMEID = 17 | ||
MISSING_CONDITIONS = 18 | ||
ASSERTION_TOO_EARLY = 19 | ||
ASSERTION_EXPIRED = 20 | ||
WRONG_NUMBER_OF_AUTHSTATEMENTS = 21 | ||
NO_ATTRIBUTESTATEMENT = 22 | ||
ENCRYPTED_ATTRIBUTES = 23 | ||
WRONG_DESTINATION = 24 | ||
EMPTY_DESTINATION = 25 | ||
WRONG_AUDIENCE = 26 | ||
ISSUER_NOT_FOUND_IN_RESPONSE = 27 | ||
ISSUER_NOT_FOUND_IN_ASSERTION = 28 | ||
WRONG_ISSUER = 29 | ||
SESSION_EXPIRED = 30 | ||
WRONG_SUBJECTCONFIRMATION = 31 | ||
NO_SIGNED_RESPONSE = 32 | ||
NO_SIGNED_ASSERTION = 33 | ||
NO_SIGNATURE_FOUND = 34 | ||
KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA = 35 | ||
CHILDREN_NODE_NOT_FOIND_IN_KEYINFO = 36 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
UNSUPPORTED_RETRIEVAL_METHOD = 37 | ||
NO_NAMEID = 38 | ||
EMPTY_NAMEID = 39 | ||
SP_NAME_QUALIFIER_NAME_MISMATCH = 40 | ||
DUPLICATED_ATTRIBUTE_NAME_FOUND = 41 | ||
INVALID_SIGNATURE = 42 | ||
WRONG_NUMBER_OF_SIGNATURES = 43 | ||
RESPONSE_EXPIRED = 44 | ||
|
||
def __init__(self, message, code=0, errors=None): | ||
""" | ||
Initializes the Exception instance. | ||
Arguments are: | ||
* (str) message. Describes the error. | ||
* (int) code. The code error (defined in the error class). | ||
""" | ||
assert isinstance(code, int) | ||
|
||
if errors is not None: | ||
message = message % errors | ||
|
||
Exception.__init__(self, message) | ||
self.code = code |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ | |
""" | ||
|
||
from onelogin.saml2.constants import OneLogin_Saml2_Constants | ||
from onelogin.saml2.utils import OneLogin_Saml2_Utils | ||
from onelogin.saml2.utils import OneLogin_Saml2_Utils, OneLogin_Saml2_Error, OneLogin_Saml2_ValidationError | ||
from onelogin.saml2.xml_templates import OneLogin_Saml2_Templates | ||
from onelogin.saml2.xml_utils import OneLogin_Saml2_XML | ||
|
||
|
@@ -141,7 +141,10 @@ def get_nameid_data(request, key=None): | |
|
||
if len(encrypted_entries) == 1: | ||
if key is None: | ||
raise Exception('Key is required in order to decrypt the NameID') | ||
raise OneLogin_Saml2_Error( | ||
'Private Key is required in order to decrypt the NameID, check settings', | ||
OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND | ||
) | ||
|
||
encrypted_data_nodes = OneLogin_Saml2_XML.query(elem, '/samlp:LogoutRequest/saml:EncryptedID/xenc:EncryptedData') | ||
if len(encrypted_data_nodes) == 1: | ||
|
@@ -153,7 +156,10 @@ def get_nameid_data(request, key=None): | |
name_id = entries[0] | ||
|
||
if name_id is None: | ||
raise Exception('Not NameID found in the Logout Request') | ||
raise OneLogin_Saml2_ValidationError( | ||
'Not NameID found in the Logout Request', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok I will fix that message |
||
OneLogin_Saml2_ValidationError.NO_NAMEID | ||
) | ||
|
||
name_id_data = { | ||
'Value': name_id.text | ||
|
@@ -212,12 +218,15 @@ def get_session_indexes(request): | |
session_indexes.append(session_index_node.text) | ||
return session_indexes | ||
|
||
def is_valid(self, request_data): | ||
def is_valid(self, request_data, raise_exceptions=False): | ||
""" | ||
Checks if the Logout Request received is valid | ||
:param request_data: Request Data | ||
:type request_data: dict | ||
|
||
:param raise_exceptions: Whether to return false on failure or raise an exception | ||
:type raise_exceptions: Boolean | ||
|
||
:return: If the Logout Request is or not valid | ||
:rtype: boolean | ||
""" | ||
|
@@ -233,7 +242,10 @@ def is_valid(self, request_data): | |
if self.__settings.is_strict(): | ||
res = OneLogin_Saml2_XML.validate_xml(root, 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) | ||
if isinstance(res, str): | ||
raise Exception('Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd') | ||
raise OneLogin_Saml2_ValidationError( | ||
'Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd', | ||
OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT | ||
) | ||
|
||
security = self.__settings.get_security_data() | ||
|
||
|
@@ -243,37 +255,50 @@ def is_valid(self, request_data): | |
if root.get('NotOnOrAfter', None): | ||
na = OneLogin_Saml2_Utils.parse_SAML_to_time(root.get('NotOnOrAfter')) | ||
if na <= OneLogin_Saml2_Utils.now(): | ||
raise Exception('Timing issues (please check your clock settings)') | ||
raise OneLogin_Saml2_ValidationError( | ||
'Could not validate timestamp: expired. Check system clock.)', | ||
OneLogin_Saml2_ValidationError.RESPONSE_EXPIRED | ||
) | ||
|
||
# Check destination | ||
if root.get('Destination', None): | ||
destination = root.get('Destination') | ||
if destination != '': | ||
if current_url not in destination: | ||
raise Exception( | ||
raise OneLogin_Saml2_ValidationError( | ||
'The LogoutRequest was received at ' | ||
'%(currentURL)s instead of %(destination)s' % | ||
{ | ||
'currentURL': current_url, | ||
'destination': destination, | ||
} | ||
}, | ||
OneLogin_Saml2_ValidationError.WRONG_DESTINATION | ||
) | ||
|
||
# Check issuer | ||
issuer = OneLogin_Saml2_Logout_Request.get_issuer(root) | ||
if issuer is not None and issuer != idp_entity_id: | ||
raise Exception('Invalid issuer in the Logout Request') | ||
raise OneLogin_Saml2_ValidationError( | ||
'Invalid issuer in the Logout Request', | ||
OneLogin_Saml2_ValidationError.WRONG_ISSUER | ||
) | ||
|
||
if security['wantMessagesSigned']: | ||
if 'Signature' not in get_data: | ||
raise Exception('The Message of the Logout Request is not signed and the SP require it') | ||
raise OneLogin_Saml2_ValidationError( | ||
'The Message of the Logout Request is not signed and the SP require it', | ||
OneLogin_Saml2_ValidationError.NO_SIGNED_RESPONSE | ||
) | ||
|
||
return True | ||
except Exception as err: | ||
# pylint: disable=R0801 | ||
self.__error = str(err) | ||
debug = self.__settings.is_debug_active() | ||
if debug: | ||
print(err) | ||
if raise_exceptions: | ||
raise | ||
return False | ||
|
||
def get_error(self): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will break backward compatibility
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I will keep the old and mark as deprecated