Skip to content

Commit e9e287e

Browse files
Tipagem do AssinaturaA1.assinar() e melhorias para a assinatura (#405)
* Melhorias em tratamento de erros * Tipagem * Testes unitários * Compatibilidade com Python 3.8 e 3.9 * Remoção de TODO * Refatoração * Refatoração * Atualização: actions/runner-images#11101
1 parent f2d768f commit e9e287e

File tree

7 files changed

+159
-122
lines changed

7 files changed

+159
-122
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: PyNFe CI
22
on: [pull_request, push]
33
jobs:
44
build:
5-
runs-on: ubuntu-20.04
5+
runs-on: ubuntu-latest
66
strategy:
77
fail-fast: false
88
matrix:

pynfe/entidades/certificado.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,13 @@ def separar_arquivo(self, senha, caminho=False):
4646
try:
4747
with open(self.caminho_arquivo, "rb") as cert_arquivo:
4848
cert_conteudo = cert_arquivo.read()
49-
except (PermissionError, FileNotFoundError) as exc:
50-
raise Exception(
51-
"""Falha ao abrir arquivo do certificado digital A1.
52-
Verifique local e permissoes do arquivo."""
49+
except FileNotFoundError as exc:
50+
raise FileNotFoundError(
51+
"Falha ao abrir arquivo do certificado digital A1. Verifique o local do arquivo."
52+
) from exc
53+
except PermissionError as exc:
54+
raise PermissionError(
55+
"Falha ao abrir arquivo do certificado digital A1. Verifique as permissoes do arquivo."
5356
) from exc
5457
except Exception as exc:
5558
raise Exception(

pynfe/processamento/assinatura.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22
import signxml
3+
from typing import Union
34

45
from pynfe.entidades import CertificadoA1
56
from pynfe.utils import CustomXMLSigner, etree, remover_acentos
@@ -26,7 +27,7 @@ class AssinaturaA1(Assinatura):
2627
def __init__(self, certificado, senha):
2728
self.key, self.cert = CertificadoA1(certificado).separar_arquivo(senha)
2829

29-
def assinar(self, xml, retorna_string=False):
30+
def assinar(self, xml: etree._Element, retorna_string=False) -> Union[str, etree._Element]:
3031
# busca tag que tem id(reference_uri), logo nao importa se tem namespace
3132
reference = xml.find(".//*[@Id]").attrib["Id"]
3233

tests/test_certificadoA1.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,15 @@ def test_assinatura_com_caminho(self):
2525
self.a1.excluir()
2626

2727
def test_exception_certificado_filenotfounderror(self):
28-
with self.assertRaises(Exception) as context:
28+
with self.assertRaises(FileNotFoundError) as context:
2929
self.a1 = CertificadoA1(self.certificado_incorreto)
3030
self.a1.separar_arquivo(
3131
senha=self.senha_incorreto, caminho=self.certificado_incorreto
3232
)
3333
self.assertEqual(
3434
str(context.exception),
3535
(
36-
"""Falha ao abrir arquivo do certificado digital A1.
37-
Verifique local e permissoes do arquivo."""
36+
"Falha ao abrir arquivo do certificado digital A1. Verifique o local do arquivo."
3837
),
3938
)
4039

tests/test_nfse_serializacao.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pynfe.entidades.emitente import Emitente
55
from pynfe.entidades.notafiscal import NotaFiscalServico
66
from pynfe.entidades.servico import Servico
7+
from pynfe.processamento.assinatura import AssinaturaA1
78
from pynfe.processamento.serializacao import SerializacaoNfse
89
from pynfe.utils import obter_codigo_por_municipio
910
from pynfe.utils.flags import (
@@ -12,6 +13,7 @@
1213
from decimal import Decimal
1314
import datetime
1415
import re
16+
from lxml import etree
1517

1618

1719
class SerializacaoNFSeConfigTest:
@@ -22,14 +24,7 @@ def __init__(self, certificado: str, senha: str, homologacao: bool):
2224

2325

2426
class SerializacaoNFSeTest:
25-
26-
@staticmethod
27-
def get_config() -> SerializacaoNFSeConfigTest:
28-
return SerializacaoNFSeConfigTest(
29-
"./tests/certificado.pfx",
30-
bytes("123456", "utf-8"),
31-
True
32-
)
27+
data_hora = "2025-04-10T09:45:29"
3328

3429
@staticmethod
3530
def get_notafiscal_servico() -> NotaFiscalServico:
@@ -61,7 +56,8 @@ def get_notafiscal_servico() -> NotaFiscalServico:
6156

6257
return NotaFiscalServico(
6358
identificador='50',
64-
data_emissao=datetime.datetime.now(),
59+
data_emissao=datetime.datetime.strptime(
60+
SerializacaoNFSeTest.data_hora, "%Y-%m-%dT%H:%M:%S"),
6561
servico=servico,
6662
emitente=SerializacaoNFSeTest._get_emitente(),
6763
cliente=SerializacaoNFSeTest._get_destinatario(),
@@ -84,16 +80,19 @@ def serializa_nfse(nfse: NotaFiscalServico, autorizador: str) -> str:
8480
xml = serializador.gerar(nfse)
8581
return xml
8682

87-
# TODO: assinatura digital
88-
# @staticmethod
89-
# def assina_xml(config: SerializacaoNFSeConfigTest, nfse_xml: str):
90-
# a1 = AssinaturaA1(config.certificado, config.senha)
91-
# xml = a1.assinar(nfse_xml, True)
92-
# return xml
83+
@staticmethod
84+
def assina_xml(xml: str) -> str:
85+
config = SerializacaoNFSeTest._get_config()
86+
nfse = etree.fromstring(xml)
87+
88+
a1 = AssinaturaA1(config.certificado, config.senha)
89+
xml_assinado = a1.assinar(nfse, True)
90+
91+
return xml_assinado
9392

9493
@staticmethod
9594
def strip_xml(xml: str) -> str:
96-
return re.sub(r">\s+<", "><", xml.replace("\n", "").strip())
95+
return re.sub(r">\s+<", "><", xml.strip())
9796

9897
@staticmethod
9998
def limpa_namespace() -> None:
@@ -114,6 +113,14 @@ def limpa_namespace() -> None:
114113
# xml_doc=nfse_xml_assinado, xsd_file=xsd_nfse, use_assert=True
115114
# )
116115

116+
@staticmethod
117+
def _get_config() -> SerializacaoNFSeConfigTest:
118+
return SerializacaoNFSeConfigTest(
119+
"./tests/certificado.pfx",
120+
bytes("123456", "utf-8"),
121+
True
122+
)
123+
117124
@staticmethod
118125
def _get_emitente() -> Emitente:
119126
return Emitente(

tests/test_nfse_serializacao_betha.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import unittest
22

33
from pynfe.entidades.notafiscal import NotaFiscalServico
4-
54
from tests.test_nfse_serializacao import SerializacaoNFSeTest
65

76

@@ -10,15 +9,12 @@ def test_notafiscal_geral(self):
109
nfse = self._get_notafiscal_servico()
1110
nfse_xml = SerializacaoNFSeTest.serializa_nfse(nfse, 'betha')
1211

13-
# TODO: assinatura digital
14-
# config = SerializacaoNFSeTest.get_config()
15-
# nfse_xml_assinado = SerializacaoNFSeTest.assina_xml(config, nfse_xml)
16-
17-
self.maxDiff = None
18-
result = self._get_nfse_esperada(nfse)
12+
nfse_xml_assinado = SerializacaoNFSeTest.assina_xml(nfse_xml)
13+
nfse_esperada = self._get_nfse_esperada()
1914

2015
# Teste do conteúdo das tags do XML
21-
self.assertEqual(nfse_xml, result)
16+
self.maxDiff = None
17+
self.assertEqual(nfse_xml_assinado, nfse_esperada)
2218

2319
# TODO: Testa a validação do XML com os schemas XSD
2420
# SerializacaoNFSeTest.validacao_com_xsd_do_xml_gerado_sem_processar(
@@ -36,9 +32,8 @@ def _get_notafiscal_servico(self) -> NotaFiscalServico:
3632

3733
return nota_fiscal
3834

39-
def _get_nfse_esperada(self, nfse: NotaFiscalServico) -> str:
35+
def _get_nfse_esperada(self) -> str:
4036
return SerializacaoNFSeTest.strip_xml(f"""
41-
<?xml version="1.0" ?>
4237
<GerarNfseEnvio xmlns:ns1="http://www.betha.com.br/e-nota-contribuinte-ws">
4338
<ns1:Rps>
4439
<ns1:InfDeclaracaoPrestacaoServico Id="50">
@@ -48,10 +43,10 @@ def _get_nfse_esperada(self, nfse: NotaFiscalServico) -> str:
4843
<ns1:Serie>A1</ns1:Serie>
4944
<ns1:Tipo>1</ns1:Tipo>
5045
</ns1:IdentificacaoRps>
51-
<ns1:DataEmissao>{nfse.data_emissao.strftime("%Y-%m-%d")}</ns1:DataEmissao>
46+
<ns1:DataEmissao>{SerializacaoNFSeTest.data_hora[:10]}</ns1:DataEmissao>
5247
<ns1:Status>1</ns1:Status>
5348
</ns1:Rps>
54-
<ns1:Competencia>{nfse.data_emissao.strftime("%Y-%m-%d")}</ns1:Competencia>
49+
<ns1:Competencia>{SerializacaoNFSeTest.data_hora[:10]}</ns1:Competencia>
5550
<ns1:Servico>
5651
<ns1:Valores>
5752
<ns1:ValorServicos>100.0</ns1:ValorServicos>
@@ -85,8 +80,27 @@ def _get_nfse_esperada(self, nfse: NotaFiscalServico) -> str:
8580
<ns1:IncentivoFiscal>2</ns1:IncentivoFiscal>
8681
</ns1:InfDeclaracaoPrestacaoServico>
8782
</ns1:Rps>
88-
</GerarNfseEnvio>
89-
""")
83+
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
84+
<SignedInfo>
85+
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
86+
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
87+
<Reference URI="#50">
88+
<Transforms>
89+
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
90+
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
91+
</Transforms>
92+
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
93+
<DigestValue>xzbAflAhVih7JNxmSa2ZJ1rMpzw=</DigestValue>
94+
</Reference>
95+
</SignedInfo>
96+
<SignatureValue>W6Ys1KHIbz9BDtG+ej++ROAkqZTtSgYH2cplorcxxOZ2VJG3KKWnasyLghEIJfcXtss4kjblgdGOf3IJVgaDuub4GyYPkEzCxnGEr1nQXw74rDmGLwZg1vPBUdHsIbcw8wvAoUOW6zfMI5ljr61Rz5CCytBu4IqpUFCQzWguiG8=</SignatureValue>
97+
<KeyInfo>
98+
<X509Data>
99+
<X509Certificate>MIICMTCCAZqgAwIBAgIQfYOsIEVuAJ1FwwcTrY0t1DANBgkqhkiG9w0BAQUFADBX\nMVUwUwYDVQQDHkwAewA1ADkARgAxAEUANAA2ADEALQBEAEQARQA1AC0ANABEADIA\nRgAtAEEAMAAxAEEALQA4ADMAMwAyADIAQQA5AEUAQgA4ADMAOAB9MB4XDTE1MDYx\nNTA1NDc1N1oXDTE2MDYxNDExNDc1N1owVzFVMFMGA1UEAx5MAHsANQA5AEYAMQBF\nADQANgAxAC0ARABEAEUANQAtADQARAAyAEYALQBBADAAMQBBAC0AOAAzADMAMgAy\nAEEAOQBFAEIAOAAzADgAfTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAk41G\nnqXXLaiOC/y0/cA4tbS+NZCqI+x4EsztgDFvPPlHstiVYcLRkni4i93gK9zoC6g0\nmh66HMVzAfE8vRNwW5b7m6nWS1SiHBon7/Mqsw4MIq3SC+J/fTbKpqwyfAuH2YZl\nAiQuQc85fyllAMLh2WrA7JgOLR/5tF3kLtpbHdECAwEAATANBgkqhkiG9w0BAQUF\nAAOBgQArdh+RyT6VxKGsXk1zhHsgwXfToe6GpTF4W8PHI1+T0WIsNForDhvst6nm\nQtgAhuZM9rxpOJuNKc+pM29EixpAiZZiRMCSWEItNyEVdUIi+YnKBcAHd88TwO86\nd126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw==\n</X509Certificate>
100+
</X509Data>
101+
</KeyInfo>
102+
</Signature>
103+
</GerarNfseEnvio>""")
90104

91105

92106
if __name__ == "__main__":

0 commit comments

Comments
 (0)