Browse Source

Modificando para a biblioteca signxml a assinatura

tags/0.1.5
Danimar Ribeiro 10 years ago
parent
commit
41e219ac6e
  1. 4
      pytrustnfe/servicos/NfeStatusServico.py
  2. 5
      pytrustnfe/servicos/RecepcaoEvento.py
  3. 4
      pytrustnfe/servicos/Validacao.py
  4. 54
      pytrustnfe/servicos/assinatura.py
  5. 22
      pytrustnfe/servicos/comunicacao.py
  6. 23
      pytrustnfe/servicos/nfe_autorizacao.py
  7. 1
      pytrustnfe/utils.py
  8. 76
      pytrustnfe/xml/DynamicXml.py
  9. 22
      pytrustnfe/xml/__init__.py
  10. 58
      pytrustnfe/xml/filters.py
  11. 174
      pytrustnfe/xml/nfeEnv.xml
  12. 1
      setup.py

4
pytrustnfe/servicos/NfeStatusServico.py

@ -4,9 +4,7 @@ Created on 21/06/2015
@author: danimar
'''
from pytrustnfe.servicos.Comunicacao import Comunicacao
from pytrustnfe.xml import DynamicXml
from pytrustnfe.servicos.comunicacao import Comunicacao
class NfeStatusServico(Comunicacao):

5
pytrustnfe/servicos/RecepcaoEvento.py

@ -5,11 +5,10 @@ Created on 21/06/2015
@author: danimar
'''
from pytrustnfe.servicos.Comunicacao import Comunicacao
from pytrustnfe.xml import DynamicXml
class RecepcaoEvento(Comunicacao):
def registrar_evento(self, evento):
xml = self._validar_xml(recibo)
@ -18,4 +17,4 @@ class RecepcaoEvento(Comunicacao):
self.web_service = 'ws/recepcaoevento/recepcaoevento.asmx'
self.url = 'nfe.sefazrs.rs.gov.br'
return self._executar_consulta(xml)
return self._executar_consulta(xml)

4
pytrustnfe/servicos/Validacao.py

@ -8,8 +8,8 @@ def validar_schema():
arquivo_esquema = ''
xml = tira_abertura(self.xml).encode('utf-8')
esquema = etree.XMLSchema(etree.parse(arquivo_esquema))
esquema = etree.XMLSchema(etree.parse(arquivo_esquema))
esquema.validate(etree.fromstring(xml))
namespace = '{http://www.portalfiscal.inf.br/nfe}'
return "\n".join([x.message.replace(namespace, '') for x in esquema.error_log])
return "\n".join([x.message.replace(namespace, '') for x in esquema.error_log])

54
pytrustnfe/servicos/Assinatura.py → pytrustnfe/servicos/assinatura.py

@ -1,14 +1,57 @@
#coding=utf-8
# coding=utf-8
'''
Created on Jun 14, 2015
@author: danimar
'''
import xmlsec, libxml2
import xmlsec
import libxml2
import os.path
from signxml import xmldsig
from signxml import methods
from lxml import etree
NAMESPACE_SIG = 'http://www.w3.org/2000/09/xmldsig#'
def recursively_empty(e):
if e.text:
return False
return all((recursively_empty(c) for c in e.iterchildren()))
def assinar(xml, cert, key, reference, pfx, senha):
context = etree.iterwalk(xml)
for action, elem in context:
parent = elem.getparent()
if recursively_empty(elem):
parent.remove(elem)
element = xml.find('{' + xml.nsmap[None] + '}NFe')
not_signed = etree.tostring(element)
not_signed = "<!DOCTYPE NFe [<!ATTLIST infNFe Id ID #IMPLIED>]>" + \
not_signed
signer = xmldsig(element, digest_algorithm=u'sha1')
ns = {}
ns[None] = signer.namespaces['ds']
signer.namespaces = ns
signed_root = signer.sign(
key=str(key), cert=cert, reference_uri=reference,
algorithm="rsa-sha1", method=methods.enveloped,
c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315')
xmldsig(signed_root, digest_algorithm=u'sha1').verify(x509_cert=cert)
# signature = Assinatura(pfx, senha)
# xmlsec = signature.assina_xml(not_signed, reference)
# xmlsec = xmlsec.replace("""<!DOCTYPE NFe [
# <!ATTLIST infNFe Id ID #IMPLIED>
# ]>\n""", "")
return etree.tostring(signed_root)
# , xmlsec
class Assinatura(object):
def __init__(self, arquivo, senha):
@ -34,14 +77,13 @@ class Assinatura(object):
libxml2.cleanupParser()
def assina_xml(self, xml):
def assina_xml(self, xml, reference):
self._checar_certificado()
self._inicializar_cripto()
try:
doc_xml = libxml2.parseMemory(xml.encode('utf-8'),
len(xml.encode('utf-8')))
import ipdb; ipdb.set_trace()
signNode = xmlsec.TmplSignature(doc_xml,
xmlsec.transformInclC14NId(),
xmlsec.transformRsaSha1Id(), None)
@ -49,7 +91,7 @@ class Assinatura(object):
doc_xml.getRootElement().addChild(signNode)
refNode = signNode.addReference(
xmlsec.transformSha1Id(),
None, '#NFe43150602261542000143550010000000761792265342', None)
None, reference, None)
refNode.addTransform(xmlsec.transformEnvelopedId())
refNode.addTransform(xmlsec.transformInclC14NId())

