def xml_to_str(tree, encoding=None, xml_declaration=False): """Serialize an XML tree. Returns unicode if 'encoding' is None. Otherwise, we return encoded 'bytes'.""" if xml_declaration and not encoding: raise ValueError("'xml_declaration' is not supported when 'encoding' is None") if encoding: return tostring(tree, encoding=encoding, xml_declaration=True) return tostring(tree, encoding=text_type, xml_declaration=False)
def xml_to_str(tree, encoding=None, xml_declaration=False): """Serialize an XML tree. Returns unicode if 'encoding' is None. Otherwise, we return encoded 'bytes'.""" if xml_declaration and not encoding: raise ValueError("'xml_declaration' is not supported when 'encoding' is None") if encoding: return tostring(tree, encoding=encoding, xml_declaration=True) return tostring(tree, encoding=text_type, xml_declaration=False)
def xpath_selector(selector, html, select_all): """ Returns Xpath match for `selector` within `html`. :param selector: XPath string :param html: Unicode content :param select_all: True to get all matches """ from defusedxml import lxml as dlxml from lxml import etree import re # lxml requires argument to be bytes # see https://github.com/kibitzr/kibitzr/issues/47 encoded = html.encode('utf-8') root = dlxml.fromstring(encoded, parser=etree.HTMLParser()) elements = root.xpath(selector) if not elements: logger.warning('XPath selector not found: %r', selector) return False, html if select_all is False: elements = elements[0:1] elements = [ re.sub(r'\s+', ' ', dlxml.tostring(ele, method='html', encoding='unicode')).strip() for ele in elements ] return True, u"\n".join(six.text_type(x) for x in elements)
def __str__(self, level=0): """ Parameters ---------- level : int Number of double spaces " " to indent the resulting output to Returns ------- str representation of this element, pretty print of entire contents. """ if self.text: cur_node = xml_node(self.tag, self.text) result = "{}{}".format( " " * level, lxml.tostring(cur_node, pretty_print=True).decode()) result = result.rstrip() else: result = "{}<{}>".format(" " * level, self.tag, self.tag) for child in self.children: if type(self.__dict__[child.tag]) == XMLNode: child = self.__dict__[child.tag] result += '\n' + child.__str__(level=level + 1) result += '\n{}</{}>'.format(" " * level, self.tag) return result
def to_xml(text): try: if PY2: # On python2, fromstring expects an encoded string return fromstring((text[BOM_LEN:] if text.startswith(BOM) else text).encode('utf-8')) return fromstring(text[BOM_LEN:] if text.startswith(BOM) else text) except ParseError: # Exchange servers may spit out the weirdest XML. lxml is pretty good at recovering from errors log.warning('Fallback to lxml processing of faulty XML') magical_parser = XMLParser(recover=True, resolve_entities=False) magical_parser.set_element_class_lookup(ElementDefaultClassLookup(element=RestrictedElement)) no_bom_text = text[BOM_LEN:] if text.startswith(BOM) else text try: root = parse(io.BytesIO(no_bom_text.encode('utf-8')), parser=magical_parser) except AssertionError as e: raise ParseError(*e.args) try: return fromstring(tostring(root)) except ParseError as e: if hasattr(e, 'position'): e.lineno, e.offset = e.position if not e.lineno: raise ParseError('%s' % text_type(e)) try: offending_line = no_bom_text.splitlines()[e.lineno - 1] except IndexError: raise ParseError('%s' % text_type(e)) else: offending_excerpt = offending_line[max(0, e.offset - 20):e.offset + 20] raise ParseError('%s\nOffending text: [...]%s[...]' % (text_type(e), offending_excerpt)) except TypeError: raise ParseError('This is not XML: %s' % text)
def handle(self, *args, **options): client = get_soap_client() if not options['command']: self.logger.info('No command specified, dumping wsdl information') client.wsdl.dump() return arguments = { x.split('=')[0]: x.split('=')[1] for x in options["arguments"] } self.logger.info( f'Calling command {options["command"]} with parameters {arguments}' ) response = getattr(client.service, options['command'])(**arguments) dict_response = xml_etree_to_dict(response) self.logger.info(type(response)) if not isinstance(response, str): response = ET.tostring(response, pretty_print=True) self.logger.info(f'SOAP call response:') self.logger.info(response.decode('unicode_escape')) self.logger.info(f'Dictionary parsed response: {dict_response}')
def node_to_string(node, encoding=True): """ Parameters ---------- node : lxml note Returns ------- str : Pretty string representation of node """ if not type(node) == etree._ElementTree: tree = etree.ElementTree(node) else: tree = node return lxml.tostring( tree, pretty_print=True, with_tail=False, encoding="UTF-8", xml_declaration=encoding, ).decode("utf-8")
def xml_tree(self): """ Parse the infile with lxml and add the proper namespace if required. :return etree.ElementTree: An lxml ElementTree with proper namespace """ if hasattr(self.infile, 'seek'): self.infile.seek(0) tree = lxml.parse(self.infile) if self.meta.namespaces: return tree log.debug('Adding namespaces to xml for validation') root = tree.getroot() ns_root = etree.Element( tree.docinfo.root_name, root.attrib, nsmap={None: self.meta.get_ns_string()} ) ns_root[:] = root[:] # Roundtrip to add namespace doc = lxml.tostring( ns_root, encoding=tree.docinfo.encoding, xml_declaration=True, pretty_print=True ) ns_tree = lxml.fromstring(doc) return etree.ElementTree(ns_tree)
def serialize_xpath_results(xpath_results, select_all): """ Serializes results of xpath evaluation. :param xpath_results: Results of xpath evaluation. See: https://lxml.de/xpathxslt.html#xpath-return-values :param select_all: True to get all matches """ from defusedxml import lxml as dlxml import re if isinstance(xpath_results, list): if select_all is False: xpath_results = xpath_results[0:1] else: xpath_results = [xpath_results] results = [] for r in xpath_results: # namespace declarations if isinstance(r, tuple): results.append("%s=\"%s\"" % (r[0], r[1])) # an element elif hasattr(r, 'tag'): results.append( re.sub(r'\s+', ' ', dlxml.tostring(r, method='html', encoding='unicode')) ) else: results.append(r) return u"\n".join(six.text_type(x).strip() for x in results)
def prettify_xml(cls, xml_bytes): # Re-formats an XML document to a consistent style return tostring(cls.parse_bytes(xml_bytes), xml_declaration=True, encoding='utf-8', pretty_print=True).replace(b'\t', b' ').replace( b' xmlns:', b'\n xmlns:')
def xml_tree(self): """ Parse the infile with lxml and add the proper namespace if required. :return etree.ElementTree: An lxml ElementTree with proper namespace """ if hasattr(self.infile, 'seek'): self.infile.seek(0) tree = lxml.parse(self.infile) if self.meta.namespaces: return tree log.debug('Adding namespaces to xml for validation') root = tree.getroot() ns_root = etree.Element( tree.docinfo.root_name, root.attrib, nsmap={None: self.meta.get_ns_string()} ) ns_root[:] = root[:] # Roundtrip to add namespace doc = lxml.tostring( ns_root, encoding=tree.docinfo.encoding, xml_declaration=True, pretty_print=True ) ns_tree = lxml.fromstring(doc) return etree.ElementTree(ns_tree)
def xpath_selector(selector, html): """ Returns Xpath match for `selector` within `html`. :param selector: XPath string :param html: Unicode content """ from defusedxml import lxml as dlxml from lxml import etree # lxml requires argument to be bytes # see https://github.com/kibitzr/kibitzr/issues/47 encoded = html.encode('utf-8') root = dlxml.fromstring(encoded, parser=etree.HTMLParser()) elements = root.xpath(selector) if elements: return True, dlxml.tostring( next(iter(elements)), method='html', pretty_print=True, encoding='unicode', ) else: logger.warning('XPath selector not found: %r', selector) return False, html
def export(dataset, mode: str = "Single", profile_whitelist: Union[list, tuple] = None, header_data: dict = None): """ Returns a XML-serialization of the dataset within a zip-Archive. :param dataset: The dataset/SQLAlchemy-Session object to export. :param mode: {'Single', 'Multi'} - The export Mode, e.g. Single-File or Split by profiles. :param profile_whitelist: The profiles to export. Mandatory if Export-mode is 'Multi'. Profiles can be specificed by their full name (e.g. 'EquipmentProfile' or their short name 'EQ') :param header_data: Additional information for creating the file-headers (FullModel Objects). This is a dictionary of CIM-header supplements. Currently only the 'profile_header' field is supported, in which a list of profile identifiers (e.g. 'http://entsoe.eu/CIM/Topology/4/1') are defined. :return: A BytesIO-filehandle containing a zip-archive of the export. """ if not mode in ("Multi", "Single"): raise ValueError("Unknown serialization mode ('Single' and 'Multi' are valid values)") trees = serialize(dataset, mode, profile_whitelist, header_data) if isinstance(trees, _ElementTree): trees = [trees] file = BytesIO() with ZipFile(file, "w") as zf: if not profile_whitelist or mode == "Single": if not len(trees) == 1: raise ValueError("Too many objects returned by serializer.") zf.writestr( f"Export.xml", tostring(trees[0], encoding="UTF-8", xml_declaration=True, pretty_print=True) ) else: profile_lib = {p.name: p for p in dataset.query(CIMProfile)} for profile, tree in zip(profile_whitelist, trees): fname = profile_lib[profile].short zf.writestr( f"{fname}.xml", tostring(tree, encoding="UTF-8", xml_declaration=True, pretty_print=True) ) return file
def prettify_xml(cls, xml_bytes): # Re-formats an XML document to a consistent style return tostring( cls.parse_bytes(xml_bytes), xml_declaration=True, encoding='utf-8', pretty_print=True ).replace(b'\t', b' ').replace(b' xmlns:', b'\n xmlns:')
def process(value): if value is not None: if isinstance(value, str): return value else: return tostring(value) else: return None
def add_sign(xml, key, cert, debug=False): """Add sign. Args: xml (str): SAML assertion key (Path): path enc key cert (Path): path to cert/pem debug (boolean): xmlsec enable debug trace Returns: str: signed SAML assertion Raises: Exception: if xml is empty """ if xml is None or xml == '': raise Exception('Empty string supplied as input') elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) sign_algorithm_transform = xmlsec.Transform.ECDSA_SHA256 signature = xmlsec.template.create( elem, xmlsec.Transform.EXCL_C14N, sign_algorithm_transform, ns='ds', ) issuer = elem.findall('.//{urn:oasis:names:tc:SAML:2.0:assertion}Issuer') if issuer: issuer = issuer[0] issuer.addnext(signature) elem_to_sign = issuer.getparent() elem_id = elem_to_sign.get('ID', None) if elem_id is not None: if elem_id: elem_id = f'#{elem_id}' xmlsec.enable_debug_trace(debug) xmlsec.tree.add_ids(elem_to_sign, ['ID']) digest_algorithm_transform = xmlsec.Transform.SHA256 ref = xmlsec.template.add_reference( signature, digest_algorithm_transform, uri=elem_id, ) xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED) xmlsec.template.add_transform(ref, xmlsec.Transform.EXCL_C14N) key_info = xmlsec.template.ensure_key_info(signature) xmlsec.template.add_x509_data(key_info) dsig_ctx = xmlsec.SignatureContext() sign_key = xmlsec.Key.from_file(key, xmlsec.KeyFormat.PEM, None) sign_key.load_cert_from_file(cert, xmlsec.KeyFormat.PEM) dsig_ctx.key = sign_key dsig_ctx.sign(signature) return tostring(elem).decode()
def validate_xml(xml, schema, debug=False): """ Validates a xml against a schema :param xml: The xml that will be validated :type: string|DomDocument :param schema: The schema :type: string :param debug: If debug is active, the parse-errors will be showed :type: bool :returns: Error code or the DomDocument of the xml :rtype: string """ assert isinstance(xml, basestring) or isinstance( xml, Document) or isinstance(xml, etree._Element) assert isinstance(schema, basestring) if isinstance(xml, Document): xml = xml.toxml() elif isinstance(xml, etree._Element): xml = tostring(xml, encoding='unicode') # Switch to lxml for schema validation try: dom = fromstring(xml.encode('utf-8'), forbid_dtd=True) except Exception: return 'unloaded_xml' schema_file = join(dirname(__file__), 'schemas', schema) f_schema = open(schema_file, 'r') schema_doc = etree.parse(f_schema) f_schema.close() xmlschema = etree.XMLSchema(schema_doc) if not xmlschema.validate(dom): if debug: stderr.write('Errors validating the metadata') stderr.write(':\n\n') for error in xmlschema.error_log: stderr.write('%s\n' % error.message) return 'invalid_xml' return parseString(tostring(dom, encoding='unicode').encode('utf-8'), forbid_dtd=True)
def validate_xml(xml, schema, debug=False): """ Validates a xml against a schema :param xml: The xml that will be validated :type: string|DomDocument :param schema: The schema :type: string :param debug: If debug is active, the parse-errors will be showed :type: bool :returns: Error code or the DomDocument of the xml :rtype: string """ assert isinstance(xml, basestring) or isinstance(xml, Document) or isinstance(xml, etree._Element) assert isinstance(schema, basestring) if isinstance(xml, Document): xml = xml.toxml() elif isinstance(xml, etree._Element): xml = tostring(xml, encoding='unicode') # Switch to lxml for schema validation try: dom = fromstring(xml.encode('utf-8'), forbid_dtd=True) except Exception: return 'unloaded_xml' schema_file = join(dirname(__file__), 'schemas', schema) f_schema = open(schema_file, 'r') schema_doc = etree.parse(f_schema) f_schema.close() xmlschema = etree.XMLSchema(schema_doc) if not xmlschema.validate(dom): if debug: stderr.write('Errors validating the metadata') stderr.write(':\n\n') for error in xmlschema.error_log: stderr.write('%s\n' % error.message) return 'invalid_xml' return parseString(tostring(dom, encoding='unicode').encode('utf-8'), forbid_dtd=True)
def validate_xml(xml, schema, debug=False): # TS: added caching of schema's """ Validates a xml against a schema :param xml: The xml that will be validated :type: string|DomDocument :param schema: The schema :type: string :param debug: If debug is active, the parse-errors will be showed :type: bool :returns: Error code or the DomDocument of the xml :rtype: string """ assert isinstance(xml, basestring) or isinstance( xml, Document) or isinstance(xml, etree._Element) assert isinstance(schema, basestring) if isinstance(xml, Document): xml = xml.toxml() elif isinstance(xml, etree._Element): xml = tostring(xml, encoding='unicode') # Switch to lxml for schema validation try: dom = fromstring(xml.encode('utf-8')) except Exception: return 'unloaded_xml' xmlschema = loadXmlSchemaByFilename(schema) if not xmlschema.validate(dom): if debug: sys.stderr.write('Errors validating the metadata') sys.stderr.write(':\n\n') for error in xmlschema.error_log: sys.stderr.write('%s\n' % error.message) sys.stderr.flush() return 'invalid_xml' return parseString(tostring(dom, encoding='unicode').encode('utf-8'))
def get_last_response_xml(self, pretty_print_if_possible=False): """ Retrieves the raw XML (decrypted) of the last SAML response, or the last Logout Response generated or processed :returns: SAML response XML :rtype: string|None """ response = None if self.__last_response is not None: if isinstance(self.__last_response, compat.str_type): response = self.__last_response else: response = tostring(self.__last_response, encoding='unicode', pretty_print=pretty_print_if_possible) return response
def get_last_response_xml(self, pretty_print_if_possible=False): """ Retrieves the raw XML (decrypted) of the last SAML response, or the last Logout Response generated or processed :returns: SAML response XML :rtype: string|None """ response = None if self.__last_response is not None: if isinstance(self.__last_response, compat.str_type): response = self.__last_response else: response = tostring(self.__last_response, encoding='unicode', pretty_print=pretty_print_if_possible) return response
def createSigniature(student, key): root = ElementTree.Element("Student") username = ElementTree.SubElement(root, "Username") username.text = student["username"] fakulteti = ElementTree.SubElement(root, "Fakulteti") fakulteti.text = student["fakulteti"] nota = ElementTree.SubElement(root, "Nota") nota.text = student["nota"] viti = ElementTree.SubElement(root, "Viti") viti.text = student["viti"] signedXML = XMLSigner().sign(root, key=PRIVATEKEY) xmlStr = tostring(signedXML) message = {"cmd": "SIGNIATURE", "data": xmlStr.decode()} message = json.dumps(message) return MessageToSend(key, message)
def generate_metadata_xml(self): """Helper method to generate the SP metadata XML. Extends the base class metadata generation with extensions, elements and attributes required by the Suomi.fi IdP. Returns (metadata XML string, list of errors) """ metadata, errors = super().generate_metadata_xml() metadata_xml = fromstring(metadata) self._amend_services(metadata_xml) self._amend_contacts(metadata_xml) self._add_entity_attributes_extension(metadata_xml) self._add_ui_info_extension(metadata_xml) metadata = tostring(metadata_xml, encoding='utf-8', xml_declaration=True) return metadata, errors
def node_to_string(node): """ Parameters ---------- node : lxml note Returns ------- str : Pretty string representation of node """ return lxml.tostring(node, pretty_print=True, with_tail=False, encoding='UTF-8', xml_declaration=True).decode("utf-8")
def xpath_selector(selector, html, select_all): """ Returns Xpath match for `selector` within `html`. :param selector: XPath string :param html: Unicode content :param select_all: True to get all matches """ from defusedxml import lxml as dlxml from lxml import etree import re # lxml requires argument to be bytes # see https://github.com/kibitzr/kibitzr/issues/47 encoded = html.encode('utf-8') root = dlxml.fromstring(encoded, parser=etree.HTMLParser()) xpath_results = root.xpath(selector) if not xpath_results: logger.warning('XPath selector not found: %r', selector) return False, html if isinstance(xpath_results, list): if select_all is False: xpath_results = xpath_results[0:1] else: xpath_results = [xpath_results] # Serialize xpath_results # see https://lxml.de/xpathxslt.html#xpath-return-values results = [] for r in xpath_results: # namespace declarations if isinstance(r, tuple): results.append("%s=\"%s\"" % (r[0], r[1])) # an element elif hasattr(r, 'tag'): results.append( re.sub(r'\s+', ' ', dlxml.tostring(r, method='html', encoding='unicode'))) else: results.append(r) return True, u"\n".join(six.text_type(x).strip() for x in results)
def generate_metadata_xml(self): """Helper method to generate the SP metadata XML. Extends the base class metadata generation with extensions, elements and attributes required by the Suomi.fi IdP. Returns (metadata XML string, list of errors) """ metadata, errors = super().generate_metadata_xml() metadata_xml = fromstring(metadata) self._amend_services(metadata_xml) self._amend_contacts(metadata_xml) self._add_entity_attributes_extension(metadata_xml) self._add_ui_info_extension(metadata_xml) metadata = tostring(metadata_xml, encoding='utf-8', xml_declaration=True) return metadata, errors
def validate_xml(xml, schema, debug=False): """ Validates a xml against a schema :param xml: The xml that will be validated :type: string|DomDocument :param schema: The schema :type: string :param debug: If debug is active, the parse-errors will be showed :type: bool :returns: Error code or the DomDocument of the xml :rtype: string """ assert isinstance(xml, basestring) or isinstance(xml, Document) or isinstance(xml, etree._Element) assert isinstance(schema, basestring) if isinstance(xml, Document): xml = xml.toxml() elif isinstance(xml, etree._Element): xml = tostring(xml) # Switch to lxml for schema validation try: dom = fromstring(str(xml)) except Exception: return "unloaded_xml" schema_file = join(dirname(__file__), "schemas", schema) f_schema = open(schema_file, "r") schema_doc = etree.parse(f_schema) f_schema.close() xmlschema = etree.XMLSchema(schema_doc) if not xmlschema.validate(dom): if debug: stderr.write("Errors validating the metadata") stderr.write(":\n\n") for error in xmlschema.error_log: stderr.write("%s\n" % error.message) return "invalid_xml" return parseString(etree.tostring(dom))
def is_valid(self, request_data, request_id=None, raise_exceptions=False): """ Validates the response object. :param request_data: Request Data :type request_data: dict :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP :type request_id: string :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean :returns: True if the SAML Response is valid, False if not :rtype: bool """ self.__error = None try: # Checks SAML version if self.document.get('Version', None) != '2.0': raise OneLogin_Saml2_ValidationError( 'Unsupported SAML version', OneLogin_Saml2_ValidationError.UNSUPPORTED_SAML_VERSION) # Checks that ID exists if self.document.get('ID', None) is None: raise OneLogin_Saml2_ValidationError( 'Missing ID attribute on SAML Response', OneLogin_Saml2_ValidationError.MISSING_ID) # Checks that the response has the SUCCESS status self.check_status() # Checks that the response only has one assertion if not self.validate_num_assertions(): raise OneLogin_Saml2_ValidationError( 'SAML Response must contain 1 assertion', OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS) idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data.get('entityId', '') sp_data = self.__settings.get_sp_data() sp_entity_id = sp_data.get('entityId', '') signed_elements = self.process_signed_elements() has_signed_response = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements has_signed_assertion = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML in signed_elements if self.__settings.is_strict(): no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' res = OneLogin_Saml2_Utils.validate_xml( tostring(self.document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if not isinstance(res, Document): raise OneLogin_Saml2_ValidationError( no_valid_xml_msg, OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT) # If encrypted, check also the decrypted document if self.encrypted: res = OneLogin_Saml2_Utils.validate_xml( tostring(self.decrypted_document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active()) if not isinstance(res, Document): raise OneLogin_Saml2_ValidationError( no_valid_xml_msg, OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT) security = self.__settings.get_security_data() current_url = OneLogin_Saml2_Utils.get_self_url_no_query( request_data) in_response_to = self.document.get('InResponseTo', None) if request_id is None and in_response_to is not None and security.get( 'rejectUnsolicitedResponsesWithInResponseTo', False): raise OneLogin_Saml2_ValidationError( 'The Response has an InResponseTo attribute: %s while no InResponseTo was expected' % in_response_to, OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO) # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided if request_id is not None and in_response_to != request_id: raise OneLogin_Saml2_ValidationError( 'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id), OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO) if not self.encrypted and security.get( 'wantAssertionsEncrypted', False): raise OneLogin_Saml2_ValidationError( 'The assertion of the Response is not encrypted and the SP require it', OneLogin_Saml2_ValidationError.NO_ENCRYPTED_ASSERTION) if security.get('wantNameIdEncrypted', False): encrypted_nameid_nodes = self.__query_assertion( '/saml:Subject/saml:EncryptedID/xenc:EncryptedData') if len(encrypted_nameid_nodes) != 1: raise OneLogin_Saml2_ValidationError( 'The NameID of the Response is not encrypted and the SP require it', OneLogin_Saml2_ValidationError.NO_ENCRYPTED_NAMEID) # Checks that a Conditions element exists if not self.check_one_condition(): raise OneLogin_Saml2_ValidationError( 'The Assertion must include a Conditions element', OneLogin_Saml2_ValidationError.MISSING_CONDITIONS) # Validates Assertion timestamps self.validate_timestamps(raise_exceptions=True) # Checks that an AuthnStatement element exists and is unique if not self.check_one_authnstatement(): raise OneLogin_Saml2_ValidationError( 'The Assertion must include an AuthnStatement element', OneLogin_Saml2_ValidationError. WRONG_NUMBER_OF_AUTHSTATEMENTS) # Checks that there is at least one AttributeStatement if required attribute_statement_nodes = self.__query_assertion( '/saml:AttributeStatement') if security.get('wantAttributeStatement', True) and not attribute_statement_nodes: raise OneLogin_Saml2_ValidationError( 'There is no AttributeStatement on the Response', OneLogin_Saml2_ValidationError.NO_ATTRIBUTESTATEMENT) encrypted_attributes_nodes = self.__query_assertion( '/saml:AttributeStatement/saml:EncryptedAttribute') if encrypted_attributes_nodes: raise OneLogin_Saml2_ValidationError( 'There is an EncryptedAttribute in the Response and this SP not support them', OneLogin_Saml2_ValidationError.ENCRYPTED_ATTRIBUTES) # Checks destination destination = self.document.get('Destination', None) if destination: if not destination.startswith(current_url): # TODO: Review if following lines are required, since we can control the # request_data # current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data) # if not destination.startswith(current_url_routed): raise OneLogin_Saml2_ValidationError( 'The response was received at %s instead of %s' % (current_url, destination), OneLogin_Saml2_ValidationError.WRONG_DESTINATION) elif destination == '': raise OneLogin_Saml2_ValidationError( 'The response has an empty Destination value', OneLogin_Saml2_ValidationError.EMPTY_DESTINATION) # Checks audience valid_audiences = self.get_audiences() if valid_audiences and sp_entity_id not in valid_audiences: raise OneLogin_Saml2_ValidationError( '%s is not a valid audience for this Response' % sp_entity_id, OneLogin_Saml2_ValidationError.WRONG_AUDIENCE) # Checks the issuers issuers = self.get_issuers() for issuer in issuers: if issuer is None or issuer != idp_entity_id: raise OneLogin_Saml2_ValidationError( 'Invalid issuer in the Assertion/Response (expected %(idpEntityId)s, got %(issuer)s)' % { 'idpEntityId': idp_entity_id, 'issuer': issuer }, OneLogin_Saml2_ValidationError.WRONG_ISSUER) # Checks the session Expiration session_expiration = self.get_session_not_on_or_after() if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now( ): raise OneLogin_Saml2_ValidationError( 'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response', OneLogin_Saml2_ValidationError.SESSION_EXPIRED) # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid any_subject_confirmation = False subject_confirmation_nodes = self.__query_assertion( '/saml:Subject/saml:SubjectConfirmation') for scn in subject_confirmation_nodes: method = scn.get('Method', None) if method and method != OneLogin_Saml2_Constants.CM_BEARER: continue sc_data = scn.find( 'saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP) if sc_data is None: continue else: irt = sc_data.get('InResponseTo', None) if (in_response_to is None and irt is not None and security.get('rejectUnsolicitedResponsesWithInResponseTo', False)) or \ in_response_to and irt and irt != in_response_to: continue recipient = sc_data.get('Recipient', None) if recipient and current_url not in recipient: continue nooa = sc_data.get('NotOnOrAfter', None) if nooa: parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time( nooa) if parsed_nooa <= OneLogin_Saml2_Utils.now(): continue nb = sc_data.get('NotBefore', None) if nb: parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time( nb) if parsed_nb > OneLogin_Saml2_Utils.now(): continue if nooa: self.valid_scd_not_on_or_after = OneLogin_Saml2_Utils.parse_SAML_to_time( nooa) any_subject_confirmation = True break if not any_subject_confirmation: raise OneLogin_Saml2_ValidationError( 'A valid SubjectConfirmation was not found on this Response', OneLogin_Saml2_ValidationError. WRONG_SUBJECTCONFIRMATION) if security.get('wantAssertionsSigned', False) and not has_signed_assertion: raise OneLogin_Saml2_ValidationError( 'The Assertion of the Response is not signed and the SP require it', OneLogin_Saml2_ValidationError.NO_SIGNED_ASSERTION) if security.get('wantMessagesSigned', False) and not has_signed_response: raise OneLogin_Saml2_ValidationError( 'The Message of the Response is not signed and the SP require it', OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE) if not signed_elements or (not has_signed_response and not has_signed_assertion): raise OneLogin_Saml2_ValidationError( 'No Signature found. SAML Response rejected', OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND) else: cert = idp_data.get('x509cert', None) fingerprint = idp_data.get('certFingerprint', None) fingerprintalg = idp_data.get('certFingerprintAlgorithm', None) multicerts = None if 'x509certMulti' in idp_data and 'signing' in idp_data[ 'x509certMulti'] and idp_data['x509certMulti'][ 'signing']: multicerts = idp_data['x509certMulti']['signing'] # If find a Signature on the Response, validates it checking the original response if has_signed_response and not OneLogin_Saml2_Utils.validate_sign( self.document, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False): raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE) document_check_assertion = self.decrypted_document if self.encrypted else self.document if has_signed_assertion and not OneLogin_Saml2_Utils.validate_sign( document_check_assertion, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False): raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE) return True except Exception as err: self.__error = err.__str__() debug = self.__settings.is_debug_active() if debug: print err.__str__() if raise_exceptions: raise err return False
def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1, digest_algorithm=OneLogin_Saml2_Constants.SHA1): """ Adds signature key and senders certificate to an element (Message or Assertion). :param xml: The element we should sign :type: string | Document :param key: The private key :type: string :param cert: The public :type: string :param debug: Activate the xmlsec debug :type: bool :param sign_algorithm: Signature algorithm method :type sign_algorithm: string :param digest_algorithm: Digest algorithm method :type digest_algorithm: string :returns: Signed XML :rtype: string """ if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) elif isinstance(xml, Element): xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP)) xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML)) xml = xml.toxml() elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) elif isinstance(xml, basestring): elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) else: raise Exception('Error parsing xml string') error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512 } sign_algorithm_transform = sign_algorithm_transform_map.get( sign_algorithm, xmlsec.TransformRsaSha1) signature = Signature(xmlsec.TransformExclC14N, sign_algorithm_transform, nsPrefix='ds') issuer = OneLogin_Saml2_Utils.query(elem, '//saml:Issuer') if len(issuer) > 0: issuer = issuer[0] issuer.addnext(signature) elem_to_sign = issuer.getparent() else: entity_descriptor = OneLogin_Saml2_Utils.query( elem, '//md:EntityDescriptor') if len(entity_descriptor) > 0: elem.insert(0, signature) else: elem[0].insert(0, signature) elem_to_sign = elem elem_id = elem_to_sign.get('ID', None) if elem_id is not None: if elem_id: elem_id = '#' + elem_id else: generated_id = generated_id = OneLogin_Saml2_Utils.generate_unique_id( ) elem_id = '#' + generated_id elem_to_sign.attrib['ID'] = generated_id xmlsec.addIDs(elem_to_sign, ["ID"]) digest_algorithm_transform_map = { OneLogin_Saml2_Constants.SHA1: xmlsec.TransformSha1, OneLogin_Saml2_Constants.SHA256: xmlsec.TransformSha256, OneLogin_Saml2_Constants.SHA384: xmlsec.TransformSha384, OneLogin_Saml2_Constants.SHA512: xmlsec.TransformSha512 } digest_algorithm_transform = digest_algorithm_transform_map.get( digest_algorithm, xmlsec.TransformSha1) ref = signature.addReference(digest_algorithm_transform) if elem_id: ref.attrib['URI'] = elem_id ref.addTransform(xmlsec.TransformEnveloped) ref.addTransform(xmlsec.TransformExclC14N) key_info = signature.ensureKeyInfo() key_info.addX509Data() dsig_ctx = xmlsec.DSigCtx() sign_key = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None) file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) sign_key.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem) file_cert.close() dsig_ctx.signKey = sign_key dsig_ctx.sign(signature) return tostring(elem, encoding='unicode').encode('utf-8')
def generate_name_id(value, sp_nq, sp_format=None, cert=None, debug=False, nq=None): """ Generates a nameID. :param value: fingerprint :type: string :param sp_nq: SP Name Qualifier :type: string :param sp_format: SP Format :type: string :param cert: IdP Public Cert to encrypt the nameID :type: string :param debug: Activate the xmlsec debug :type: bool :param nq: IDP Name Qualifier :type: string :returns: DOMElement | XMLSec nameID :rtype: string """ doc = Document() name_id_container = doc.createElementNS( OneLogin_Saml2_Constants.NS_SAML, 'container') name_id_container.setAttribute("xmlns:saml", OneLogin_Saml2_Constants.NS_SAML) name_id = doc.createElement('saml:NameID') if sp_nq is not None: name_id.setAttribute('SPNameQualifier', sp_nq) if nq is not None: name_id.setAttribute('NameQualifier', nq) if sp_format is not None: name_id.setAttribute('Format', sp_format) name_id.appendChild(doc.createTextNode(value)) name_id_container.appendChild(name_id) if cert is not None: xml = name_id_container.toxml() elem = fromstring(xml) error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) # Load the public cert mngr = xmlsec.KeysMngr() file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) key_data = xmlsec.Key.load(file_cert.name, xmlsec.KeyDataFormatCertPem, None) key_data.name = basename(file_cert.name) mngr.addKey(key_data) file_cert.close() # Prepare for encryption enc_data = EncData(xmlsec.TransformAes128Cbc, type=xmlsec.TypeEncElement) enc_data.ensureCipherValue() key_info = enc_data.ensureKeyInfo() # enc_key = key_info.addEncryptedKey(xmlsec.TransformRsaPkcs1) enc_key = key_info.addEncryptedKey(xmlsec.TransformRsaOaep) enc_key.ensureCipherValue() # Encrypt! enc_ctx = xmlsec.EncCtx(mngr) enc_ctx.encKey = xmlsec.Key.generate(xmlsec.KeyDataAes, 128, xmlsec.KeyDataTypeSession) edata = enc_ctx.encryptXml(enc_data, elem[0]) newdoc = parseString( tostring(edata, encoding='unicode').encode('utf-8')) if newdoc.hasChildNodes(): child = newdoc.firstChild child.removeAttribute('xmlns') child.removeAttribute('xmlns:saml') child.setAttribute('xmlns:xenc', OneLogin_Saml2_Constants.NS_XENC) child.setAttribute('xmlns:dsig', OneLogin_Saml2_Constants.NS_DS) nodes = newdoc.getElementsByTagName("*") for node in nodes: if node.tagName == 'ns0:KeyInfo': node.tagName = 'dsig:KeyInfo' node.removeAttribute('xmlns:ns0') node.setAttribute('xmlns:dsig', OneLogin_Saml2_Constants.NS_DS) else: node.tagName = 'xenc:' + node.tagName encrypted_id = newdoc.createElement('saml:EncryptedID') encrypted_data = newdoc.replaceChild(encrypted_id, newdoc.firstChild) encrypted_id.appendChild(encrypted_data) return newdoc.saveXML(encrypted_id) else: return doc.saveXML(name_id)
def is_valid(self, request_data, request_id=None, raise_exceptions=False): """ Validates the response object. :param request_data: Request Data :type request_data: dict :param request_id: Optional argument. The ID of the AuthNRequest sent by this SP to the IdP :type request_id: string :param raise_exceptions: Whether to return false on failure or raise an exception :type raise_exceptions: Boolean :returns: True if the SAML Response is valid, False if not :rtype: bool """ self.__error = None try: # Checks SAML version if self.document.get('Version', None) != '2.0': raise OneLogin_Saml2_ValidationError( 'Unsupported SAML version', OneLogin_Saml2_ValidationError.UNSUPPORTED_SAML_VERSION ) # Checks that ID exists if self.document.get('ID', None) is None: raise OneLogin_Saml2_ValidationError( 'Missing ID attribute on SAML Response', OneLogin_Saml2_ValidationError.MISSING_ID ) # Checks that the response has the SUCCESS status self.check_status() # Checks that the response only has one assertion if not self.validate_num_assertions(): raise OneLogin_Saml2_ValidationError( 'SAML Response must contain 1 assertion', OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_ASSERTIONS ) idp_data = self.__settings.get_idp_data() idp_entity_id = idp_data.get('entityId', '') sp_data = self.__settings.get_sp_data() sp_entity_id = sp_data.get('entityId', '') signed_elements = self.process_signed_elements() has_signed_response = '{%s}Response' % OneLogin_Saml2_Constants.NS_SAMLP in signed_elements has_signed_assertion = '{%s}Assertion' % OneLogin_Saml2_Constants.NS_SAML in signed_elements if self.__settings.is_strict(): no_valid_xml_msg = 'Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd' res = OneLogin_Saml2_Utils.validate_xml( tostring(self.document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active() ) if not isinstance(res, Document): raise OneLogin_Saml2_ValidationError( no_valid_xml_msg, OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT ) # If encrypted, check also the decrypted document if self.encrypted: res = OneLogin_Saml2_Utils.validate_xml( tostring(self.decrypted_document), 'saml-schema-protocol-2.0.xsd', self.__settings.is_debug_active() ) if not isinstance(res, Document): raise OneLogin_Saml2_ValidationError( no_valid_xml_msg, OneLogin_Saml2_ValidationError.INVALID_XML_FORMAT ) security = self.__settings.get_security_data() current_url = OneLogin_Saml2_Utils.get_self_url_no_query(request_data) in_response_to = self.document.get('InResponseTo', None) if request_id is None and in_response_to is not None and security.get('rejectUnsolicitedResponsesWithInResponseTo', False): raise OneLogin_Saml2_ValidationError( 'The Response has an InResponseTo attribute: %s while no InResponseTo was expected' % in_response_to, OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO ) # Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided if request_id is not None and in_response_to != request_id: raise OneLogin_Saml2_ValidationError( 'The InResponseTo of the Response: %s, does not match the ID of the AuthNRequest sent by the SP: %s' % (in_response_to, request_id), OneLogin_Saml2_ValidationError.WRONG_INRESPONSETO ) if not self.encrypted and security.get('wantAssertionsEncrypted', False): raise OneLogin_Saml2_ValidationError( 'The assertion of the Response is not encrypted and the SP require it', OneLogin_Saml2_ValidationError.NO_ENCRYPTED_ASSERTION ) if security.get('wantNameIdEncrypted', False): encrypted_nameid_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData') if len(encrypted_nameid_nodes) != 1: raise OneLogin_Saml2_ValidationError( 'The NameID of the Response is not encrypted and the SP require it', OneLogin_Saml2_ValidationError.NO_ENCRYPTED_NAMEID ) # Checks that a Conditions element exists if not self.check_one_condition(): raise OneLogin_Saml2_ValidationError( 'The Assertion must include a Conditions element', OneLogin_Saml2_ValidationError.MISSING_CONDITIONS ) # Validates Assertion timestamps self.validate_timestamps(raise_exceptions=True) # Checks that an AuthnStatement element exists and is unique if not self.check_one_authnstatement(): raise OneLogin_Saml2_ValidationError( 'The Assertion must include an AuthnStatement element', OneLogin_Saml2_ValidationError.WRONG_NUMBER_OF_AUTHSTATEMENTS ) # Checks that the response has all of the AuthnContexts that we provided in the request. # Only check if failOnAuthnContextMismatch is true and requestedAuthnContext is set to a list. requested_authn_contexts = security.get('requestedAuthnContext', True) if security.get('failOnAuthnContextMismatch', False) and requested_authn_contexts and requested_authn_contexts is not True: authn_contexts = self.get_authn_contexts() unmatched_contexts = set(requested_authn_contexts).difference(authn_contexts) if unmatched_contexts: raise OneLogin_Saml2_ValidationError( 'The AuthnContext "%s" didn\'t include requested context "%s"' % (', '.join(authn_contexts), ', '.join(unmatched_contexts)), OneLogin_Saml2_ValidationError.AUTHN_CONTEXT_MISMATCH ) # Checks that there is at least one AttributeStatement if required attribute_statement_nodes = self.__query_assertion('/saml:AttributeStatement') if security.get('wantAttributeStatement', True) and not attribute_statement_nodes: raise OneLogin_Saml2_ValidationError( 'There is no AttributeStatement on the Response', OneLogin_Saml2_ValidationError.NO_ATTRIBUTESTATEMENT ) encrypted_attributes_nodes = self.__query_assertion('/saml:AttributeStatement/saml:EncryptedAttribute') if encrypted_attributes_nodes: raise OneLogin_Saml2_ValidationError( 'There is an EncryptedAttribute in the Response and this SP not support them', OneLogin_Saml2_ValidationError.ENCRYPTED_ATTRIBUTES ) # Checks destination destination = self.document.get('Destination', None) if destination: if not destination.startswith(current_url): # TODO: Review if following lines are required, since we can control the # request_data # current_url_routed = OneLogin_Saml2_Utils.get_self_routed_url_no_query(request_data) # if not destination.startswith(current_url_routed): raise OneLogin_Saml2_ValidationError( 'The response was received at %s instead of %s' % (current_url, destination), OneLogin_Saml2_ValidationError.WRONG_DESTINATION ) elif destination == '': raise OneLogin_Saml2_ValidationError( 'The response has an empty Destination value', OneLogin_Saml2_ValidationError.EMPTY_DESTINATION ) # Checks audience valid_audiences = self.get_audiences() if valid_audiences and sp_entity_id not in valid_audiences: raise OneLogin_Saml2_ValidationError( '%s is not a valid audience for this Response' % sp_entity_id, OneLogin_Saml2_ValidationError.WRONG_AUDIENCE ) # Checks the issuers issuers = self.get_issuers() for issuer in issuers: if issuer is None or issuer != idp_entity_id: raise OneLogin_Saml2_ValidationError( 'Invalid issuer in the Assertion/Response (expected %(idpEntityId)s, got %(issuer)s)' % { 'idpEntityId': idp_entity_id, 'issuer': issuer }, OneLogin_Saml2_ValidationError.WRONG_ISSUER ) # Checks the session Expiration session_expiration = self.get_session_not_on_or_after() if session_expiration and session_expiration <= OneLogin_Saml2_Utils.now(): raise OneLogin_Saml2_ValidationError( 'The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response', OneLogin_Saml2_ValidationError.SESSION_EXPIRED ) # Checks the SubjectConfirmation, at least one SubjectConfirmation must be valid any_subject_confirmation = False subject_confirmation_nodes = self.__query_assertion('/saml:Subject/saml:SubjectConfirmation') for scn in subject_confirmation_nodes: method = scn.get('Method', None) if method and method != OneLogin_Saml2_Constants.CM_BEARER: continue sc_data = scn.find('saml:SubjectConfirmationData', namespaces=OneLogin_Saml2_Constants.NSMAP) if sc_data is None: continue else: irt = sc_data.get('InResponseTo', None) if (in_response_to is None and irt is not None and security.get('rejectUnsolicitedResponsesWithInResponseTo', False)) or \ in_response_to and irt and irt != in_response_to: continue recipient = sc_data.get('Recipient', None) if recipient and current_url not in recipient: continue nooa = sc_data.get('NotOnOrAfter', None) if nooa: parsed_nooa = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa) if parsed_nooa <= OneLogin_Saml2_Utils.now(): continue nb = sc_data.get('NotBefore', None) if nb: parsed_nb = OneLogin_Saml2_Utils.parse_SAML_to_time(nb) if parsed_nb > OneLogin_Saml2_Utils.now(): continue if nooa: self.valid_scd_not_on_or_after = OneLogin_Saml2_Utils.parse_SAML_to_time(nooa) any_subject_confirmation = True break if not any_subject_confirmation: raise OneLogin_Saml2_ValidationError( 'A valid SubjectConfirmation was not found on this Response', OneLogin_Saml2_ValidationError.WRONG_SUBJECTCONFIRMATION ) if security.get('wantAssertionsSigned', False) and not has_signed_assertion: raise OneLogin_Saml2_ValidationError( 'The Assertion of the Response is not signed and the SP require it', OneLogin_Saml2_ValidationError.NO_SIGNED_ASSERTION ) if security.get('wantMessagesSigned', False) and not has_signed_response: raise OneLogin_Saml2_ValidationError( 'The Message of the Response is not signed and the SP require it', OneLogin_Saml2_ValidationError.NO_SIGNED_MESSAGE ) if not signed_elements or (not has_signed_response and not has_signed_assertion): raise OneLogin_Saml2_ValidationError( 'No Signature found. SAML Response rejected', OneLogin_Saml2_ValidationError.NO_SIGNATURE_FOUND ) else: cert = idp_data.get('x509cert', None) fingerprint = idp_data.get('certFingerprint', None) fingerprintalg = idp_data.get('certFingerprintAlgorithm', None) multicerts = None if 'x509certMulti' in idp_data and 'signing' in idp_data['x509certMulti'] and idp_data['x509certMulti']['signing']: multicerts = idp_data['x509certMulti']['signing'] # If find a Signature on the Response, validates it checking the original response if has_signed_response and not OneLogin_Saml2_Utils.validate_sign(self.document, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.RESPONSE_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False): raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE ) document_check_assertion = self.decrypted_document if self.encrypted else self.document if has_signed_assertion and not OneLogin_Saml2_Utils.validate_sign(document_check_assertion, cert, fingerprint, fingerprintalg, xpath=OneLogin_Saml2_Utils.ASSERTION_SIGNATURE_XPATH, multicerts=multicerts, raise_exceptions=False): raise OneLogin_Saml2_ValidationError( 'Signature validation failed. SAML Response rejected', OneLogin_Saml2_ValidationError.INVALID_SIGNATURE ) return True except Exception as err: self.__error = err.__str__() debug = self.__settings.is_debug_active() if debug: print(err.__str__()) if raise_exceptions: raise err return False
def encode(self): return lxml.tostring( self.to_element(ElementMaker(nsmap=self.nsmap)), xml_declaration=True, encoding='UTF-8', )
def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1, digest_algorithm=OneLogin_Saml2_Constants.SHA1): """ Adds signature key and senders certificate to an element (Message or Assertion). :param xml: The element we should sign :type: string | Document :param key: The private key :type: string :param cert: The public :type: string :param debug: Activate the xmlsec debug :type: bool :param sign_algorithm: Signature algorithm method :type sign_algorithm: string :param digest_algorithm: Digest algorithm method :type digest_algorithm: string :returns: Signed XML :rtype: string """ if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) elif isinstance(xml, Element): xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP) ) xml.setAttributeNS( unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML) ) xml = xml.toxml() elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) elif isinstance(xml, basestring): elem = fromstring(xml.encode('utf-8'), forbid_dtd=True) else: raise Exception('Error parsing xml string') error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512 } sign_algorithm_transform = sign_algorithm_transform_map.get(sign_algorithm, xmlsec.TransformRsaSha1) signature = Signature(xmlsec.TransformExclC14N, sign_algorithm_transform, nsPrefix='ds') issuer = OneLogin_Saml2_Utils.query(elem, '//saml:Issuer') if len(issuer) > 0: issuer = issuer[0] issuer.addnext(signature) elem_to_sign = issuer.getparent() else: entity_descriptor = OneLogin_Saml2_Utils.query(elem, '//md:EntityDescriptor') if len(entity_descriptor) > 0: elem.insert(0, signature) else: elem[0].insert(0, signature) elem_to_sign = elem elem_id = elem_to_sign.get('ID', None) if elem_id is not None: if elem_id: elem_id = '#' + elem_id else: generated_id = generated_id = OneLogin_Saml2_Utils.generate_unique_id() elem_id = '#' + generated_id elem_to_sign.attrib['ID'] = generated_id xmlsec.addIDs(elem_to_sign, ["ID"]) digest_algorithm_transform_map = { OneLogin_Saml2_Constants.SHA1: xmlsec.TransformSha1, OneLogin_Saml2_Constants.SHA256: xmlsec.TransformSha256, OneLogin_Saml2_Constants.SHA384: xmlsec.TransformSha384, OneLogin_Saml2_Constants.SHA512: xmlsec.TransformSha512 } digest_algorithm_transform = digest_algorithm_transform_map.get(digest_algorithm, xmlsec.TransformSha1) ref = signature.addReference(digest_algorithm_transform) if elem_id: ref.attrib['URI'] = elem_id ref.addTransform(xmlsec.TransformEnveloped) ref.addTransform(xmlsec.TransformExclC14N) key_info = signature.ensureKeyInfo() key_info.addX509Data() dsig_ctx = xmlsec.DSigCtx() sign_key = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None) file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) sign_key.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem) file_cert.close() dsig_ctx.signKey = sign_key dsig_ctx.sign(signature) return tostring(elem, encoding='unicode').encode('utf-8')
def generate_name_id(value, sp_nq, sp_format=None, cert=None, debug=False, nq=None): """ Generates a nameID. :param value: fingerprint :type: string :param sp_nq: SP Name Qualifier :type: string :param sp_format: SP Format :type: string :param cert: IdP Public Cert to encrypt the nameID :type: string :param debug: Activate the xmlsec debug :type: bool :param nq: IDP Name Qualifier :type: string :returns: DOMElement | XMLSec nameID :rtype: string """ doc = Document() name_id_container = doc.createElementNS(OneLogin_Saml2_Constants.NS_SAML, 'container') name_id_container.setAttribute("xmlns:saml", OneLogin_Saml2_Constants.NS_SAML) name_id = doc.createElement('saml:NameID') if sp_nq is not None: name_id.setAttribute('SPNameQualifier', sp_nq) if nq is not None: name_id.setAttribute('NameQualifier', nq) if sp_format is not None: name_id.setAttribute('Format', sp_format) name_id.appendChild(doc.createTextNode(value)) name_id_container.appendChild(name_id) if cert is not None: xml = name_id_container.toxml() elem = fromstring(xml, forbid_dtd=True) error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) # Load the public cert mngr = xmlsec.KeysMngr() file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) key_data = xmlsec.Key.load(file_cert.name, xmlsec.KeyDataFormatCertPem, None) key_data.name = basename(file_cert.name) mngr.addKey(key_data) file_cert.close() # Prepare for encryption enc_data = EncData(xmlsec.TransformAes128Cbc, type=xmlsec.TypeEncElement) enc_data.ensureCipherValue() key_info = enc_data.ensureKeyInfo() # enc_key = key_info.addEncryptedKey(xmlsec.TransformRsaPkcs1) enc_key = key_info.addEncryptedKey(xmlsec.TransformRsaOaep) enc_key.ensureCipherValue() # Encrypt! enc_ctx = xmlsec.EncCtx(mngr) enc_ctx.encKey = xmlsec.Key.generate(xmlsec.KeyDataAes, 128, xmlsec.KeyDataTypeSession) edata = enc_ctx.encryptXml(enc_data, elem[0]) newdoc = parseString(tostring(edata, encoding='unicode').encode('utf-8'), forbid_dtd=True) if newdoc.hasChildNodes(): child = newdoc.firstChild child.removeAttribute('xmlns') child.removeAttribute('xmlns:saml') child.setAttribute('xmlns:xenc', OneLogin_Saml2_Constants.NS_XENC) child.setAttribute('xmlns:dsig', OneLogin_Saml2_Constants.NS_DS) nodes = newdoc.getElementsByTagName("*") for node in nodes: if node.tagName == 'ns0:KeyInfo': node.tagName = 'dsig:KeyInfo' node.removeAttribute('xmlns:ns0') node.setAttribute('xmlns:dsig', OneLogin_Saml2_Constants.NS_DS) else: node.tagName = 'xenc:' + node.tagName encrypted_id = newdoc.createElement('saml:EncryptedID') encrypted_data = newdoc.replaceChild(encrypted_id, newdoc.firstChild) encrypted_id.appendChild(encrypted_data) return newdoc.saveXML(encrypted_id) else: return doc.saveXML(name_id)
def encode(self): return lxml.tostring(self.to_element(ElementMaker(nsmap=self.nsmap)), xml_declaration=True, encoding="UTF-8")
def xml(self) -> str: string = tostring(self.root, encoding='utf-8', pretty_print=True) return string.decode('utf-8')
def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constants.RSA_SHA1, digest_algorithm=OneLogin_Saml2_Constants.SHA1): """ Adds signature key and senders certificate to an element (Message or Assertion). :param xml: The element we should sign :type: string | Document :param key: The private key :type: string :param cert: The public :type: string :param debug: Activate the xmlsec debug :type: bool :param sign_algorithm: Signature algorithm method :type sign_algorithm: string :param digest_algorithm: Digest algorithm method :type digest_algorithm: string :returns: Signed XML :rtype: string """ if xml is None or xml == '': raise Exception('Empty string supplied as input') elif isinstance(xml, etree._Element): elem = xml elif isinstance(xml, Document): xml = xml.toxml() elem = fromstring(xml.encode('utf-8')) elif isinstance(xml, Element): xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAMLP), 'xmlns:samlp', unicode(OneLogin_Saml2_Constants.NS_SAMLP)) xml.setAttributeNS(unicode(OneLogin_Saml2_Constants.NS_SAML), 'xmlns:saml', unicode(OneLogin_Saml2_Constants.NS_SAML)) xml = xml.toxml() elem = fromstring(xml.encode('utf-8')) elif isinstance(xml, basestring): elem = fromstring(xml.encode('utf-8')) else: raise Exception('Error parsing xml string') error_callback_method = None if debug: error_callback_method = print_xmlsec_errors xmlsec.set_error_callback(error_callback_method) # Sign the metadata with our private key. sign_algorithm_transform_map = { OneLogin_Saml2_Constants.DSA_SHA1: xmlsec.TransformDsaSha1, OneLogin_Saml2_Constants.RSA_SHA1: xmlsec.TransformRsaSha1, OneLogin_Saml2_Constants.RSA_SHA256: xmlsec.TransformRsaSha256, OneLogin_Saml2_Constants.RSA_SHA384: xmlsec.TransformRsaSha384, OneLogin_Saml2_Constants.RSA_SHA512: xmlsec.TransformRsaSha512 } sign_algorithm_transform = sign_algorithm_transform_map.get( sign_algorithm, xmlsec.TransformRsaSha1) signature = Signature(xmlsec.TransformExclC14N, sign_algorithm_transform) issuer = OneLogin_Saml2_Utils.query(elem, '//saml:Issuer') if len(issuer) > 0: issuer = issuer[0] issuer.addnext(signature) else: entity_descriptor = OneLogin_Saml2_Utils.query( elem, '//md:EntityDescriptor') if len(entity_descriptor) > 0: elem.insert(0, signature) else: elem[0].insert(0, signature) digest_algorithm_transform_map = { OneLogin_Saml2_Constants.SHA1: xmlsec.TransformSha1, OneLogin_Saml2_Constants.SHA256: xmlsec.TransformSha256, OneLogin_Saml2_Constants.SHA384: xmlsec.TransformSha384, OneLogin_Saml2_Constants.SHA512: xmlsec.TransformSha512 } digest_algorithm_transform = digest_algorithm_transform_map.get( digest_algorithm, xmlsec.TransformSha1) ref = signature.addReference(digest_algorithm_transform) ref.addTransform(xmlsec.TransformEnveloped) ref.addTransform(xmlsec.TransformExclC14N) key_info = signature.ensureKeyInfo() key_info.addX509Data() dsig_ctx = xmlsec.DSigCtx() sign_key = xmlsec.Key.loadMemory(key, xmlsec.KeyDataFormatPem, None) file_cert = OneLogin_Saml2_Utils.write_temp_file(cert) sign_key.loadCert(file_cert.name, xmlsec.KeyDataFormatCertPem) file_cert.close() dsig_ctx.signKey = sign_key dsig_ctx.sign(signature) newdoc = parseString( tostring(elem, encoding='unicode').encode('utf-8')) signature_nodes = newdoc.getElementsByTagName("Signature") for signature in signature_nodes: signature.removeAttribute('xmlns') signature.setAttribute('xmlns:ds', OneLogin_Saml2_Constants.NS_DS) if not signature.tagName.startswith('ds:'): signature.tagName = 'ds:' + signature.tagName nodes = signature.getElementsByTagName("*") for node in nodes: if not node.tagName.startswith('ds:'): node.tagName = 'ds:' + node.tagName return newdoc.saveXML(newdoc.firstChild)
def synchronize(self, soap_function="IPMSDataWebService", test=False): ''' Runs the SynchronizationJob Args: soap_function (str) - The function to call on the SOAP client test (bool) - Indicates if we should use the testing method of the SOAP client ''' start = datetime.datetime.utcnow() self.running = True self.save() try: logger = logging.getLogger('synchronization') logger.info(self) logger.info("Getting SOAP client") client = get_soap_client(soap_function=soap_function, test=test) logger.info(f"Using function {soap_function}") synchronization_tasks = get_synchronization_information( self.talentmap_model) for task in synchronization_tasks: logger.info(f"Running task {task.__name__}") soap_arguments, instance_tag, tag_map, collision_field, post_load_function, override_loading_method = task( ) model = apps.get_model(self.talentmap_model) logger.info("Intializing XML loader") loader = XMLloader(model, instance_tag, tag_map, 'update', collision_field, override_loading_method) logger.info("Loader initialized, pulling XML data") ''' The XML data from DOS SOAP comes back in batches, we need to use the last 'collision_field' as the PaginationStartKey of the next request, and continue until we get no more results ''' new_ids = [] updated_ids = [] last_collision_field = None soap_function_name = soap_function while True: if last_collision_field: # Set the pagination start key to our last collision field; which should be the remote data's primary key soap_arguments[ 'PaginationStartKey'] = last_collision_field logger.info( f"Requesting page from primary key: {last_collision_field}" ) else: logger.info(f"Requesting first page") # Get the data response_xml = ET.tostring(getattr( client.service, soap_function_name)(**soap_arguments), encoding="unicode") newer_ids, updateder_ids, last_collision_field = loader.create_models_from_xml( response_xml, raw_string=True) # If there are no new or updated ids on this page, we've reached the end if len(newer_ids) == 0 and len(updateder_ids) == 0: break logger.info( f"Got: {len(newer_ids)} new, {len(updateder_ids)} updated this page" ) # Append our newer and updated-er ids to our total list new_ids = new_ids + newer_ids updated_ids = updated_ids + updateder_ids logger.info("Data pull complete") # Run the post load function, if it exists if callable(post_load_function): logger.info(f"Calling post-load function for model {model}") post_load_function(model, new_ids, updated_ids) logger.info("Synchronization complete") d = relativedelta(start, datetime.datetime.utcnow()) logger.info( f"Synchronization Report\n\tModel: {self.talentmap_model}\n\tNew: {len(new_ids)}\n\tUpdated: {len(updated_ids)}\n\tElapsed time: {d.days} d {d.minutes} min {d.seconds} s\t\t" ) # Successful, set the last synchronization self.last_synchronization = timezone.now() except: # pragma: no cover raise finally: self.running = False self.save()