def get_issuers(self): """ Gets the issuers (from message and from assertion) :returns: The issuers :rtype: list """ issuers = set() message_issuer_nodes = OneLogin_Saml2_XML.query( self.document, '/samlp:Response/saml:Issuer') if len(message_issuer_nodes) > 0: if len(message_issuer_nodes) == 1: issuer_value = OneLogin_Saml2_XML.element_text( message_issuer_nodes[0]) if issuer_value: issuers.add(issuer_value) else: raise OneLogin_Saml2_ValidationError( 'Issuer of the Response is multiple.', OneLogin_Saml2_ValidationError.ISSUER_MULTIPLE_IN_RESPONSE) assertion_issuer_nodes = self.__query_assertion('/saml:Issuer') if len(assertion_issuer_nodes) == 1: issuer_value = OneLogin_Saml2_XML.element_text( assertion_issuer_nodes[0]) if issuer_value: issuers.add(issuer_value) else: raise OneLogin_Saml2_ValidationError( 'Issuer of the Assertion not found or multiple.', OneLogin_Saml2_ValidationError.ISSUER_NOT_FOUND_IN_ASSERTION) return list(set(issuers))
def get_audiences(self): """ Gets the audiences :returns: The valid audiences for the SAML Response :rtype: list """ audience_nodes = self.__query_assertion( '/saml:Conditions/saml:AudienceRestriction/saml:Audience') return [ OneLogin_Saml2_XML.element_text(node) for node in audience_nodes if OneLogin_Saml2_XML.element_text(node) is not None ]
def get_nameid_data(self): """ Gets the NameID Data provided by the SAML Response from the IdP :returns: Name ID Data (Value, Format, NameQualifier, SPNameQualifier) :rtype: dict """ nameid = None nameid_data = {} encrypted_id_data_nodes = self.__query_assertion( '/saml:Subject/saml:EncryptedID/xenc:EncryptedData') if encrypted_id_data_nodes: encrypted_data = encrypted_id_data_nodes[0] key = self.__settings.get_sp_key() nameid = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key) else: nameid_nodes = self.__query_assertion('/saml:Subject/saml:NameID') if nameid_nodes: nameid = nameid_nodes[0] is_strict = self.__settings.is_strict() want_nameid = self.__settings.get_security_data().get( 'wantNameId', True) if nameid is None: if is_strict and want_nameid: raise OneLogin_Saml2_ValidationError( 'NameID not found in the assertion of the Response', OneLogin_Saml2_ValidationError.NO_NAMEID) else: if is_strict and want_nameid and not OneLogin_Saml2_XML.element_text( nameid): raise OneLogin_Saml2_ValidationError( 'An empty NameID value found', OneLogin_Saml2_ValidationError.EMPTY_NAMEID) nameid_data = {'Value': OneLogin_Saml2_XML.element_text(nameid)} for attr in ['Format', 'SPNameQualifier', 'NameQualifier']: value = nameid.get(attr, None) if value: if is_strict and attr == 'SPNameQualifier': sp_data = self.__settings.get_sp_data() sp_entity_id = sp_data.get('entityId', '') if sp_entity_id != value: raise OneLogin_Saml2_ValidationError( 'The SPNameQualifier value mistmatch the SP entityID value.', OneLogin_Saml2_ValidationError. SP_NAME_QUALIFIER_NAME_MISMATCH) nameid_data[attr] = value return nameid_data
def get_issuer(self): """ Gets the Issuer of the Logout Response Message :return: The Issuer :rtype: string """ issuer = None issuer_nodes = self.__query('/samlp:LogoutResponse/saml:Issuer') if len(issuer_nodes) == 1: issuer = OneLogin_Saml2_XML.element_text(issuer_nodes[0]) return issuer
def get_authn_contexts(self): """ Gets the authentication contexts :returns: The authentication classes for the SAML Response :rtype: list """ authn_context_nodes = self.__query_assertion( '/saml:AuthnStatement/saml:AuthnContext/saml:AuthnContextClassRef') return [ OneLogin_Saml2_XML.element_text(node) for node in authn_context_nodes ]
def get_issuer(request): """ Gets the Issuer of the Logout Request Message :param request: Logout Request Message :type request: string|DOMDocument :return: The Issuer :rtype: string """ elem = OneLogin_Saml2_XML.to_etree(request) issuer = None issuer_nodes = OneLogin_Saml2_XML.query( elem, '/samlp:LogoutRequest/saml:Issuer') if len(issuer_nodes) == 1: issuer = OneLogin_Saml2_XML.element_text(issuer_nodes[0]) return issuer
def get_session_indexes(request): """ Gets the SessionIndexes from the Logout Request :param request: Logout Request Message :type request: string|DOMDocument :return: The SessionIndex value :rtype: list """ elem = OneLogin_Saml2_XML.to_etree(request) session_indexes = [] session_index_nodes = OneLogin_Saml2_XML.query( elem, '/samlp:LogoutRequest/samlp:SessionIndex') for session_index_node in session_index_nodes: session_indexes.append( OneLogin_Saml2_XML.element_text(session_index_node)) return session_indexes
def get_nameid_data(request, key=None): """ Gets the NameID Data of the the Logout Request :param request: Logout Request Message :type request: string|DOMDocument :param key: The SP key :type key: string :return: Name ID Data (Value, Format, NameQualifier, SPNameQualifier) :rtype: dict """ elem = OneLogin_Saml2_XML.to_etree(request) name_id = None encrypted_entries = OneLogin_Saml2_XML.query( elem, '/samlp:LogoutRequest/saml:EncryptedID') if len(encrypted_entries) == 1: if key is None: 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: encrypted_data = encrypted_data_nodes[0] name_id = OneLogin_Saml2_Utils.decrypt_element( encrypted_data, key) else: entries = OneLogin_Saml2_XML.query( elem, '/samlp:LogoutRequest/saml:NameID') if len(entries) == 1: name_id = entries[0] if name_id is None: raise OneLogin_Saml2_ValidationError( 'NameID not found in the Logout Request', OneLogin_Saml2_ValidationError.NO_NAMEID) name_id_data = {'Value': OneLogin_Saml2_XML.element_text(name_id)} for attr in ['Format', 'SPNameQualifier', 'NameQualifier']: if attr in name_id.attrib: name_id_data[attr] = name_id.attrib[attr] return name_id_data
def get_status(dom): """ Gets Status from a Response. :param dom: The Response as XML :type: Document :returns: The Status, an array with the code and a message. :rtype: dict """ status = {} status_entry = OneLogin_Saml2_XML.query( dom, '/samlp:Response/samlp:Status') if len(status_entry) != 1: raise OneLogin_Saml2_ValidationError( 'Missing Status on response', OneLogin_Saml2_ValidationError.MISSING_STATUS) code_entry = OneLogin_Saml2_XML.query( dom, '/samlp:Response/samlp:Status/samlp:StatusCode', status_entry[0]) if len(code_entry) != 1: raise OneLogin_Saml2_ValidationError( 'Missing Status Code on response', OneLogin_Saml2_ValidationError.MISSING_STATUS_CODE) code = code_entry[0].values()[0] status['code'] = code status['msg'] = '' message_entry = OneLogin_Saml2_XML.query( dom, '/samlp:Response/samlp:Status/samlp:StatusMessage', status_entry[0]) if len(message_entry) == 0: subcode_entry = OneLogin_Saml2_XML.query( dom, '/samlp:Response/samlp:Status/samlp:StatusCode/samlp:StatusCode', status_entry[0]) if len(subcode_entry) == 1: status['msg'] = subcode_entry[0].values()[0] elif len(message_entry) == 1: status['msg'] = OneLogin_Saml2_XML.element_text(message_entry[0]) return status
def get_attributes(self): """ Gets the Attributes from the AttributeStatement element. EncryptedAttributes are not supported """ attributes = {} attribute_nodes = self.__query_assertion( '/saml:AttributeStatement/saml:Attribute') for attribute_node in attribute_nodes: attr_name = attribute_node.get('Name') if attr_name in attributes.keys(): raise OneLogin_Saml2_ValidationError( 'Found an Attribute element with duplicated Name', OneLogin_Saml2_ValidationError. DUPLICATED_ATTRIBUTE_NAME_FOUND) values = [] for attr in attribute_node.iterchildren( '{%s}AttributeValue' % OneLogin_Saml2_Constants.NSMAP['saml']): attr_text = OneLogin_Saml2_XML.element_text(attr) if attr_text: attr_text = attr_text.strip() if attr_text: values.append(attr_text) # Parse any nested NameID children for nameid in attr.iterchildren( '{%s}NameID' % OneLogin_Saml2_Constants.NSMAP['saml']): values.append({ 'NameID': { 'Format': nameid.get('Format'), 'NameQualifier': nameid.get('NameQualifier'), 'value': nameid.text } }) attributes[attr_name] = values return attributes
def parse(idp_metadata, required_sso_binding=OneLogin_Saml2_Constants. BINDING_HTTP_REDIRECT, required_slo_binding=OneLogin_Saml2_Constants. BINDING_HTTP_REDIRECT, entity_id=None): """ Parses the Identity Provider metadata and return a dict with extracted data. If there are multiple <IDPSSODescriptor> tags, parse only the first. Parses only those SSO endpoints with the same binding as given by the `required_sso_binding` parameter. Parses only those SLO endpoints with the same binding as given by the `required_slo_binding` parameter. If the metadata specifies multiple SSO endpoints with the required binding, extract only the first (the same holds true for SLO endpoints). :param idp_metadata: XML of the Identity Provider Metadata. :type idp_metadata: string :param required_sso_binding: Parse only POST or REDIRECT SSO endpoints. :type required_sso_binding: one of OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT or OneLogin_Saml2_Constants.BINDING_HTTP_POST :param required_slo_binding: Parse only POST or REDIRECT SLO endpoints. :type required_slo_binding: one of OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT or OneLogin_Saml2_Constants.BINDING_HTTP_POST :param entity_id: Specify the entity_id of the EntityDescriptor that you want to parse a XML that contains multiple EntityDescriptor. :type entity_id: string :returns: settings dict with extracted data :rtype: dict """ data = {} dom = OneLogin_Saml2_XML.to_etree(idp_metadata) idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None entity_desc_path = '//md:EntityDescriptor' if entity_id: entity_desc_path += "[@entityID='%s']" % entity_id entity_descriptor_nodes = OneLogin_Saml2_XML.query( dom, entity_desc_path) if len(entity_descriptor_nodes) > 0: entity_descriptor_node = entity_descriptor_nodes[0] idp_descriptor_nodes = OneLogin_Saml2_XML.query( entity_descriptor_node, './md:IDPSSODescriptor') if len(idp_descriptor_nodes) > 0: idp_descriptor_node = idp_descriptor_nodes[0] idp_entity_id = entity_descriptor_node.get('entityID', None) want_authn_requests_signed = entity_descriptor_node.get( 'WantAuthnRequestsSigned', None) name_id_format_nodes = OneLogin_Saml2_XML.query( idp_descriptor_node, './md:NameIDFormat') if len(name_id_format_nodes) > 0: idp_name_id_format = OneLogin_Saml2_XML.element_text( name_id_format_nodes[0]) sso_nodes = OneLogin_Saml2_XML.query( idp_descriptor_node, "./md:SingleSignOnService[@Binding='%s']" % required_sso_binding) if len(sso_nodes) > 0: idp_sso_url = sso_nodes[0].get('Location', None) slo_nodes = OneLogin_Saml2_XML.query( idp_descriptor_node, "./md:SingleLogoutService[@Binding='%s']" % required_slo_binding) if len(slo_nodes) > 0: idp_slo_url = slo_nodes[0].get('Location', None) signing_nodes = OneLogin_Saml2_XML.query( idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate" ) encryption_nodes = OneLogin_Saml2_XML.query( idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate" ) if len(signing_nodes) > 0 or len(encryption_nodes) > 0: certs = {} if len(signing_nodes) > 0: certs['signing'] = [] for cert_node in signing_nodes: certs['signing'].append(''.join( OneLogin_Saml2_XML.element_text( cert_node).split())) if len(encryption_nodes) > 0: certs['encryption'] = [] for cert_node in encryption_nodes: certs['encryption'].append(''.join( OneLogin_Saml2_XML.element_text( cert_node).split())) data['idp'] = {} if idp_entity_id is not None: data['idp']['entityId'] = idp_entity_id if idp_sso_url is not None: data['idp']['singleSignOnService'] = {} data['idp']['singleSignOnService']['url'] = idp_sso_url data['idp']['singleSignOnService'][ 'binding'] = required_sso_binding if idp_slo_url is not None: data['idp']['singleLogoutService'] = {} data['idp']['singleLogoutService']['url'] = idp_slo_url data['idp']['singleLogoutService'][ 'binding'] = required_slo_binding if want_authn_requests_signed is not None: data['security'] = {} data['security'][ 'authnRequestsSigned'] = want_authn_requests_signed if idp_name_id_format: data['sp'] = {} data['sp']['NameIDFormat'] = idp_name_id_format if certs is not None: if (len(certs) == 1 and (('signing' in certs and len(certs['signing']) == 1) or ('encryption' in certs and len(certs['encryption']) == 1))) or \ (('signing' in certs and len(certs['signing']) == 1) and ('encryption' in certs and len(certs['encryption']) == 1 and certs['signing'][0] == certs['encryption'][0])): if 'signing' in certs: data['idp']['x509cert'] = certs['signing'][0] else: data['idp']['x509cert'] = certs['encryption'][0] else: data['idp']['x509certMulti'] = certs return data
def validate_node_sign(signature_node, elem, cert=None, fingerprint=None, fingerprintalg='sha1', validatecert=False, debug=False): """ Validates a signature node. :param signature_node: The signature node :type: Node :param xml: The element we should validate :type: Document :param cert: The public cert :type: string :param fingerprint: The fingerprint of the public cert :type: string :param fingerprintalg: The algorithm used to build the fingerprint :type: string :param validatecert: If true, will verify the signature and if the cert is valid. :type: bool :param debug: Activate the xmlsec debug :type: bool :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean """ if (cert is None or cert == '') and fingerprint: x509_certificate_nodes = OneLogin_Saml2_XML.query( signature_node, '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate') if len(x509_certificate_nodes) > 0: x509_certificate_node = x509_certificate_nodes[0] x509_cert_value = OneLogin_Saml2_XML.element_text( x509_certificate_node) x509_cert_value_formatted = OneLogin_Saml2_Utils.format_cert( x509_cert_value) x509_fingerprint_value = OneLogin_Saml2_Utils.calculate_x509_fingerprint( x509_cert_value_formatted, fingerprintalg) if fingerprint == x509_fingerprint_value: cert = x509_cert_value_formatted if cert is None or cert == '': raise OneLogin_Saml2_Error( 'Could not validate node signature: No certificate provided.', OneLogin_Saml2_Error.CERT_NOT_FOUND) # Check if Reference URI is empty reference_elem = OneLogin_Saml2_XML.query(signature_node, '//ds:Reference') if len(reference_elem) > 0: if reference_elem[0].get('URI') == '': reference_elem[0].set( 'URI', '#%s' % signature_node.getparent().get('ID')) if validatecert: manager = xmlsec.KeysManager() manager.load_cert_from_memory(cert, xmlsec.KeyFormat.CERT_PEM, xmlsec.KeyDataType.TRUSTED) dsig_ctx = xmlsec.SignatureContext(manager) else: dsig_ctx = xmlsec.SignatureContext() dsig_ctx.key = xmlsec.Key.from_memory(cert, xmlsec.KeyFormat.CERT_PEM, None) dsig_ctx.set_enabled_key_data([xmlsec.KeyData.X509]) try: dsig_ctx.verify(signature_node) except Exception as err: raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected. %s', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE, str(err)) return True