def __decrypt_assertion(self, dom): """ Decrypts the Assertion :raises: Exception if no private key available :param dom: Encrypted Assertion :type dom: Element :returns: Decrypted Assertion :rtype: Element """ key = self.__settings.get_sp_key() if not key: raise Exception('No private key available, check settings') encrypted_assertion_nodes = OneLogin_Saml2_Utils.query( dom, '//saml:EncryptedAssertion') if encrypted_assertion_nodes: encrypted_data_nodes = OneLogin_Saml2_Utils.query( encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData') if encrypted_data_nodes: encrypted_data = encrypted_data_nodes[0] OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key) return dom
def __decrypt_assertion(self, dom): """ Decrypts the Assertion :raises: Exception if no private key available :param dom: Encrypted Assertion :type dom: Element :returns: Decrypted Assertion :rtype: Element """ key = self.__settings.get_sp_key() debug = self.__settings.is_debug_active() if not key: raise OneLogin_Saml2_Error( 'No private key available to decrypt the assertion, check settings', OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND ) encrypted_assertion_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/saml:EncryptedAssertion') if encrypted_assertion_nodes: encrypted_data_nodes = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData') if encrypted_data_nodes: keyinfo = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo') if not keyinfo: raise OneLogin_Saml2_ValidationError( 'No KeyInfo present, invalid Assertion', OneLogin_Saml2_ValidationError.KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA ) keyinfo = keyinfo[0] children = keyinfo.getchildren() if not children: raise OneLogin_Saml2_ValidationError( 'KeyInfo has no children nodes, invalid Assertion', OneLogin_Saml2_ValidationError.CHILDREN_NODE_NOT_FOUND_IN_KEYINFO ) for child in children: if 'RetrievalMethod' in child.tag: if child.attrib['Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey': raise OneLogin_Saml2_ValidationError( 'Unsupported Retrieval Method found', OneLogin_Saml2_ValidationError.UNSUPPORTED_RETRIEVAL_METHOD ) uri = child.attrib['URI'] if not uri.startswith('#'): break uri = uri.split('#')[1] encrypted_key = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id="' + uri + '"]') if encrypted_key: keyinfo.append(encrypted_key[0]) encrypted_data = encrypted_data_nodes[0] decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug=debug, inplace=True) dom.replace(encrypted_assertion_nodes[0], decrypted) return dom
def __decrypt_assertion(self, dom): """ Decrypts the Assertion :raises: Exception if no private key available :param dom: Encrypted Assertion :type dom: Element :returns: Decrypted Assertion :rtype: Element """ key = self.__settings.get_sp_key() debug = self.__settings.is_debug_active() if not key: raise OneLogin_Saml2_Error( 'No private key available to decrypt the assertion, check settings', OneLogin_Saml2_Error.PRIVATE_KEY_NOT_FOUND ) encrypted_assertion_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/saml:EncryptedAssertion') if encrypted_assertion_nodes: encrypted_data_nodes = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData') if encrypted_data_nodes: keyinfo = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo') if not keyinfo: raise OneLogin_Saml2_ValidationError( 'No KeyInfo present, invalid Assertion', OneLogin_Saml2_ValidationError.KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA ) keyinfo = keyinfo[0] children = keyinfo.getchildren() if not children: raise OneLogin_Saml2_ValidationError( 'KeyInfo has no children nodes, invalid Assertion', OneLogin_Saml2_ValidationError.CHILDREN_NODE_NOT_FOUND_IN_KEYINFO ) for child in children: if 'RetrievalMethod' in child.tag: if child.attrib['Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey': raise OneLogin_Saml2_ValidationError( 'Unsupported Retrieval Method found', OneLogin_Saml2_ValidationError.UNSUPPORTED_RETRIEVAL_METHOD ) uri = child.attrib['URI'] if not uri.startswith('#'): break uri = uri.split('#')[1] encrypted_key = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id=$tagid]', None, uri) if encrypted_key: keyinfo.append(encrypted_key[0]) encrypted_data = encrypted_data_nodes[0] decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug=debug, inplace=True) dom.replace(encrypted_assertion_nodes[0], decrypted) return dom
def validate_num_assertions(self): """ Verifies that the document only contains a single Assertion (encrypted or not) :returns: True if only 1 assertion encrypted or not :rtype: bool """ encrypted_assertion_nodes = OneLogin_Saml2_Utils.query(self.document, '//saml:EncryptedAssertion') assertion_nodes = OneLogin_Saml2_Utils.query(self.document, '//saml:Assertion') return (len(encrypted_assertion_nodes) + len(assertion_nodes)) == 1
def _parse_metadata_dom(self, metadata_dom): entity_descriptor_nodes = OneLogin_Saml2_Utils.query(metadata_dom, self.ENTITY_DESCRIPTOR_XPATH) sps = [] for entity_descriptor_node in entity_descriptor_nodes: sp_entity_id = entity_descriptor_node.get(self.ENTITY_ID_ATTRIBUTE, None) sp_descriptor_nodes = OneLogin_Saml2_Utils.query(entity_descriptor_node, self.SP_DESCRIPTOR_XPATH) for sp_descriptor_node in sp_descriptor_nodes: display_name_node = OneLogin_Saml2_Utils.query(sp_descriptor_node, self.DISPLAY_NAME_XPATH) if not display_name_node: continue display_name = display_name_node[0].text attribute_consuming_service_nodes = OneLogin_Saml2_Utils.query( sp_descriptor_node, self.ATTRIBUTE_CONSUMING_SERVICE_XPATH) attribute_consuming_services = [] for attribute_consuming_service_node in attribute_consuming_service_nodes: requested_attributes = [] requested_attribute_nodes = OneLogin_Saml2_Utils.query( attribute_consuming_service_node, self.REQUESTED_ATTRIBUTE_XPATH) for requested_attribute_node in requested_attribute_nodes: friendly_name = requested_attribute_node.get(self.FRIENDLY_NAME_ATTRIBUTE, '') name = requested_attribute_node.get(self.NAME_ATTRIBUTE, '') name_format = requested_attribute_node.get(self.NAME_FORMAT_ATTRIBUTE, '') is_required = requested_attribute_node.get(self.IS_REQUIRED_ATTRIBUTE, False) requested_attribute = RequestedAttribute( friendly_name, name, name_format, is_required ) requested_attributes.append(requested_attribute) attribute_consuming_service = AttributeConsumingService( requested_attributes ) attribute_consuming_services.append(attribute_consuming_service) idp = ServiceProvider( sp_entity_id, display_name, attribute_consuming_services ) sps.append(idp) return sps
def testQuery(self): """ Tests the query method of the OneLogin_Saml2_Utils """ xml = self.file_contents( join(self.data_path, 'responses', 'valid_response.xml.base64')) xml = b64decode(xml) dom = etree.fromstring(xml) assertion_nodes = OneLogin_Saml2_Utils.query( dom, '/samlp:Response/saml:Assertion') self.assertEqual(1, len(assertion_nodes)) assertion = assertion_nodes[0] self.assertIn('Assertion', assertion.tag) attribute_statement_nodes = OneLogin_Saml2_Utils.query( dom, '/samlp:Response/saml:Assertion/saml:AttributeStatement') self.assertEqual(1, len(assertion_nodes)) attribute_statement = attribute_statement_nodes[0] self.assertIn('AttributeStatement', attribute_statement.tag) attribute_statement_nodes_2 = OneLogin_Saml2_Utils.query( dom, './saml:AttributeStatement', assertion) self.assertEqual(1, len(attribute_statement_nodes_2)) attribute_statement_2 = attribute_statement_nodes_2[0] self.assertEqual(attribute_statement, attribute_statement_2) signature_res_nodes = OneLogin_Saml2_Utils.query( dom, '/samlp:Response/ds:Signature') self.assertEqual(1, len(signature_res_nodes)) signature_res = signature_res_nodes[0] self.assertIn('Signature', signature_res.tag) signature_nodes = OneLogin_Saml2_Utils.query( dom, '/samlp:Response/saml:Assertion/ds:Signature') self.assertEqual(1, len(signature_nodes)) signature = signature_nodes[0] self.assertIn('Signature', signature.tag) signature_nodes_2 = OneLogin_Saml2_Utils.query(dom, './ds:Signature', assertion) self.assertEqual(1, len(signature_nodes_2)) signature2 = signature_nodes_2[0] self.assertNotEqual(signature_res, signature2) self.assertEqual(signature, signature2) signature_nodes_3 = OneLogin_Saml2_Utils.query(dom, './ds:SignatureValue', assertion) self.assertEqual(0, len(signature_nodes_3)) signature_nodes_4 = OneLogin_Saml2_Utils.query( dom, './ds:Signature/ds:SignatureValue', assertion) self.assertEqual(1, len(signature_nodes_4)) signature_nodes_5 = OneLogin_Saml2_Utils.query(dom, './/ds:SignatureValue', assertion) self.assertEqual(1, len(signature_nodes_5))
def __decrypt_assertion(self, dom): """ Decrypts the Assertion :raises: Exception if no private key available :param dom: Encrypted Assertion :type dom: Element :returns: Decrypted Assertion :rtype: Element """ key = self.__settings.get_sp_key() debug = self.__settings.is_debug_active() if not key: raise Exception('No private key available, check settings') encrypted_assertion_nodes = OneLogin_Saml2_Utils.query( dom, '/samlp:Response/saml:EncryptedAssertion') if encrypted_assertion_nodes: encrypted_data_nodes = OneLogin_Saml2_Utils.query( encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData') if encrypted_data_nodes: keyinfo = OneLogin_Saml2_Utils.query( encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo') if not keyinfo: raise Exception('No KeyInfo present, invalid Assertion') keyinfo = keyinfo[0] children = keyinfo.getchildren() if not children: raise Exception('No child to KeyInfo, invalid Assertion') for child in children: if 'RetrievalMethod' in child.tag: if child.attrib[ 'Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey': raise Exception( 'Unsupported Retrieval Method found') uri = child.attrib['URI'] if not uri.startswith('#'): break uri = uri.split('#')[1] encrypted_key = OneLogin_Saml2_Utils.query( encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id="' + uri + '"]') if encrypted_key: keyinfo.append(encrypted_key[0]) encrypted_data = encrypted_data_nodes[0] decrypted = OneLogin_Saml2_Utils.decrypt_element( encrypted_data, key, debug) dom.replace(encrypted_assertion_nodes[0], decrypted) return dom
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 """ if isinstance(request, etree._Element): elem = request else: if isinstance(request, Document): request = request.toxml() elem = fromstring(request, forbid_dtd=True) name_id = None encrypted_entries = OneLogin_Saml2_Utils.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_Utils.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_Utils.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_Utils.element_text(name_id)} for attr in ['Format', 'SPNameQualifier', 'NameQualifier']: if attr in name_id.attrib.keys(): name_id_data[attr] = name_id.attrib[attr] return name_id_data
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 """ if isinstance(request, etree._Element): elem = request else: if isinstance(request, Document): request = request.toxml() elem = fromstring(request, forbid_dtd=True) name_id = None encrypted_entries = OneLogin_Saml2_Utils.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_Utils.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_Utils.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_Utils.element_text(name_id) } for attr in ['Format', 'SPNameQualifier', 'NameQualifier']: if attr in name_id.attrib.keys(): name_id_data[attr] = name_id.attrib[attr] return name_id_data
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 """ if isinstance(request, etree._Element): elem = request else: if isinstance(request, Document): request = request.toxml() elem = fromstring(request) name_id = None encrypted_entries = OneLogin_Saml2_Utils.query( elem, '/samlp:LogoutRequest/saml:EncryptedID') if len(encrypted_entries) == 1: if key is None: raise Exception( 'Key is required in order to decrypt the NameID') encrypted_data_nodes = OneLogin_Saml2_Utils.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_Utils.query( elem, '/samlp:LogoutRequest/saml:NameID') if len(entries) == 1: name_id = entries[0] if name_id is None: raise Exception('Not NameID found in the Logout Request') name_id_data = {'Value': name_id.text} for attr in ['Format', 'SPNameQualifier', 'NameQualifier']: if attr in name_id.attrib.keys(): name_id_data[attr] = name_id.attrib[attr] return name_id_data
def validate_signed_elements(self, signed_elements): """ Verifies that the document has the expected signed nodes. """ if len(signed_elements) > 2: return False response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML if (response_tag in signed_elements and signed_elements.count(response_tag) > 1) or \ (assertion_tag in signed_elements and signed_elements.count(assertion_tag) > 1) or \ (response_tag not in signed_elements and assertion_tag not in signed_elements): return False # Check that the signed elements found here, are the ones that will be verified # by OneLogin_Saml2_Utils.validate_sign if response_tag in signed_elements: expected_signature_nodes = OneLogin_Saml2_Utils.query(self.document, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH) if len(expected_signature_nodes) != 1: raise Exception('Unexpected number of Response signatures found. SAML Response rejected.') if assertion_tag in signed_elements: expected_signature_nodes = self.__query(OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH) if len(expected_signature_nodes) != 1: raise Exception('Unexpected number of Assertion signatures found. SAML Response rejected.') return True
def get_issuers(self): """ Gets the issuers (from message and from assertion) :returns: The issuers :rtype: list """ issuers = [] message_issuer_nodes = OneLogin_Saml2_Utils.query(self.document, '/samlp:Response/saml:Issuer') if len(message_issuer_nodes) > 0: if len(message_issuer_nodes) == 1: issuers.append(OneLogin_Saml2_Utils.element_text(message_issuer_nodes[0])) 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: issuers.append(OneLogin_Saml2_Utils.element_text(assertion_issuer_nodes[0])) 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_issuers(self): """ Gets the issuers (from message and from assertion) :returns: The issuers :rtype: list """ issuers = [] message_issuer_nodes = OneLogin_Saml2_Utils.query( self.document, '/samlp:Response/saml:Issuer') if len(message_issuer_nodes) > 0: if len(message_issuer_nodes) == 1: issuers.append( OneLogin_Saml2_Utils.element_text(message_issuer_nodes[0])) 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: issuers.append( OneLogin_Saml2_Utils.element_text(assertion_issuer_nodes[0])) 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_metadata(url): """ Get the metadata XML from the provided URL :param url: Url where the XML of the Identity Provider Metadata is published. :type url: string :returns: metadata XML :rtype: string """ valid = False response = urllib2.urlopen(url) xml = response.read() if xml: try: dom = fromstring(xml) idp_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, '//md:IDPSSODescriptor') if idp_descriptor_nodes: valid = True except: pass if not valid: raise Exception('Not valid IdP XML found from URL: %s' % (url)) return xml
def validate_signed_elements(self, signed_elements): """ Verifies that the document has the expected signed nodes. """ if len(signed_elements) > 2: return False response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML if (response_tag in signed_elements and signed_elements.count(response_tag) > 1) or \ (assertion_tag in signed_elements and signed_elements.count(assertion_tag) > 1) or \ (response_tag not in signed_elements and assertion_tag not in signed_elements): return False # Check that the signed elements found here, are the ones that will be verified # by OneLogin_Saml2_Utils.validate_sign if response_tag in signed_elements: expected_signature_nodes = OneLogin_Saml2_Utils.query( self.document, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH) if len(expected_signature_nodes) != 1: raise Exception( 'Unexpected number of Response signatures found. SAML Response rejected.' ) if assertion_tag in signed_elements: expected_signature_nodes = self.__query( OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH) if len(expected_signature_nodes) != 1: raise Exception( 'Unexpected number of Assertion signatures found. SAML Response rejected.' ) return True
def parse(self, xml_metadata): """Parses an XML string containing SAML metadata and translates it into a list of IdentityProviderMetadata/ServiceProviderMetadata objects :param xml_metadata: XML string containing SAML metadata :type xml_metadata: string :return: List of IdentityProviderMetadata/ServiceProviderMetadata objects :rtype: List[ProviderMetadata] :raise: MetadataParsingError """ self._logger.info( 'Started parsing an XML string containing SAML metadata') metadata_dom = self._convert_xml_string_to_dom(xml_metadata) providers = [] try: entity_descriptor_nodes = OneLogin_Saml2_Utils.query( metadata_dom, '//md:EntityDescriptor') for entity_descriptor_node in entity_descriptor_nodes: idp_descriptor_nodes = OneLogin_Saml2_Utils.query( entity_descriptor_node, './md:IDPSSODescriptor') idps = self._parse_providers(entity_descriptor_node, idp_descriptor_nodes, self._parse_idp_metadata) providers += idps sp_descriptor_nodes = OneLogin_Saml2_Utils.query( entity_descriptor_node, './md:SPSSODescriptor') sps = self._parse_providers(entity_descriptor_node, sp_descriptor_nodes, self._parse_sp_metadata) providers += sps except XMLSyntaxError as exception: self._logger.exception( 'An unexpected error occurred during parsing an XML string containing SAML metadata' ) raise SAMLMetadataParsingError(inner_exception=exception) self._logger.info( 'Finished parsing an XML string containing SAML metadata') return providers
def process_signed_elements(self): """ Verifies the signature nodes: - Checks that are Response or Assertion - Check that IDs and reference URI are unique and consistent. :returns: The signed elements tag names :rtype: list """ sign_nodes = self.__query('//ds:Signature') signed_elements = [] verified_seis = [] verified_ids = [] response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML for sign_node in sign_nodes: signed_element = sign_node.getparent().tag if signed_element != response_tag and signed_element != assertion_tag: raise Exception( 'Invalid Signature Element %s SAML Response rejected' % signed_element) if not sign_node.getparent().get('ID'): raise Exception( 'Signed Element must contain an ID. SAML Response rejected' ) id_value = sign_node.getparent().get('ID') if id_value in verified_ids: raise Exception('Duplicated ID. SAML Response rejected') verified_ids.append(id_value) # Check that reference URI matches the parent ID and no duplicate References or IDs ref = OneLogin_Saml2_Utils.query(sign_node, './/ds:Reference') if ref: ref = ref[0] if ref.get('URI'): sei = ref.get('URI')[1:] if sei != id_value: raise Exception( 'Found an invalid Signed Element. SAML Response rejected' ) if sei in verified_seis: raise Exception( 'Duplicated Reference URI. SAML Response rejected') verified_seis.append(sei) signed_elements.append(signed_element) if signed_elements: if not self.validate_signed_elements(signed_elements): raise Exception( 'Found an unexpected Signature Element. SAML Response rejected' ) return signed_elements
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 """ if isinstance(request, etree._Element): elem = request else: if isinstance(request, Document): request = request.toxml() elem = fromstring(request) name_id = None encrypted_entries = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/saml:EncryptedID') if len(encrypted_entries) == 1: if key is None: raise Exception('Key is required in order to decrypt the NameID') encrypted_data_nodes = OneLogin_Saml2_Utils.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_Utils.query(elem, '/samlp:LogoutRequest/saml:NameID') if len(entries) == 1: name_id = entries[0] if name_id is None: raise Exception('Not NameID found in the Logout Request') name_id_data = { 'Value': name_id.text } for attr in ['Format', 'SPNameQualifier', 'NameQualifier']: if attr in name_id.attrib.keys(): name_id_data[attr] = name_id.attrib[attr] return name_id_data
def __decrypt_assertion(self, dom): """ Decrypts the Assertion :raises: Exception if no private key available :param dom: Encrypted Assertion :type dom: Element :returns: Decrypted Assertion :rtype: Element """ key = self.__settings.get_sp_key() debug = self.__settings.is_debug_active() if not key: raise Exception('No private key available, check settings') encrypted_assertion_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/saml:EncryptedAssertion') if encrypted_assertion_nodes: encrypted_data_nodes = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData') if encrypted_data_nodes: keyinfo = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData/ds:KeyInfo') if not keyinfo: raise Exception('No KeyInfo present, invalid Assertion') keyinfo = keyinfo[0] children = keyinfo.getchildren() if not children: raise Exception('No child to KeyInfo, invalid Assertion') for child in children: if 'RetrievalMethod' in child.tag: if child.attrib['Type'] != 'http://www.w3.org/2001/04/xmlenc#EncryptedKey': raise Exception('Unsupported Retrieval Method found') uri = child.attrib['URI'] if not uri.startswith('#'): break uri = uri.split('#')[1] encrypted_key = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], './xenc:EncryptedKey[@Id="' + uri + '"]') if encrypted_key: keyinfo.append(encrypted_key[0]) encrypted_data = encrypted_data_nodes[0] decrypted = OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key, debug) dom.replace(encrypted_assertion_nodes[0], decrypted) return dom
def testQuery(self): """ Tests the query method of the OneLogin_Saml2_Utils """ xml = self.file_contents(join(self.data_path, 'responses', 'valid_response.xml.base64')) xml = b64decode(xml) dom = etree.fromstring(xml) assertion_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/saml:Assertion') self.assertEqual(1, len(assertion_nodes)) assertion = assertion_nodes[0] self.assertIn('Assertion', assertion.tag) attribute_statement_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/saml:Assertion/saml:AttributeStatement') self.assertEqual(1, len(assertion_nodes)) attribute_statement = attribute_statement_nodes[0] self.assertIn('AttributeStatement', attribute_statement.tag) attribute_statement_nodes_2 = OneLogin_Saml2_Utils.query(dom, './saml:AttributeStatement', assertion) self.assertEqual(1, len(attribute_statement_nodes_2)) attribute_statement_2 = attribute_statement_nodes_2[0] self.assertEqual(attribute_statement, attribute_statement_2) signature_res_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/ds:Signature') self.assertEqual(1, len(signature_res_nodes)) signature_res = signature_res_nodes[0] self.assertIn('Signature', signature_res.tag) signature_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:Response/saml:Assertion/ds:Signature') self.assertEqual(1, len(signature_nodes)) signature = signature_nodes[0] self.assertIn('Signature', signature.tag) signature_nodes_2 = OneLogin_Saml2_Utils.query(dom, './ds:Signature', assertion) self.assertEqual(1, len(signature_nodes_2)) signature2 = signature_nodes_2[0] self.assertNotEqual(signature_res, signature2) self.assertEqual(signature, signature2) signature_nodes_3 = OneLogin_Saml2_Utils.query(dom, './ds:SignatureValue', assertion) self.assertEqual(0, len(signature_nodes_3)) signature_nodes_4 = OneLogin_Saml2_Utils.query(dom, './ds:Signature/ds:SignatureValue', assertion) self.assertEqual(1, len(signature_nodes_4)) signature_nodes_5 = OneLogin_Saml2_Utils.query(dom, './/ds:SignatureValue', assertion) self.assertEqual(1, len(signature_nodes_5))
def __query(self, query): """ Extracts a node from the DOMDocument (Logout Response Menssage) :param query: Xpath Expresion :type query: string :return: The queried node :rtype: DOMNodeList """ # Switch to lxml for querying xml = self.document.toxml() return OneLogin_Saml2_Utils.query(fromstring(xml), query)
def _parse_metadata_dom(self, metadata_dom): entity_descriptor_nodes = OneLogin_Saml2_Utils.query(metadata_dom, self.ENTITY_DESCRIPTOR_XPATH) idps = [] for entity_descriptor_node in entity_descriptor_nodes: idp_descriptor_nodes = OneLogin_Saml2_Utils.query(entity_descriptor_node, self.IDP_DESCRIPTOR_XPATH) for idp_descriptor_node in idp_descriptor_nodes: idp_entity_id = entity_descriptor_node.get(self.ENTITY_ID_ATTRIBUTE, None) display_name_node = OneLogin_Saml2_Utils.query(idp_descriptor_node, self.DISPLAY_NAME_XPATH) if not display_name_node: continue display_name = display_name_node[0].text idp = IdentityProviderMetadata(idp_entity_id, display_name, entity_descriptor_node) idps.append(idp) return idps
def __decrypt_assertion(self, dom): """ Decrypts the Assertion :raises: Exception if no private key available :param dom: Encrypted Assertion :type dom: Element :returns: Decrypted Assertion :rtype: Element """ key = self.__settings.get_sp_key() if not key: raise Exception('No private key available, check settings') encrypted_assertion_nodes = OneLogin_Saml2_Utils.query(dom, '//saml:EncryptedAssertion') if encrypted_assertion_nodes: encrypted_data_nodes = OneLogin_Saml2_Utils.query(encrypted_assertion_nodes[0], '//saml:EncryptedAssertion/xenc:EncryptedData') if encrypted_data_nodes: encrypted_data = encrypted_data_nodes[0] OneLogin_Saml2_Utils.decrypt_element(encrypted_data, key) return dom
def test_start_authentication(self, name, service_provider, identity_providers): configuration = create_autospec(spec=SAMLConfiguration) configuration.get_debug = MagicMock(return_value=False) configuration.get_strict = MagicMock(return_value=False) configuration.get_service_provider = MagicMock( return_value=service_provider) configuration.get_identity_providers = MagicMock( return_value=identity_providers) onelogin_configuration = SAMLOneLoginConfiguration(configuration) authentication_manager = SAMLAuthenticationManager( onelogin_configuration, SAMLSubjectParser()) with self.app.test_request_context('/'): result = authentication_manager.start_authentication( self._db, fixtures.IDP_1_ENTITY_ID, '') query_items = urlparse.parse_qs(urlparse.urlsplit(result).query) saml_request = query_items['SAMLRequest'][0] decoded_saml_request = OneLogin_Saml2_Utils.decode_base64_and_inflate( saml_request) validation_result = OneLogin_Saml2_Utils.validate_xml( decoded_saml_request, 'saml-schema-protocol-2.0.xsd', False) assert isinstance(validation_result, Document) saml_request_dom = fromstring(decoded_saml_request) acs_url = saml_request_dom.get('AssertionConsumerServiceURL') eq_(acs_url, SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.acs_service.url) acs_binding = saml_request_dom.get('ProtocolBinding') eq_( acs_binding, SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS. acs_service.binding.value) sso_url = saml_request_dom.get('Destination') eq_(sso_url, IDENTITY_PROVIDERS[0].sso_service.url) name_id_policy_nodes = OneLogin_Saml2_Utils.query( saml_request_dom, './samlp:NameIDPolicy') assert name_id_policy_nodes is not None eq_(len(name_id_policy_nodes), 1) name_id_policy_node = name_id_policy_nodes[0] name_id_format = name_id_policy_node.get('Format') eq_(name_id_format, SERVICE_PROVIDER_WITH_UNSIGNED_REQUESTS.name_id_format)
def process_signed_elements(self): """ Verifies the signature nodes: - Checks that are Response or Assertion - Check that IDs and reference URI are unique and consistent. :returns: The signed elements tag names :rtype: list """ sign_nodes = self.__query('//ds:Signature') signed_elements = [] verified_seis = [] verified_ids = [] response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML for sign_node in sign_nodes: signed_element = sign_node.getparent().tag if signed_element != response_tag and signed_element != assertion_tag: raise Exception('Invalid Signature Element %s SAML Response rejected' % signed_element) if not sign_node.getparent().get('ID'): raise Exception('Signed Element must contain an ID. SAML Response rejected') id_value = sign_node.getparent().get('ID') if id_value in verified_ids: raise Exception('Duplicated ID. SAML Response rejected') verified_ids.append(id_value) # Check that reference URI matches the parent ID and no duplicate References or IDs ref = OneLogin_Saml2_Utils.query(sign_node, './/ds:Reference') if ref: ref = ref[0] if ref.get('URI'): sei = ref.get('URI')[1:] if sei != id_value: raise Exception('Found an invalid Signed Element. SAML Response rejected') if sei in verified_seis: raise Exception('Duplicated Reference URI. SAML Response rejected') verified_seis.append(sei) signed_elements.append(signed_element) if signed_elements: if not self.validate_signed_elements(signed_elements): raise Exception('Found an unexpected Signature Element. SAML Response rejected') return signed_elements
def __query(self, query): """ Extracts nodes that match the query from the Response :param query: Xpath Expresion :type query: String :returns: The queried nodes :rtype: list """ if self.encrypted: document = self.decrypted_document else: document = self.document return OneLogin_Saml2_Utils.query(document, query)
def _parse_localizable_metadata_items(self, provider_descriptor_node, xpath, required=False): """Parses IDPSSODescriptor/SPSSODescriptor's mdui:UIInfo child elements (for example, mdui:DisplayName) :param provider_descriptor_node: Parent IDPSSODescriptor/SPSSODescriptor XML node :type provider_descriptor_node: defusedxml.lxml.RestrictedElement :param xpath: XPath expression for a particular md:localizedNameType child element (for example, mdui:DisplayName) :type xpath: string :param required: Boolean value indicating whether particular md:localizedNameType child element is required or not :type required: bool :return: List of md:localizedNameType child elements :rtype: Optional[List[LocalizableMetadataItem]] :raise: MetadataParsingError """ localizable_metadata_nodes = OneLogin_Saml2_Utils.query( provider_descriptor_node, xpath) if not localizable_metadata_nodes and required: last_slash_index = xpath.rfind('/') localizable_metadata_tag_name = xpath[last_slash_index + 1:] raise SAMLMetadataParsingError( _('{0} tag is missing'.format(localizable_metadata_tag_name))) localizable_items = None if localizable_metadata_nodes: localizable_items = [] for localizable_metadata_node in localizable_metadata_nodes: localizable_item_text = localizable_metadata_node.text localizable_item_language = localizable_metadata_node.get( '{http://www.w3.org/XML/1998/namespace}lang', None) localizable_item = LocalizableMetadataItem( localizable_item_text, localizable_item_language) localizable_items.append(localizable_item) return localizable_items
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 """ if isinstance(request, Document): request = request.toxml() dom = fromstring(request) session_indexes = [] session_index_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:LogoutRequest/samlp:SessionIndex') for session_index_node in session_index_nodes: session_indexes.append(session_index_node.text) return session_indexes
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 """ if isinstance(request, Document): request = request.toxml() dom = fromstring(request) issuer = None issuer_nodes = OneLogin_Saml2_Utils.query(dom, '/samlp:LogoutRequest/saml:Issuer') if len(issuer_nodes) == 1: issuer = issuer_nodes[0].text return issuer
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 """ if isinstance(request, Document): request = request.toxml() dom = fromstring(request) issuer = None issuer_nodes = OneLogin_Saml2_Utils.query( dom, '/samlp:LogoutRequest/saml:Issuer') if len(issuer_nodes) == 1: issuer = issuer_nodes[0].text 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 """ if isinstance(request, Document): request = request.toxml() dom = fromstring(request) session_indexes = [] session_index_nodes = OneLogin_Saml2_Utils.query( dom, '/samlp:LogoutRequest/samlp:SessionIndex') for session_index_node in session_index_nodes: session_indexes.append(session_index_node.text) return session_indexes
def _parse_name_id_format(self, provider_node): """Parses a name ID format NOTE: OneLogin's python-saml library used for implementing SAML authentication support only one name ID format. If there are multiple name ID formats specified in the XML metadata, we select the first one. :param provider_node: Parent IDPSSODescriptor/SPSSODescriptor node :type provider_node: defusedxml.lxml.RestrictedElement :return: Name ID format :rtype: string """ name_id_format = NameIDFormat.UNSPECIFIED.value name_id_format_nodes = OneLogin_Saml2_Utils.query(provider_node, './ md:NameIDFormat') if len(name_id_format_nodes) > 0: # OneLogin's python-saml supports only one name ID format so we select the first one name_id_format = OneLogin_Saml2_Utils.element_text(name_id_format_nodes[0]) return name_id_format
def validate_signed_elements(self, signed_elements): """ Verifies that the document has the expected signed nodes. :param signed_elements: The signed elements to be checked :type signed_elements: list :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean """ if len(signed_elements) > 2: return False response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML if (response_tag in signed_elements and signed_elements.count(response_tag) > 1) or \ (assertion_tag in signed_elements and signed_elements.count(assertion_tag) > 1) or \ (response_tag not in signed_elements and assertion_tag not in signed_elements): return False # Check that the signed elements found here, are the ones that will be verified # by OneLogin_Saml2_Utils.validate_sign if response_tag in signed_elements: expected_signature_nodes = OneLogin_Saml2_Utils.query( self.document, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH) if len(expected_signature_nodes) != 1: raise OneLogin_Saml2_ValidationError( 'Unexpected number of Response signatures found. SAML Response rejected.', OneLogin_Saml2_ValidationError. WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE) if assertion_tag in signed_elements: expected_signature_nodes = self.__query( OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH) if len(expected_signature_nodes) != 1: raise OneLogin_Saml2_ValidationError( 'Unexpected number of Assertion signatures found. SAML Response rejected.', OneLogin_Saml2_ValidationError. WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION) return True
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 """ if isinstance(request, etree._Element): elem = request else: if isinstance(request, Document): request = request.toxml() elem = fromstring(request, forbid_dtd=True) issuer = None issuer_nodes = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/saml:Issuer') if len(issuer_nodes) == 1: issuer = OneLogin_Saml2_Utils.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 """ if isinstance(request, etree._Element): elem = request else: if isinstance(request, Document): request = request.toxml() elem = fromstring(request, forbid_dtd=True) session_indexes = [] session_index_nodes = OneLogin_Saml2_Utils.query(elem, '/samlp:LogoutRequest/samlp:SessionIndex') for session_index_node in session_index_nodes: session_indexes.append(OneLogin_Saml2_Utils.element_text(session_index_node)) return session_indexes
def load_metadata(self, url, validate_cert=True): """ Gets the metadata XML from the provided URL :param url: Url where the XML of the Identity Provider Metadata is published. :type url: string :param validate_cert: If the url uses https schema, that flag enables or not the verification of the associated certificate. :type validate_cert: bool :returns: metadata XML :rtype: string """ self._logger.info('Start loading metadata from {0}'.format(self.IN_COMMON_METADATA_SERVICE_URL)) valid = False if validate_cert: response = urllib2.urlopen(url) else: ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE response = urllib2.urlopen(url, context=ctx) xml = response.read() if xml: try: dom = fromstring(xml, forbid_dtd=True) sp_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, '//md:SPSSODescriptor') if sp_descriptor_nodes: valid = True except Exception: pass if not valid: raise Exception('Not valid IdP XML found from URL: %s' % url) self._logger.info('Finished loading metadata from {0}'.format(self.IN_COMMON_METADATA_SERVICE_URL)) return xml
def validate_signed_elements(self, signed_elements): """ Verifies that the document has the expected signed nodes. :param signed_elements: The signed elements to be checked :type signed_elements: list :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean """ if len(signed_elements) > 2: return False response_tag = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP assertion_tag = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML if (response_tag in signed_elements and signed_elements.count(response_tag) > 1) or \ (assertion_tag in signed_elements and signed_elements.count(assertion_tag) > 1) or \ (response_tag not in signed_elements and assertion_tag not in signed_elements): return False # Check that the signed elements found here, are the ones that will be verified # by OneLogin_Saml2_Utils.validate_sign if response_tag in signed_elements: expected_signature_nodes = OneLogin_Saml2_Utils.query(self.document, OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH) if len(expected_signature_nodes) != 1: raise OneLogin_Saml2_ValidationError( 'Unexpected number of Response signatures found. SAML Response rejected.', OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE ) if assertion_tag in signed_elements: expected_signature_nodes = self.__query(OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH) if len(expected_signature_nodes) != 1: raise OneLogin_Saml2_ValidationError( 'Unexpected number of Assertion signatures found. SAML Response rejected.', OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION ) return True
def get_issuers(self): """ Gets the issuers (from message and from assertion) :returns: The issuers :rtype: list """ issuers = [] message_issuer_nodes = OneLogin_Saml2_Utils.query(self.document, '/samlp:Response/saml:Issuer') if len(message_issuer_nodes) == 1: issuers.append(message_issuer_nodes[0].text) else: raise Exception('Issuer of the Response not found or multiple.') assertion_issuer_nodes = self.__query_assertion('/saml:Issuer') if len(assertion_issuer_nodes) == 1: issuers.append(assertion_issuer_nodes[0].text) else: raise Exception('Issuer of the Assertion not found or multiple.') return list(set(issuers))
def get_metadata(url, validate_cert=True): """ Gets the metadata XML from the provided URL :param url: Url where the XML of the Identity Provider Metadata is published. :type url: string :param validate_cert: If the url uses https schema, that flag enables or not the verification of the associated certificate. :type validate_cert: bool :returns: metadata XML :rtype: string """ valid = False if validate_cert: response = urllib2.urlopen(url) else: ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE response = urllib2.urlopen(url, context=ctx) xml = response.read() if xml: try: dom = fromstring(xml, forbid_dtd=True) idp_descriptor_nodes = OneLogin_Saml2_Utils.query( dom, '//md:IDPSSODescriptor') if idp_descriptor_nodes: valid = True except Exception: pass if not valid: raise Exception('Not valid IdP XML found from URL: %s' % (url)) return xml
def get_metadata(url, validate_cert=True): """ Gets the metadata XML from the provided URL :param url: Url where the XML of the Identity Provider Metadata is published. :type url: string :param validate_cert: If the url uses https schema, that flag enables or not the verification of the associated certificate. :type validate_cert: bool :returns: metadata XML :rtype: string """ valid = False if validate_cert: response = urllib2.urlopen(url) else: ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE response = urllib2.urlopen(url, context=ctx) xml = response.read() if xml: try: dom = fromstring(xml, forbid_dtd=True) idp_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, '//md:IDPSSODescriptor') if idp_descriptor_nodes: valid = True except Exception: pass if not valid: raise Exception('Not valid IdP XML found from URL: %s' % (url)) return xml
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): """ Parse the Identity Provider metadata and return a dict with extracted data. If there are multiple <IDPSSODescriptor> tags, parse only the first. Parse only those SSO endpoints with the same binding as given by the `required_sso_binding` parameter. Parse 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 = fromstring(idp_metadata) entity_desc_path = '//md:EntityDescriptor' if entity_id: entity_desc_path += "[@entityID='%s']" % entity_id entity_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, entity_desc_path) idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None if len(entity_descriptor_nodes) > 0: entity_descriptor_node = entity_descriptor_nodes[0] idp_descriptor_nodes = OneLogin_Saml2_Utils.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_Utils.query(idp_descriptor_node, './md:NameIDFormat') if len(name_id_format_nodes) > 0: idp_name_id_format = OneLogin_Saml2_Utils.element_text(name_id_format_nodes[0]) sso_nodes = OneLogin_Saml2_Utils.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_Utils.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_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate") encryption_nodes = OneLogin_Saml2_Utils.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_Utils.element_text(cert_node).split())) if len(encryption_nodes) > 0: certs['encryption'] = [] for cert_node in encryption_nodes: certs['encryption'].append(''.join(OneLogin_Saml2_Utils.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 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 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 return data
def _parse(self, dom, required_sso_binding=OneLogin_Saml2_Constants. BINDING_HTTP_REDIRECT, required_slo_binding=OneLogin_Saml2_Constants. BINDING_HTTP_REDIRECT, entity_id=None): data = {} idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None entity_descriptor_node = dom idp_descriptor_nodes = OneLogin_Saml2_Utils.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_Utils.query( idp_descriptor_node, './md:NameIDFormat') if len(name_id_format_nodes) > 0: idp_name_id_format = OneLogin_Saml2_Utils.element_text( name_id_format_nodes[0]) sso_nodes = OneLogin_Saml2_Utils.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_Utils.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_Utils.query( idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate" ) encryption_nodes = OneLogin_Saml2_Utils.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_Utils.element_text( cert_node).split())) if len(encryption_nodes) > 0: certs['encryption'] = [] for cert_node in encryption_nodes: certs['encryption'].append(''.join( OneLogin_Saml2_Utils.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 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 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 return data
def parse( idp_metadata, required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT, required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT): """ Parse the Identity Provider metadata and return a dict with extracted data. If there are multiple <IDPSSODescriptor> tags, parse only the first. Parse only those SSO endpoints with the same binding as given by the `required_sso_binding` parameter. Parse 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 :returns: settings dict with extracted data :rtype: dict """ data = {} dom = fromstring(idp_metadata) entity_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, '//md:EntityDescriptor') idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = idp_x509_cert = None if len(entity_descriptor_nodes) > 0: for entity_descriptor_node in entity_descriptor_nodes: idp_descriptor_nodes = OneLogin_Saml2_Utils.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_Utils.query(idp_descriptor_node, './md:NameIDFormat') if len(name_id_format_nodes) > 0: idp_name_id_format = name_id_format_nodes[0].text sso_nodes = OneLogin_Saml2_Utils.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_Utils.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) cert_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate") if len(cert_nodes) > 0: idp_x509_cert = cert_nodes[0].text 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 idp_x509_cert is not None: data['idp']['x509cert'] = idp_x509_cert 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 break return data
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): """ Parse the Identity Provider metadata and return a dict with extracted data. If there are multiple <IDPSSODescriptor> tags, parse only the first. Parse only those SSO endpoints with the same binding as given by the `required_sso_binding` parameter. Parse 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 = fromstring(idp_metadata, forbid_dtd=True) entity_desc_path = '//md:EntityDescriptor' if entity_id: entity_desc_path += "[@entityID='%s']" % entity_id entity_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, entity_desc_path) idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = certs = None if len(entity_descriptor_nodes) > 0: entity_descriptor_node = entity_descriptor_nodes[0] idp_descriptor_nodes = OneLogin_Saml2_Utils.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_Utils.query(idp_descriptor_node, './md:NameIDFormat') if len(name_id_format_nodes) > 0: idp_name_id_format = OneLogin_Saml2_Utils.element_text(name_id_format_nodes[0]) sso_nodes = OneLogin_Saml2_Utils.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_Utils.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_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate") encryption_nodes = OneLogin_Saml2_Utils.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_Utils.element_text(cert_node).split())) if len(encryption_nodes) > 0: certs['encryption'] = [] for cert_node in encryption_nodes: certs['encryption'].append(''.join(OneLogin_Saml2_Utils.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 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 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 return data
def _parse_sp_metadata( self, provider_node, entity_id, ui_info, organization, required_acs_binding=Binding.HTTP_POST): """Parses SPSSODescriptor node and translates it into a ServiceProvider object :param provider_node: SPSSODescriptor node containing SP metadata :param provider_node: defusedxml.lxml.RestrictedElement :param entity_id: String containing IdP's entityID :type entity_id: string :param ui_info: UIInfo object containing IdP's description :type ui_info: UIInfo :param organization: Organization object containing basic information about an organization responsible for a SAML entity or role :type organization: Organization :param required_acs_binding: Required binding for Assertion Consumer Service (HTTP-Redirect by default) :type required_acs_binding: Binding :return: ServiceProvider containing SP metadata :rtype: ServiceProvider :raise: MetadataParsingError """ authn_requests_signed = provider_node.get('AuthnRequestsSigned', False) want_assertions_signed = provider_node.get('WantAssertionsSigned', False) name_id_format = self._parse_name_id_format(provider_node) acs_service = None acs_service_nodes = OneLogin_Saml2_Utils.query( provider_node, "./md:AssertionConsumerService[@Binding='%s']" % required_acs_binding.value ) if len(acs_service_nodes) > 0: acs_service_node = self._select_default_or_first_indexed_element(acs_service_nodes) acs_url = acs_service_node.get('Location', None) acs_service = Service(acs_url, required_acs_binding) else: raise SAMLMetadataParsingError(_('Missing {0} AssertionConsumerService'.format(required_acs_binding.value))) certificate_nodes = OneLogin_Saml2_Utils.query( provider_node, './md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate') certificates = self._parse_certificates(certificate_nodes) if len(certificates) > 1: raise SAMLMetadataParsingError( _('There are more than 1 SP certificates'.format(required_acs_binding.value))) certificate = next(iter(certificates)) if certificates else None sp = ServiceProviderMetadata( entity_id, ui_info, organization, name_id_format, acs_service, authn_requests_signed, want_assertions_signed, certificate) return sp
def parse(idp_metadata): """ Parse the Identity Provider metadata and returns a dict with extracted data If there are multiple IDPSSODescriptor it will only parse the first :param idp_metadata: XML of the Identity Provider Metadata. :type idp_metadata: string :param url: If true and the URL is HTTPs, the cert of the domain is checked. :type url: bool :returns: settings dict with extracted data :rtype: string """ data = {} dom = fromstring(idp_metadata) entity_descriptor_nodes = OneLogin_Saml2_Utils.query(dom, '//md:EntityDescriptor') idp_entity_id = want_authn_requests_signed = idp_name_id_format = idp_sso_url = idp_slo_url = idp_x509_cert = None if len(entity_descriptor_nodes) > 0: for entity_descriptor_node in entity_descriptor_nodes: idp_descriptor_nodes = OneLogin_Saml2_Utils.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_Utils.query(idp_descriptor_node, './md:NameIDFormat') if len(name_id_format_nodes) > 0: idp_name_id_format = name_id_format_nodes[0].text sso_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:SingleSignOnService[@Binding='%s']" % OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT) if len(sso_nodes) > 0: idp_sso_url = sso_nodes[0].get('Location', None) slo_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:SingleLogoutService[@Binding='%s']" % OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT) if len(slo_nodes) > 0: idp_slo_url = slo_nodes[0].get('Location', None) cert_nodes = OneLogin_Saml2_Utils.query(idp_descriptor_node, "./md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate") if len(cert_nodes) > 0: idp_x509_cert = cert_nodes[0].text data['idp'] = {} if idp_entity_id is not None: data['idp']['entityId'] = idp_entity_id if idp_sso_url is not None: data['idp']['singleLogoutService'] = {} data['idp']['singleLogoutService']['url'] = idp_sso_url if idp_slo_url is not None: data['idp']['singleLogoutService'] = {} data['idp']['singleLogoutService']['url'] = idp_slo_url if idp_x509_cert is not None: data['idp']['x509cert'] = idp_x509_cert 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 break return data