From 1c534231564fccb300f97ce38b2411543ac1d80a Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Wed, 10 May 2023 16:44:38 -0300 Subject: [PATCH 1/8] Create python-publish.yml --- .github/workflows/python-publish.yml | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 00000000..a9ba2bc3 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,39 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.7' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} From ab415cfd87f51a4a793ff13f13eedbe98f0771eb Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Wed, 10 May 2023 17:53:29 -0300 Subject: [PATCH 2/8] Adding a github action for testing --- .github/workflows/python-test.yml | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/python-test.yml diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml new file mode 100644 index 00000000..62350a25 --- /dev/null +++ b/.github/workflows/python-test.yml @@ -0,0 +1,41 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "master3" ] + pull_request: + branches: [ "master3" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.7" + - name: Install dependencies + run: | + sudo apt-get update -qq + sudo apt-get install -qq python-dev libffi-dev libxml2-dev libxslt1-dev libssl-dev + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest --cov=pytrustnfe From 05e894458b13094200b17dddd9ac1a3226403b77 Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Wed, 10 May 2023 18:05:26 -0300 Subject: [PATCH 3/8] Removing python-dev --- .github/workflows/python-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 62350a25..e57a42cf 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update -qq - sudo apt-get install -qq python-dev libffi-dev libxml2-dev libxslt1-dev libssl-dev + sudo apt-get install -qq libffi-dev libxml2-dev libxslt1-dev libssl-dev python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi From 46a68f6a75cff1d971be19c0c290f7beb707f0ae Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Thu, 11 May 2023 16:55:51 -0300 Subject: [PATCH 4/8] Corrige assinatura digital, melhora requirements removendo restricoes --- .github/workflows/python-test.yml | 2 +- pytrustnfe/nfe/assinatura.py | 8 +++++++- requirements.txt | 32 ++++++++++++++++++------------- setup.py | 17 ++++++++-------- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index e57a42cf..03b8dc0c 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update -qq - sudo apt-get install -qq libffi-dev libxml2-dev libxslt1-dev libssl-dev + sudo apt-get install -qq libxml2-dev libxmlsec1-dev libxmlsec1-openssl python -m pip install --upgrade pip pip install flake8 pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi diff --git a/pytrustnfe/nfe/assinatura.py b/pytrustnfe/nfe/assinatura.py index 3f53d100..c9d4b794 100644 --- a/pytrustnfe/nfe/assinatura.py +++ b/pytrustnfe/nfe/assinatura.py @@ -8,6 +8,11 @@ from signxml import XMLSigner +class XMLSignerWithSHA1(XMLSigner): + def check_deprecated_methods(self): + pass + + class Assinatura(object): def __init__(self, arquivo, senha): self.arquivo = arquivo @@ -20,7 +25,7 @@ def assina_xml(self, xml_element, reference, getchildren=False): if element.text is not None and not element.text.strip(): element.text = None - signer = XMLSigner( + signer = XMLSignerWithSHA1( method=signxml.methods.enveloped, signature_algorithm="rsa-sha1", digest_algorithm="sha1", @@ -30,6 +35,7 @@ def assina_xml(self, xml_element, reference, getchildren=False): ns = {} ns[None] = signer.namespaces["ds"] signer.namespaces = ns + signer.excise_empty_xmlns_declarations = True ref_uri = ("#%s" % reference) if reference else None signed_root = signer.sign( diff --git a/requirements.txt b/requirements.txt index e898c920..c2c385c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,25 @@ -lxml >= 4.2.1, < 5 -coveralls -Jinja2 +# Sign xml dependencies signxml==2.9.0 ; python_version < '3.7' signxml ; python_version >= '3.7' -urllib3 >= 1.22 +lxml >= 4.2.1 +cryptography >= 3.4.8 +pyOpenSSL >= 22.1.0 +certifi >= 2018.1.18 + +# XmlSec +xmlsec >= 1.3.13 + +# pytrustnfe dependencies +Jinja2 +pytz +zeep +reportlab +urllib3 suds-community suds-requests4 -defusedxml >= 0.7.1, < 1 -eight >= 0.3.0, < 1 -cryptography >= 2.1.4 -pyOpenSSL == 22.1.0 -certifi >= 2015.11.20.1 -xmlsec >= 1.3.3 -reportlab + +# Test dependencies +coveralls pytest>=4.1.1 pytest-cov -pytz -zeep + diff --git a/setup.py b/setup.py index 8d547d18..a6308521 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages -VERSION = "1.0.62" +VERSION = "2.0.0" setup( @@ -50,13 +50,14 @@ long_description=open("README.md", "r").read(), long_description_content_type="text/markdown", install_requires=[ - 'urllib3 >= 1.22', - 'xmlsec >= 1.3.3', # apt update;apt install libxmlsec1-dev pkg-config -y - 'Jinja2 >= 2.8, <= 3.0.3', - 'lxml >= 4.2.1, < 5', - 'cryptography >= 2.1.4', - 'pyOpenSSL == 22.1.0', - 'suds', + 'xmlsec >= 1.3.13', # apt update;apt install libxmlsec1-dev pkg-config -y + 'lxml >= 4.2.1', + 'cryptography >= 3.4.8', + 'pyOpenSSL >= 17.5.0', + 'certifi >= 2018.1.18', + 'urllib3', + 'Jinja2', + 'suds-community', 'suds-requests4', 'reportlab', 'pytz', From 6ea3f00a5543878bccd899efaa43ac13ada84551 Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Thu, 11 May 2023 17:04:52 -0300 Subject: [PATCH 5/8] Remove unused function --- pytrustnfe/nfe/__init__.py | 110 ------------------------------------- 1 file changed, 110 deletions(-) diff --git a/pytrustnfe/nfe/__init__.py b/pytrustnfe/nfe/__init__.py index c861bbb5..67c054d3 100644 --- a/pytrustnfe/nfe/__init__.py +++ b/pytrustnfe/nfe/__init__.py @@ -68,116 +68,6 @@ def _render(certificado, method, sign, **kwargs): return xml_send -def gerar_qrcode(id_csc: int, csc: str, xml_send: str, cert = False) -> str: - xml = etree.fromstring(xml_send) - signature = xml.find( - ".//{http://www.w3.org/2000/09/xmldsig#}Signature") - id = xml.find( - ".//{http://www.portalfiscal.inf.br/nfe}infNFe").get('Id') - if id is None: - raise Exception("XML Invalido - Sem o ID") - - chave = id.replace('NFe', '') - emit_uf = chave[:2] - - tp_amb = xml.find(".//{http://www.portalfiscal.inf.br/nfe}tpAmb") - if tp_amb is None: - raise Exception("XML Invalido - Sem o tipo de ambiente") - - dh_emi = xml.find(".//{http://www.portalfiscal.inf.br/nfe}dhEmi") - if dh_emi is None: - raise Exception("XML Invalido - Sem data de Emissao") - dh_emi = dh_emi.text.split("-")[2].split("T")[0] - - tp_emis = xml.find(".//{http://www.portalfiscal.inf.br/nfe}tpEmis") - if tp_emis is None: - raise Exception("XML Invalido - Sem tipo de emissao") - - v_nf = xml.find(".//{http://www.portalfiscal.inf.br/nfe}vNF") - if v_nf is None: - raise Exception("XML Invalido - Sem o valor da NFe") - - url_qrcode_str = url_qrcode( - estado=emit_uf, - ambiente=tp_amb.text) - url_qrcode_exibicao_str = url_qrcode_exibicao( - estado=emit_uf, - ambiente=tp_amb.text) - - if tp_emis != 1: - if signature is None: - if cert is not False: - signer = Assinatura(certificado.pfx, certificado.password) - xml_send = signer.assina_xml(xmlElem_send, id) - else: - raise Exception("XML Invalido - Sem assinatura e não " - "foi enviado o certificado nos parametros") - digest_value = xml.find( - ".//{http://www.w3.org/2000/09/xmldsig#}DigestValue") - c_hash_qr_code = \ - "{ch_acesso}|{versao}|{tp_amb}|{dh_emi}|" \ - "{v_nf}|{dig_val}|{id_csc}|{csc}".format( - ch_acesso=chave, - versao=2, - tp_amb=tp_amb.text, - dh_emi=dh_emi, - v_nf=float(v_nf.text), - dig_val=digest_value.text, - id_csc=int(id_csc), - csc=csc - ) - c_hash_qr_code = hashlib.sha1(c_hash_qr_code.encode()). \ - hexdigest() - qr_code_url = 'p={ch_acesso}|{versao}|{tp_amb}|{dh_emi}|" \ - "{v_nf}|{dig_val}|{id_csc}|{hash}'.format( - ch_acesso=chave, - versao=2, - tp_amb=tp_amb.text, - dh_emi=dh_emi, - v_nf=float(v_nf.text), - dig_val=digest_value.text, - id_csc=int(id_csc), - hash=c_hash_qr_code - ) - qrcode = url_qrcode_str + qr_code_url - url_consulta = url_qrcode_exibicao_str - - qrCode = xml.find( - './/{http://www.portalfiscal.inf.br/nfe}qrCode').text = \ - qrcode - urlChave = xml.find( - './/{http://www.portalfiscal.inf.br/nfe}urlChave').text = \ - url_consulta - else: - c_hash_qr_code = \ - "{ch_acesso}|{versao}|{tp_amb}|{id_csc}|{csc}".format( - ch_acesso=chave, - versao=2, - tp_amb=tp_amb.text, - id_csc=int(id_csc), - csc=csc - ) - c_hash_qr_code = hashlib.sha1(c_hash_qr_code.encode()).hexdigest() - - qr_code_url = "p={ch_acesso}|{versao}|{tp_amb}|{id_csc}|" \ - "{hash}".\ - format( - ch_acesso=chave, - versao=2, - tp_amb=tp_amb.text, - id_csc=int(id_csc), - hash=c_hash_qr_code - ) - qrcode = url_qrcode_str + qr_code_url - url_consulta = url_qrcode_exibicao_str - qrCode = xml.find( - './/{http://www.portalfiscal.inf.br/nfe}qrCode').text = \ - qrcode - urlChave = xml.find( - './/{http://www.portalfiscal.inf.br/nfe}urlChave').text = \ - url_consulta - return etree.tostring(xml) - def _get_session(certificado): cert, key = extract_cert_and_key_from_pfx(certificado.pfx, certificado.password) cert, key = save_cert_key(cert, key) From 03af3532302fd0015c11fde55e955dd1eb13284f Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Thu, 11 May 2023 17:19:46 -0300 Subject: [PATCH 6/8] Fix unit tests for nfse --- pytrustnfe/certificado.py | 2 +- pytrustnfe/nfse/paulistana/__init__.py | 6 +++--- tests/XMLs/paulistana_signature.xml | 3 ++- tests/test_nfse_paulistana.py | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pytrustnfe/certificado.py b/pytrustnfe/certificado.py index d6aab080..35cd16fe 100644 --- a/pytrustnfe/certificado.py +++ b/pytrustnfe/certificado.py @@ -20,7 +20,7 @@ def save_pfx(self): def extract_cert_and_key_from_pfx(pfx, password): - pfx = crypto.load_pkcs12(pfx, password) + pfx = crypto.load_pkcs12(pfx, password.encode()) # PEM formatted private key key = crypto.dump_privatekey(crypto.FILETYPE_PEM, pfx.get_privatekey()) # PEM formatted certificate diff --git a/pytrustnfe/nfse/paulistana/__init__.py b/pytrustnfe/nfse/paulistana/__init__.py index bc8b8f04..689c1f66 100644 --- a/pytrustnfe/nfse/paulistana/__init__.py +++ b/pytrustnfe/nfse/paulistana/__init__.py @@ -13,14 +13,14 @@ def sign_tag(certificado, **kwargs): - pkcs12 = crypto.load_pkcs12(certificado.pfx, certificado.password) + pkcs12 = crypto.load_pkcs12(certificado.pfx, certificado.password.encode()) key = pkcs12.get_privatekey() if "nfse" in kwargs: for item in kwargs["nfse"]["lista_rps"]: - signed = crypto.sign(key, item["assinatura"], "SHA1") + signed = crypto.sign(key, item["assinatura"].encode(), "SHA1") item["assinatura"] = b64encode(signed).decode() if "cancelamento" in kwargs: - signed = crypto.sign(key, kwargs["cancelamento"]["assinatura"], "SHA1") + signed = crypto.sign(key, kwargs["cancelamento"]["assinatura"].encode(), "SHA1") kwargs["cancelamento"]["assinatura"] = b64encode(signed).decode() diff --git a/tests/XMLs/paulistana_signature.xml b/tests/XMLs/paulistana_signature.xml index f9a0af4b..79f7e873 100644 --- a/tests/XMLs/paulistana_signature.xml +++ b/tests/XMLs/paulistana_signature.xml @@ -27,7 +27,8 @@ mh66HMVzAfE8vRNwW5b7m6nWS1SiHBon7/Mqsw4MIq3SC+J/fTbKpqwyfAuH2YZl AiQuQc85fyllAMLh2WrA7JgOLR/5tF3kLtpbHdECAwEAATANBgkqhkiG9w0BAQUF AAOBgQArdh+RyT6VxKGsXk1zhHsgwXfToe6GpTF4W8PHI1+T0WIsNForDhvst6nm QtgAhuZM9rxpOJuNKc+pM29EixpAiZZiRMCSWEItNyEVdUIi+YnKBcAHd88TwO86 -d126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw== +d126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw== + \ No newline at end of file diff --git a/tests/test_nfse_paulistana.py b/tests/test_nfse_paulistana.py index ea914722..3ecf37c3 100644 --- a/tests/test_nfse_paulistana.py +++ b/tests/test_nfse_paulistana.py @@ -1,6 +1,6 @@ # coding=utf-8 -import mock +from unittest import mock import os.path import unittest from pytrustnfe.certificado import Certificado From bc1cda49062a49a0e59258cb2b9f8c37b855a0bd Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Thu, 11 May 2023 17:26:44 -0300 Subject: [PATCH 7/8] Remove a jinja extension that is now a built in --- pytrustnfe/xml/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pytrustnfe/xml/__init__.py b/pytrustnfe/xml/__init__.py index c6d21d90..29064df6 100644 --- a/pytrustnfe/xml/__init__.py +++ b/pytrustnfe/xml/__init__.py @@ -17,8 +17,7 @@ def recursively_empty(e): def render_xml(path, template_name, remove_empty, **nfe): nfe = recursively_normalize(nfe) - env = Environment(loader=FileSystemLoader( - path), extensions=["jinja2.ext.with_"]) + env = Environment(loader=FileSystemLoader(path)) env.filters["normalize"] = filters.strip_line_feed env.filters["normalize_str"] = filters.normalize_str env.filters["format_percent"] = filters.format_percent From fccafec19996a713d367259f6b3b710b29a8b741 Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Thu, 11 May 2023 17:29:44 -0300 Subject: [PATCH 8/8] Revert back the nfse paulistana test --- tests/XMLs/paulistana_signature.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/XMLs/paulistana_signature.xml b/tests/XMLs/paulistana_signature.xml index 79f7e873..f9a0af4b 100644 --- a/tests/XMLs/paulistana_signature.xml +++ b/tests/XMLs/paulistana_signature.xml @@ -27,8 +27,7 @@ mh66HMVzAfE8vRNwW5b7m6nWS1SiHBon7/Mqsw4MIq3SC+J/fTbKpqwyfAuH2YZl AiQuQc85fyllAMLh2WrA7JgOLR/5tF3kLtpbHdECAwEAATANBgkqhkiG9w0BAQUF AAOBgQArdh+RyT6VxKGsXk1zhHsgwXfToe6GpTF4W8PHI1+T0WIsNForDhvst6nm QtgAhuZM9rxpOJuNKc+pM29EixpAiZZiRMCSWEItNyEVdUIi+YnKBcAHd88TwO86 -d126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw== - +d126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw== \ No newline at end of file