22
pytrustnfe/servicos/Comunicacao.py → pytrustnfe/servicos/comunicacao.py

@ -7,7 +7,6 @@ Created on Jun 14, 2015
from lxml import objectify
from uuid import uuid4
from pytrustnfe.xml.DynamicXml import DynamicXml
from pytrustnfe.HttpClient import HttpClient
from pytrustnfe.Certificado import converte_pfx_pem
@ -25,9 +24,9 @@ class Comunicacao(object):
metodo = ''
tag_retorno = ''
def __init__(self, certificado, senha):
self.certificado = certificado
self.senha = senha
def __init__(self, cert, key):
self.certificado = cert
self.senha = key
def _soap_xml(self, body):
xml = '''<?xml version="1.0" encoding="utf-8"?>
@ -66,20 +65,15 @@ class Comunicacao(object):
assert self.metodo != '', "Método não configurado"
assert self.tag_retorno != '', "Tag de retorno não configurado"
def _validar_xml(self, obj):
xml = None
if isinstance(obj, DynamicXml):
xml = obj.render()
if isinstance(obj, basestring):
xml = obj
assert xml is not None, "Objeto deve ser do tipo DynamicXml ou string"
return xml
def _validar_nfe(self, obj):
if not isinstance(obj, dict):
raise u"Objeto deve ser um dicionário de valores"
def _executar_consulta(self, xmlEnviar):
self._validar_dados()
chave, certificado = self._preparar_temp_pem()
# chave, certificado = self._preparar_temp_pem()
client = HttpClient(self.url, chave, certificado)
client = HttpClient(self.url, self.certificado, self.senha)
soap_xml = self._soap_xml(xmlEnviar)
xml_retorno = client.post_xml(self.web_service, soap_xml)

23
pytrustnfe/servicos/nfe_autorizacao.py

@ -4,17 +4,23 @@ Created on 21/06/2015
@author: danimar
'''
from pytrustnfe.servicos.Comunicacao import Comunicacao
from lxml import etree
from pytrustnfe.servicos.comunicacao import Comunicacao
from pytrustnfe import utils
from pytrustnfe.xml import render_xml
from pytrustnfe.servicos.assinatura import assinar
class NfeAutorizacao(Comunicacao):
def __init__(self, certificado, senha):
def __init__(self, cert, key, certificado, senha):
Comunicacao.__init__(self, certificado, senha)
self.cert = cert
self.key = key
def autorizar_nfe(self, nfe):
xml = self._validar_xml(nfe)
self._validar_nfe(nfe)
xml = render_xml('nfeEnv.xml', **nfe)
self.metodo = 'NFeAutorizacao'
self.tag_retorno = 'retEnviNFe'
@ -23,8 +29,13 @@ class NfeAutorizacao(Comunicacao):
return self._executar_consulta(xml)
def autorizar_nfe_e_recibo(self, nfe):
xml = self._validar_xml(nfe)
def autorizar_nfe_e_recibo(self, nfe, id):
self._validar_nfe(nfe)
xml = render_xml('nfeEnv.xml', **nfe)
return assinar(xml, self.cert, self.key,
'#%s' % id,
self.certificado, self.senha)
self.metodo = 'NFeAutorizacao'
self.tag_retorno = 'retEnviNFe'
@ -34,7 +45,7 @@ class NfeAutorizacao(Comunicacao):
xml_recibo, recibo = self._executar_consulta(xml)
consulta_recibo = utils.gerar_consulta_recibo(recibo)
xml = self._validar_xml(nfe)
self._validar_nfe(nfe)
self.metodo = 'NFeRetAutorizacao'
self.tag_retorno = 'retConsReciNFe'

