From d04596ed3a7e5a2b9ffac36c9174cb5c9a83e63a Mon Sep 17 00:00:00 2001 From: sinergia Date: Wed, 28 Aug 2024 16:35:34 -0500 Subject: [PATCH] Fix: SigningTime tz='America/Bogota' --- facho/fe/fe.py | 82 +++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/facho/fe/fe.py b/facho/fe/fe.py index e75dd25..a703606 100644 --- a/facho/fe/fe.py +++ b/facho/fe/fe.py @@ -1,6 +1,5 @@ # This file is part of facho. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. - from ..facho import FachoXML, FachoXMLExtension, LXMLBuilder import uuid import xmlsig @@ -15,6 +14,7 @@ from .data.dian import codelist from . import form from collections import defaultdict # from pathlib import Path +from dateutil import tz from cryptography.hazmat.primitives.serialization import pkcs12 @@ -32,7 +32,7 @@ SCHEME_AGENCY_ATTRS = { POLICY_ID = 'https://facturaelectronica.dian.gov.co/politicadefirma/v2/politicadefirmav2.pdf' POLICY_NAME = u'Política de firma para facturas electrónicas de la República de Colombia.' - +Bogota = tz.gettz('America/Bogota') # NAMESPACES = { # 'atd': 'urn:oasis:names:specification:ubl:schema:xsd:AttachedDocument-2', # 'nomina': 'dian:gov:co:facturaelectronica:NominaIndividual', @@ -158,10 +158,12 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): # #DIAN 1.8.-2021: FAD03 # fachoxml.set_element('./cbc:ProfileID', 'DIAN 2.1: Factura Electrónica de Venta') - fachoxml.set_element('./cbc:ProfileExecutionID', self._tipo_ambiente_int()) + fachoxml.set_element( + './cbc:ProfileExecutionID', self._tipo_ambiente_int()) #DIAN 1.7.-2020: FAB36 - fachoxml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode', - self._get_qrcode(cufe)) + fachoxml.set_element( + './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:QRCode', + self._get_qrcode(cufe)) def issue_time(self, datetime_): return datetime_.strftime('%H:%M:%S-05:00') @@ -177,7 +179,8 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): build_vars['HoraFac'] = self.issue_time(invoice.invoice_issue) # PAG 601 build_vars['ValorBruto'] = invoice.invoice_legal_monetary_total.line_extension_amount - build_vars['ValorTotalPagar'] = invoice.invoice_legal_monetary_total.payable_amount + build_vars['ValorTotalPagar' + ] = invoice.invoice_legal_monetary_total.payable_amount ValorImpuestoPara = defaultdict(lambda: form.Amount(0.0)) build_vars['CodImpuesto1'] = '01' build_vars['CodImpuesto2'] = '04' @@ -207,7 +210,8 @@ class DianXMLExtensionCUDFE(FachoXMLExtension): class DianXMLExtensionCUFE(DianXMLExtensionCUDFE): - def __init__(self, invoice, clave_tecnica = '', tipo_ambiente = AMBIENTE_PRUEBAS): + def __init__( + self, invoice, clave_tecnica='', tipo_ambiente=AMBIENTE_PRUEBAS): self.tipo_ambiente = tipo_ambiente self.clave_tecnica = clave_tecnica self.invoice = invoice @@ -243,6 +247,7 @@ class DianXMLExtensionCUFE(DianXMLExtensionCUDFE): '%d' % build_vars['TipoAmb'], ] + class DianXMLExtensionCUDE(DianXMLExtensionCUDFE): def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS): self.tipo_ambiente = tipo_ambiente @@ -280,6 +285,7 @@ class DianXMLExtensionCUDE(DianXMLExtensionCUDFE): '%d' % build_vars['TipoAmb'], ] + class DianXMLExtensionCUDS(DianXMLExtensionCUDFE): def __init__(self, invoice, software_pin, tipo_ambiente = AMBIENTE_PRUEBAS): self.tipo_ambiente = tipo_ambiente @@ -312,7 +318,8 @@ class DianXMLExtensionCUDS(DianXMLExtensionCUDFE): '%s' % build_vars['Software-PIN'], '%d' % build_vars['TipoAmb'], ] - + + class DianXMLExtensionSoftwareProvider(FachoXMLExtension): # RESOLUCION 0004: pagina 108 @@ -322,7 +329,8 @@ class DianXMLExtensionSoftwareProvider(FachoXMLExtension): self.id_software = id_software def build(self, fexml): - software_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider') + software_provider = fexml.fragment( + './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:SoftwareProvider') provider_id_attrs = SCHEME_AGENCY_ATTRS.copy() provider_id_attrs.update({'schemeID': self.dv}) #DIAN 1.7.-2020: FAB23 @@ -367,11 +375,13 @@ class DianXMLExtensionSigner: self._localpolicy = localpolicy if passphrase: self._passphrase = passphrase.encode('utf-8') - + return self def _element_extension_content(self, fachoxml): - return fachoxml.builder.xpath(fachoxml.root, './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent') + return fachoxml.builder.xpath( + fachoxml.root, + './ext:UBLExtensions/ext:UBLExtension[2]/ext:ExtensionContent') def sign_xml_string(self, document): xml = LXMLBuilder.from_string(document) @@ -400,14 +410,16 @@ class DianXMLExtensionSigner: id_keyinfo = "xmldsig-%s-KeyInfo" % (id_uuid) xmlsig.template.add_reference( - signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_keyinfo), name="xmldsig-%s-ref1" % (id_uuid), + signature, xmlsig.constants.TransformSha256, uri="#%s" % ( + id_keyinfo), name="xmldsig-%s-ref1" % (id_uuid), ) ki = xmlsig.template.ensure_key_info(signature, name=id_keyinfo) data = xmlsig.template.add_x509_data(ki) xmlsig.template.x509_data_add_certificate(data) xmlsig.template.add_key_value(ki) - qualifying = xades.template.create_qualifying_properties(signature, 'XadesObjects', 'xades') + qualifying = xades.template.create_qualifying_properties( + signature, 'XadesObjects', 'xades') xades.utils.ensure_id(qualifying) id_props = "xmldsig-%s-signedprops" % (id_uuid) @@ -415,10 +427,12 @@ class DianXMLExtensionSigner: signature, xmlsig.constants.TransformSha256, uri="#%s" % (id_props), uri_type="http://uri.etsi.org/01903#SignedProperties" ) - xmlsig.template.add_transform(props_ref, xmlsig.constants.TransformInclC14N) + xmlsig.template.add_transform( + props_ref, xmlsig.constants.TransformInclC14N) # TODO assert with http://www.sic.gov.co/hora-legal-colombiana - props = xades.template.create_signed_properties(qualifying, name=id_props, datetime=datetime.now()) + props = xades.template.create_signed_properties( + qualifying, name=id_props, datetime=datetime.now(tz=Bogota)) xades.template.add_claimed_role(props, "supplier") policy = xades.policy.GenericPolicyId( @@ -449,29 +463,28 @@ class DianXMLExtensionSigner: extcontent = self._element_extension_content(fachoxml) fachoxml.append_element(extcontent, signature) - + class DianXMLExtensionAuthorizationProvider(FachoXMLExtension): # RESOLUCION 0004: pagina 176 def build(self, fexml): attrs = {'schemeID': '4', 'schemeName': '31'} attrs.update(SCHEME_AGENCY_ATTRS) - authorization_provider = fexml.fragment('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:AuthorizationProvider') authorization_provider.set_element('./sts:AuthorizationProviderID', '800197268', **attrs) - class DianXMLExtensionInvoiceSource(FachoXMLExtension): # CAB13 def build(self, fexml): dian_path = '/fe:CreditNote/ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode' - fexml.set_element(dian_path, 'CO', - listAgencyID="6", - listAgencyName="United Nations Economic Commission for Europe", - listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1") + fexml.set_element( + dian_path, 'CO', + listAgencyID="6", + listAgencyName="United Nations Economic Commission for Europe", + listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1") class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension): @@ -501,16 +514,15 @@ class DianXMLExtensionInvoiceAuthorization(FachoXMLExtension): invoice_control.set_element('/sts:InvoiceControl/sts:AuthorizedInvoices/sts:To', self.to) - fexml.set_element('./ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode', - 'CO', - #DIAN 1.7.-2020: FAB15 - listAgencyID="6", - #DIAN 1.7.-2020: FAB16 - listAgencyName="United Nations Economic Commission for Europe", - #DIAN 1.7.-2020: FAB17 - listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1" - ) - + fexml.set_element( + './ext:UBLExtensions/ext:UBLExtension/ext:ExtensionContent/sts:DianExtensions/sts:InvoiceSource/cbc:IdentificationCode', + 'CO', + # DIAN 1.7.-2020: FAB15 + listAgencyID="6", + # DIAN 1.7.-2020: FAB16 + listAgencyName="United Nations Economic Commission for Europe", + # DIAN 1.7.-2020: FAB17 + listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.1") class DianZIP: @@ -519,7 +531,8 @@ class DianZIP: MAX_FILES = 50 def __init__(self, file_like): - self.zipfile = zipfile.ZipFile(file_like, mode='w', compression=zipfile.ZIP_DEFLATED) + self.zipfile = zipfile.ZipFile( + file_like, mode='w', compression=zipfile.ZIP_DEFLATED) self.num_files = 0 def add_xml(self, name, xml_data): @@ -540,7 +553,6 @@ class DianZIP: def __enter__(self): """ Facilita el uso de esta manera: - f = open('xxx', 'rb') with DianZIP(f) as zip: zip.add_invoice_xml('name', 'data xml') @@ -563,7 +575,7 @@ class DianXMLExtensionSignerVerifier: def verify_string(self, document): # Obtener FachoXML xml = LXMLBuilder.from_string(document) - fachoxml = FachoXML(xml,nsmap=NAMESPACES) + fachoxml = FachoXML(xml, nsmap=NAMESPACES) # Obtener Signature signature = fachoxml.builder.xpath(fachoxml.root, '//ds:Signature')