forked from SAML-Toolkits/python3-saml
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathxml_utils.py
More file actions
176 lines (142 loc) · 5.7 KB
/
xml_utils.py
File metadata and controls
176 lines (142 loc) · 5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# -*- coding: utf-8 -*-
""" OneLogin_Saml2_XML class
Copyright (c) 2010-2018 OneLogin, Inc.
MIT License
Auxiliary class of OneLogin's Python Toolkit.
"""
from os.path import join, dirname
from lxml import etree
from defusedxml.lxml import tostring, fromstring
from onelogin.saml2 import compat
from onelogin.saml2.constants import OneLogin_Saml2_Constants
for prefix, url in OneLogin_Saml2_Constants.NSMAP.items():
etree.register_namespace(prefix, url)
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)
@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))
@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)
if isinstance(xml, OneLogin_Saml2_XML._text_class):
return OneLogin_Saml2_XML._parse_etree(compat.to_bytes(xml), forbid_dtd=True)
raise ValueError('unsupported type %r' % type(xml))
@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
@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)
@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)
@staticmethod
def extract_tag_text(xml, tagname):
open_tag = compat.to_bytes("<%s" % tagname)
close_tag = compat.to_bytes("</%s>" % tagname)
xml = OneLogin_Saml2_XML.to_string(xml)
start = xml.find(open_tag)
assert start != -1
end = xml.find(close_tag, start) + len(close_tag)
assert end != -1
return compat.to_string(xml[start:end])
@staticmethod
def element_text(node):
etree.strip_tags(node, etree.Comment)
return node.text