1
pytrustnfe/utils.py

@ -6,7 +6,6 @@ Created on 22/06/2015
'''
from datetime import date, datetime
from pytrustnfe.ChaveNFe import ChaveNFe
from pytrustnfe.xml.DynamicXml import DynamicXml
def date_tostring(data):

76
pytrustnfe/xml/DynamicXml.py

@ -1,76 +0,0 @@
# coding=utf-8
'''
Created on Jun 17, 2015
@author: danimar
'''
import xml.etree.ElementTree as ET
from lxml.etree import Element, tostring
from __builtin__ import str
class DynamicXml(object):
def __getattr__(self, name):
try:
return object.__getattribute__(self, name)
except:
self.__setattr__(name, None)
return object.__getattribute__(self, name)
def __setattr__(self, obj, val):
if(obj == "value" or obj == "atributos" or obj == "_indice"):
object.__setattr__(self, obj, val)
else:
self._indice = self._indice + 1
object.__setattr__(self, obj, DynamicXml(val, self._indice))
def __init__(self, value, indice=0):
self.value = unicode(value, 'utf-8') if isinstance(value,
str) else value
self.atributos = {}
self._indice = indice
def __str__(self):
return unicode(self.value)
def __call__(self, *args, **kw):
if(len(kw) > 0):
self.atributos = kw
if(len(args) > 0):
self.value = args[0]
else:
return self.value
def __getitem__(self, i):
if not isinstance(self.value, list):
self.value = []
if(i + 1 > len(self.value)):
self.value.append(DynamicXml(None))
return self.value[i]
def render(self, pretty_print=False):
root = Element(self.value)
self._gerar_xml(root, self)
return tostring(root, pretty_print=pretty_print)
def _gerar_xml(self, xml, objeto):
items = sorted(
objeto.__dict__.items(),
key=lambda x: x[1]._indice if isinstance(x[1], DynamicXml) else 0
)
for attr, value in items:
if(attr != "value" and attr != "atributos" and attr != "_indice"):
if isinstance(value(), list):
for item in value():
sub = ET.SubElement(xml, attr)
self._gerar_xml(sub, item)
else:
sub = ET.SubElement(xml, attr)
if(unicode(value) != u"None"):
sub.text = unicode(value)
self._gerar_xml(sub, value)
elif(attr == "atributos"):
for atr, val in value.items():
xml.set(atr.replace("__", ":"), str(val))

22
pytrustnfe/xml/__init__.py

@ -0,0 +1,22 @@
import os.path
from lxml import etree
from jinja2 import Environment, FileSystemLoader
from . import filters
def render_xml(template_name, **nfe):
path = os.path.dirname(__file__)
env = Environment(
loader=FileSystemLoader(path), extensions=['jinja2.ext.with_'])
env.filters["normalize"] = filters.normalize_str
env.filters["format_percent"] = filters.format_percent
env.filters["format_datetime"] = filters.format_datetime
env.filters["format_date"] = filters.format_date
template = env.get_template(template_name)
xml = template.render(**nfe)
xml = xml.replace('&', '&amp;')
parser = etree.XMLParser(remove_blank_text=True, remove_comments=True)
return etree.fromstring(xml, parser=parser)

58
pytrustnfe/xml/filters.py

@ -0,0 +1,58 @@
# coding=utf-8
from decimal import Decimal
from datetime import datetime
import dateutil.parser as parser_dateutil
from unicodedata import normalize
def normalize_str(string):
"""
Remove special characters and return the ascii string
"""
if string:
if not isinstance(string, unicode):
string = unicode(string, 'utf-8', 'replace')
string = string.encode('utf-8')
return normalize(
'NFKD', string.decode('utf-8')).encode('ASCII', 'ignore')
return ''
def format_percent(value):
if value:
return Decimal(value) / 100
def format_datetime(value):
"""
Format datetime
"""
dt_format = '%Y-%m-%dT%H:%M:%I'
if isinstance(value, datetime):
return value.strftime(dt_format)
try:
value = parser_dateutil.parse(value).strftime(dt_format)
except AttributeError:
pass
return value
def format_date(value):
"""
Format date
"""
dt_format = '%Y-%m-%d'
if isinstance(value, datetime):
return value.strftime(dt_format)
try:
value = parser_dateutil.parse(value).strftime(dt_format)
except AttributeError:
pass
return value

174
pytrustnfe/xml/nfeEnv.xml

@ -0,0 +1,174 @@
<enviNFe xmlns="http://www.portalfiscal.inf.br/nfe" versao="3.10">
<idLote>{{ idLote }}</idLote>
<indSinc>{{ indSinc }}</indSinc>
{% for NFe in NFes %}
<NFe xmlns="http://www.portalfiscal.inf.br/nfe">
<infNFe versao="3.10" Id="{{ NFe.infNFe.Id }}">
<ide>
{% with ide = NFe.infNFe.ide %}
<cUF>{{ ide.cUF }}</cUF>
<cNF>{{ ide.cNF }}</cNF>
<natOp>{{ ide.natOp }}</natOp>
<indPag>{{ ide.indPag }}</indPag>
<mod>{{ ide.mod }}</mod>
<serie>{{ ide.serie }}</serie>
<nNF>{{ ide.nNF }}</nNF>
<dhEmi>{{ ide.dhEmi }}</dhEmi>
<dhSaiEnt>{{ ide.dhSaiEnt }}</dhSaiEnt>
<tpNF>{{ ide.tpNF }}</tpNF>
<idDest>{{ ide.idDest }}</idDest>
<cMunFG>{{ ide.cMunFG }}</cMunFG>
<tpImp>{{ ide.tpImp }}</tpImp>
<tpEmis>{{ ide.tpEmis }}</tpEmis>
<cDV>{{ ide.cDV }}</cDV>
<tpAmb>{{ ide.tpAmb }}</tpAmb>
<finNFe>{{ ide.finNFe }}</finNFe>
<indFinal>{{ ide.indFinal }}</indFinal>
<indPres>{{ ide.indPres }}</indPres>
<procEmi>{{ ide.procEmi }}</procEmi>
<verProc>Odoo Brasil 9.0</verProc>
{% endwith %}
</ide>
<emit>
{% with emit = NFe.infNFe.emit %}
<CNPJ>{{ emit.CNPJ }}</CNPJ>
<xNome>{{ emit.xNome }}</xNome>
<xFant>{{ emit.xFant }}</xFant>
<enderEmit>
<xLgr>{{ emit.enderEmit.xLgr }}</xLgr>
<nro>{{ emit.enderEmit.nro }}</nro>
<xBairro>{{ emit.enderEmit.xBairro }}</xBairro>
<cMun>{{ emit.enderEmit.cMun }}</cMun>
<xMun>{{ emit.enderEmit.xMun }}</xMun>
<UF>{{ emit.enderEmit.UF }}</UF>
<CEP>{{ emit.enderEmit.CEP }}</CEP>
<cPais>{{ emit.enderEmit.cPais }}</cPais>
<xPais>{{ emit.enderEmit.xPais }}</xPais>
<fone>{{ emit.enderEmit.fone }}</fone>
</enderEmit>
<IE>{{ emit.IE }}</IE>
<CRT>{{ emit.CRT }}</CRT>
{% endwith %}
</emit>
<dest>
{% with dest = NFe.infNFe.dest %}
<CNPJ>{{ dest.CNPJ }}</CNPJ>
<CPF>{{ dest.CPF }}</CPF>
<xNome>{{ dest.xNome }}</xNome>
<enderDest>
<xLgr>{{ dest.enderDest.xLgr }}</xLgr>
<nro>{{ dest.enderDest.nro }}</nro>
<xBairro>{{ dest.enderDest.xBairro }}</xBairro>
<cMun>{{ dest.enderDest.cMun }}</cMun>
<xMun>{{ dest.enderDest.xMun }}</xMun>
<UF>{{ dest.enderDest.UF }}</UF>
<CEP>{{ dest.enderDest.CEP }}</CEP>
<cPais>{{ dest.enderDest.cPais }}</cPais>
<xPais>{{ dest.enderDest.xPais }}</xPais>
<fone>{{ dest.enderDest.fone }}</fone>
</enderDest>
<indIEDest>{{ dest.indIEDest }}</indIEDest>
<IE>{{ dest.IE }}</IE>
{% endwith %}
</dest>
{% for det in NFe.infNFe.detalhes %}
<det nItem="1">
<prod>
{% with prod = det.prod %}
<cProd>{{ prod.cProd }}</cProd>
<cEAN>{{ prod.cEAN }}</cEAN>
<xProd>{{ prod.xProd }}</xProd>
<NCM>{{ prod.NCM }}</NCM>
<CFOP>{{ prod.CFOP }}</CFOP>
<uCom>{{ prod.uCom }}</uCom>
<qCom>{{ prod.qCom }}</qCom>
<vUnCom>{{ prod.vUnCom }}</vUnCom>
<vProd>{{ prod.vProd }}</vProd>
<cEANTrib>{{ prod.cEANTrib }}</cEANTrib>
<uTrib>{{ prod.uTrib }}</uTrib>
<qTrib>{{ prod.qTrib }}</qTrib>
<vUnTrib>{{ prod.vUnTrib }}</vUnTrib>
<indTot>{{ prod.indTot }}</indTot>
{% endwith %}
</prod>
<imposto>
{% with imposto = det.imposto %}
<vTotTrib>{{ imposto.vTotTrib }}</vTotTrib>
<ICMS>
<ICMS00>
<orig>{{ imposto.ICMS.ICMS00.orig }}</orig>
<CST>{{ imposto.ICMS.ICMS00.CST }}</CST>
<modBC>{{ imposto.ICMS.ICMS00.modBC }}</modBC>
<vBC>{{ imposto.ICMS.ICMS00.vBC }}</vBC>
<pICMS>{{ imposto.ICMS.ICMS00.pICMS }}</pICMS>
<vICMS>{{ imposto.ICMS.ICMS00.vICMS }}</vICMS>
</ICMS00>
</ICMS>
<IPI>
<cEnq>{{ imposto.IPI.cEnq }}</cEnq>
<IPITrib>
<CST>{{ imposto.IPI.IPITrib.CST }}</CST>
<vBC>{{ imposto.IPI.IPITrib.vBC }}</vBC>
<pIPI>{{ imposto.IPI.IPITrib.pIPI }}</pIPI>
<vIPI>{{ imposto.IPI.IPITrib.vIPI }}</vIPI>
</IPITrib>
</IPI>
<PIS>
<PISAliq>
<CST>{{ imposto.PIS.PISAliq.CST }}</CST>
<vBC>{{ imposto.PIS.PISAliq.vBC }}</vBC>
<pPIS>{{ imposto.PIS.PISAliq.pPIS }}</pPIS>
<vPIS>{{ imposto.PIS.PISAliq.vPIS }}</vPIS>
</PISAliq>
</PIS>
<COFINS>
<COFINSAliq>
<CST>{{ imposto.COFINS.COFINSAliq.CST }}</CST>
<vBC>{{ imposto.COFINS.COFINSAliq.vBC }}</vBC>
<pCOFINS>{{ imposto.COFINS.COFINSAliq.pCOFINS }}</pCOFINS>
<vCOFINS>{{ imposto.COFINS.COFINSAliq.vCOFINS }}</vCOFINS>
</COFINSAliq>
</COFINS>
{% endwith %}
</imposto>
</det>
{% endfor %}
<total>
{% with total = NFe.infNFe.total %}
<ICMSTot>
<vBC>{{ total.vBC }}</vBC>
<vICMS>{{ total.vICMS }}</vICMS>
<vICMSDeson>{{ total.vICMSDeson }}</vICMSDeson>
<vBCST>{{ total.vBCST }}</vBCST>
<vST>{{ total.vST }}</vST>
<vProd>{{ total.vProd }}</vProd>
<vFrete>{{ total.vFrete }}</vFrete>
<vSeg>{{ total.vSeg }}</vSeg>
<vDesc>{{ total.vDesc }}</vDesc>
<vII>{{ total.vII }}</vII>
<vIPI>{{ total.vIPI }}</vIPI>
<vPIS>{{ total.vPIS }}</vPIS>
<vCOFINS>{{ total.vCOFINS }}</vCOFINS>
<vOutro>{{ total.vOutro }}</vOutro>
<vNF>{{ total.vNF }}</vNF>
<vTotTrib>{{ total.vTotTrib }}</vTotTrib>
</ICMSTot>
{% endwith %}
</total>
<transp>
<modFrete>{{ NFe.infNFe.transp.modFrete }}</modFrete>
</transp>
<cobr>
<dup>
<nDup>339/1</nDup>
<dVenc>2016-06-02</dVenc>
<vDup>8611.76</vDup>
</dup>
</cobr>
<infAdic>
<infCpl>{{ NFe.infNFe.infAdic.infCpl }}</infCpl>
</infAdic>
</infNFe>
</NFe>
{% endfor %}
</enviNFe>

1
setup.py

@ -18,6 +18,7 @@ setup(
'Topic :: Software Development :: Libraries :: Python Modules',
],
packages=find_packages(exclude=['*test*']),
package_data={'pytrustnfe': ['xml/*xml']},
url='https://github.com/danimaribeiro/PyNfeTrust',
license='LGPL-v2.1+',
description='PyNfeTrust é uma biblioteca para envio de NF-e',

Loading…
Cancel
Save