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 }} diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml new file mode 100644 index 00000000..03b8dc0c --- /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 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 + - 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 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/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) 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/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/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 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', 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