Odoo18-Base/addons/l10n_es_edi_facturae/xml_utils.py

66 lines
2.7 KiB
Python
Raw Permalink Normal View History

2025-01-06 10:57:38 +07:00
import base64
import hashlib
from copy import deepcopy
from lxml import etree
from odoo.exceptions import UserError
NS_MAP = {'ds': "http://www.w3.org/2000/09/xmldsig#"}
def _canonicalize_node(node):
"""
Returns the canonical (C14N 1.0, without comments, non exclusive) representation of node.
Speficied in: https://www.w3.org/TR/2001/REC-xml-c14n-20010315
Required for computing digests and signatures.
Returns an UTF-8 encoded bytes string.
"""
return etree.tostring(node, method="c14n", with_comments=False, exclusive=False)
def _get_uri(uri, reference, base_uri=""):
"""
Returns the content within `reference` that is identified by `uri`.
Canonicalization is used to convert node reference to an octet stream.
- URIs starting with # are same-document references
https://www.w3.org/TR/xmldsig-core/#sec-URI
- Empty URIs point to the whole document tree, without the signature
https://www.w3.org/TR/xmldsig-core/#sec-EnvelopedSignature
Returns an UTF-8 encoded bytes string.
"""
node = deepcopy(reference.getroottree().getroot())
if uri == base_uri:
# Base URI: whole document, without signature (default is empty URI)
for signature in node.xpath('ds:Signature', namespaces=NS_MAP):
if signature.tail:
# move the tail to the previous node or to the parent
if (previous := signature.getprevious()) is not None:
previous.tail = "".join([previous.tail or "", signature.tail or ""])
else:
signature.getparent().text = "".join([signature.getparent().text or "", signature.tail or ""])
node.remove(signature)
return _canonicalize_node(node)
if uri.startswith("#"):
path = "//*[@*[local-name() = '{}' ]=$uri]"
results = node.xpath(path.format("Id"), uri=uri.lstrip("#")) # case-sensitive 'Id'
if len(results) == 1:
return _canonicalize_node(results[0])
if len(results) > 1:
raise UserError(f"Ambiguous reference URI {uri} resolved to {len(results)} nodes")
raise UserError(f'URI {uri} not found')
def _reference_digests(node, base_uri=""):
"""
Processes the references from node and computes their digest values as specified in
https://www.w3.org/TR/xmldsig-core/#sec-DigestMethod
https://www.w3.org/TR/xmldsig-core/#sec-DigestValue
"""
for reference in node.findall("ds:Reference", namespaces=NS_MAP):
ref_node = _get_uri(reference.get("URI", ""), reference, base_uri=base_uri)
lib = hashlib.new("sha256", ref_node)
reference.find("ds:DigestValue", namespaces=NS_MAP).text = base64.b64encode(lib.digest())