# -*- coding: utf-8 -*-
""" OneLogin_Saml2_XML class
Auxiliary class of SAML Python Toolkit.
"""
from os.path import join, dirname
from lxml import etree
from onelogin.saml2 import compat
from onelogin.saml2.constants import OneLogin_Saml2_Constants
from onelogin.saml2.xmlparser import tostring, fromstring
for prefix, url in OneLogin_Saml2_Constants.NSMAP.items():
etree.register_namespace(prefix, url)
[docs]class OneLogin_Saml2_XML(object):
_element_class = type(etree.Element('root'))
_parse_etree = staticmethod(fromstring)
_schema_class = etree.XMLSchema
_text_class = compat.text_types
_bytes_class = compat.bytes_type
_unparse_etree = staticmethod(tostring)
dump = staticmethod(etree.dump)
make_root = staticmethod(etree.Element)
make_child = staticmethod(etree.SubElement)
[docs] @staticmethod
def to_string(xml, **kwargs):
"""
Serialize an element to an encoded string representation of its XML tree.
:param xml: The root node
:type xml: str|bytes|xml.dom.minidom.Document|etree.Element
:returns: string representation of xml
:rtype: string
"""
if isinstance(xml, OneLogin_Saml2_XML._text_class):
return xml
if isinstance(xml, OneLogin_Saml2_XML._element_class):
OneLogin_Saml2_XML.cleanup_namespaces(xml)
return OneLogin_Saml2_XML._unparse_etree(xml, **kwargs)
raise ValueError("unsupported type %r" % type(xml))
[docs] @staticmethod
def to_etree(xml):
"""
Parses an XML document or fragment from a string.
:param xml: the string to parse
:type xml: str|bytes|xml.dom.minidom.Document|etree.Element
:returns: the root node
:rtype: OneLogin_Saml2_XML._element_class
"""
if isinstance(xml, OneLogin_Saml2_XML._element_class):
return xml
if isinstance(xml, OneLogin_Saml2_XML._bytes_class):
return OneLogin_Saml2_XML._parse_etree(xml, forbid_dtd=True, forbid_entities=True)
if isinstance(xml, OneLogin_Saml2_XML._text_class):
return OneLogin_Saml2_XML._parse_etree(compat.to_bytes(xml), forbid_dtd=True, forbid_entities=True)
raise ValueError('unsupported type %r' % type(xml))
[docs] @staticmethod
def validate_xml(xml, schema, debug=False):
"""
Validates a xml against a schema
:param xml: The xml that will be validated
:type xml: str|bytes|xml.dom.minidom.Document|etree.Element
:param schema: The schema
:type schema: string
:param debug: If debug is active, the parse-errors will be showed
:type debug: bool
:returns: Error code or the DomDocument of the xml
:rtype: xml.dom.minidom.Document
"""
assert isinstance(schema, compat.str_type)
try:
xml = OneLogin_Saml2_XML.to_etree(xml)
except Exception as e:
if debug:
print(e)
return 'unloaded_xml'
schema_file = join(dirname(__file__), 'schemas', schema)
with open(schema_file, 'r') as f_schema:
xmlschema = OneLogin_Saml2_XML._schema_class(etree.parse(f_schema))
if not xmlschema.validate(xml):
if debug:
print('Errors validating the metadata: ')
for error in xmlschema.error_log:
print(error.message)
return 'invalid_xml'
return xml
[docs] @staticmethod
def query(dom, query, context=None, tagid=None):
"""
Extracts nodes that match the query from the Element
:param dom: The root of the lxml objet
:type: Element
:param query: Xpath Expresion
:type: string
:param context: Context Node
:type: DOMElement
:param tagid: Tag ID
:type query: String
:returns: The queried nodes
:rtype: list
"""
if context is None:
source = dom
else:
source = context
if tagid is None:
return source.xpath(query, namespaces=OneLogin_Saml2_Constants.NSMAP)
else:
return source.xpath(query, tagid=tagid, namespaces=OneLogin_Saml2_Constants.NSMAP)
[docs] @staticmethod
def cleanup_namespaces(tree_or_element, top_nsmap=None, keep_ns_prefixes=None):
"""
Keeps the xmlns:xs namespace intact when etree.cleanup_namespaces is invoked.
:param tree_or_element: An XML tree or element
:type tree_or_element: etree.Element
:param top_nsmap: A mapping from namespace prefixes to namespace URIs
:type top_nsmap: dict
:param keep_ns_prefixes: List of prefixes that should not be removed as part of the cleanup
:type keep_ns_prefixes: list
:returns: An XML tree or element
:rtype: etree.Element
"""
all_prefixes_to_keep = [
OneLogin_Saml2_Constants.NS_PREFIX_XS,
OneLogin_Saml2_Constants.NS_PREFIX_XSI,
OneLogin_Saml2_Constants.NS_PREFIX_XSD
]
if keep_ns_prefixes:
all_prefixes_to_keep = list(set(all_prefixes_to_keep.extend(keep_ns_prefixes)))
return etree.cleanup_namespaces(tree_or_element, keep_ns_prefixes=all_prefixes_to_keep)
[docs] @staticmethod
def element_text(node):
# Double check, the LXML Parser already removes comments
etree.strip_tags(node, etree.Comment)
return node.text