diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9d0eb1fa..a44f18fa 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.10.1 +current_version = 0.11.0 commit = True tag = True diff --git a/.circleci/config.yml b/.circleci/config.yml index cde09f5c..fcf3ad71 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,6 +11,22 @@ # version: "2.1" +# -----BEGIN Environment Variables----- + +# Environment variables required for deployment: +# +# - PYPI_PASSWORD := PyPI password or API token. +# - PYPI_USERNAME := PyPI username. For API tokens, use "__token__". +# - TWINE_NON_INTERACTIVE := Do not interactively prompt for credentials if they are missing. +# - TWINE_REPOSITORY_URL := The repository (package index) URL to register the package to. + +# x-deploy-environment := Deployment environment variables +x-deploy-environment: &x-deploy-environment + TWINE_NON_INTERACTIVE: "true" + TWINE_REPOSITORY_URL: https://upload.pypi.org/legacy/ + +# -----END Environment Variables----- + jobs: test: parameters: @@ -34,6 +50,12 @@ jobs: pip install --upgrade setuptools wheel pip install -r requirements/test.txt + - run: + name: Check Dependencies + command: | + . venv/bin/activate + pip check + - run: name: run tests command: | @@ -73,6 +95,12 @@ jobs: pip install --upgrade setuptools wheel pip install -r requirements/release.txt + - run: + name: Check Dependencies + command: | + . venv/bin/activate + pip check + - run: name: make dist command: | @@ -83,6 +111,35 @@ jobs: path: dist destination: dist + - persist_to_workspace: + root: ~/repo + paths: + - dist + - venv + + deploy: + docker: + - image: python:3.7.2 + environment: + <<: *x-deploy-environment + + working_directory: ~/repo + + steps: + - checkout + + - attach_workspace: + at: ~/repo + + - deploy: + name: Upload Artifacts to Repository + command: | + . venv/bin/activate + + make upload-release \ + TWINE_USERNAME="${PYPI_USERNAME:?}" \ + TWINE_PASSWORD="${PYPI_PASSWORD:?}" + workflows: version: 2 ci: @@ -95,3 +152,10 @@ workflows: - "3.7.6" - "3.8.3" - dist + - deploy: + requires: + - dist + filters: + branches: + only: + - master diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..8fb490b8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# GitHub Dependabot Configuration +# +# Documentation: +# https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 + +updates: + - package-ecosystem: pip + directory: / + schedule: + interval: daily + time: "08:30" + timezone: America/Santiago + open-pull-requests-limit: 3 + labels: + - dependencies diff --git a/HISTORY.rst b/HISTORY.rst index 17f11dfd..cd246526 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,21 @@ History ------- +0.11.0 (2020-09-15) ++++++++++++++++++++++++ + +* (PR #138, 2020-09-15) config: Add PyPI package uploading to CI +* (PR #135, 2020-09-15) rtc: Add constants +* (PR #129, 2020-09-14) build(deps): bump toml from 0.10.0 to 0.10.1 +* (PR #133, 2020-09-14) build(deps): bump codecov from 2.0.22 to 2.1.9 +* (PR #134, 2020-09-10) Add sub-package `rtc` +* (PR #131, 2020-07-22) requirements: update 'signxml' +* (PR #123, 2020-07-13) build(deps): bump six from 1.14.0 to 1.15.0 +* (PR #126, 2020-07-10) build(deps): bump virtualenv from 20.0.16 to 20.0.26 +* (PR #127, 2020-07-09) config: Verify Python dependency compatibility in CI +* (PR #124, 2020-07-07) build(deps): bump tox from 3.14.6 to 3.16.1 +* (PR #122, 2020-07-07) config: Add configuration for GitHub Dependabot + 0.10.1 (2020-06-08) +++++++++++++++++++++++ diff --git a/cl_sii/__init__.py b/cl_sii/__init__.py index 147552f6..6d1de686 100644 --- a/cl_sii/__init__.py +++ b/cl_sii/__init__.py @@ -5,4 +5,4 @@ """ -__version__ = '0.10.1' +__version__ = '0.11.0' diff --git a/cl_sii/libs/xml_utils.py b/cl_sii/libs/xml_utils.py index 20e4e2f0..677a40b7 100644 --- a/cl_sii/libs/xml_utils.py +++ b/cl_sii/libs/xml_utils.py @@ -431,8 +431,24 @@ def verify_xml_signature( try: # note: by passing 'x509_cert' we override any X.509 certificate information supplied # by the signature itself. + + # note: when an X509Data element is present in the signature and used for verification, but + # a KeyValue element is also present, there is an ambiguity and a security hazard because + # the public key used to sign the document is already encoded in the certificate (which is + # in X509Data), so the verifier must either ignore KeyValue or ensure that it matches what + # is in the certificate. SignXML does not perform that validation and throws an + # 'InvalidInput' error instead. + # + # SII's schema for XML signatures requires both elements to be present, which forces us to + # enable 'ignore_ambiguous_key_info' to bypass the error and validate the signature using + # X509Data only. + # + # Source: + # https://github.com/XML-Security/signxml/commit/ef15da8dbb904f1dedfdd210ae3e0df5da535612 result: signxml.VerifyResult = xml_verifier.verify( - data=tmp_bytes, require_x509=True, x509_cert=trusted_x509_cert_open_ssl) + data=tmp_bytes, require_x509=True, x509_cert=trusted_x509_cert_open_ssl, + ignore_ambiguous_key_info=True, + ) except signxml.exceptions.InvalidDigest as exc: # warning: catch before 'InvalidSignature' (it is the parent of 'InvalidDigest'). diff --git a/cl_sii/rtc/__init__.py b/cl_sii/rtc/__init__.py new file mode 100644 index 00000000..3fa6c9c1 --- /dev/null +++ b/cl_sii/rtc/__init__.py @@ -0,0 +1,10 @@ +""" +SII RTC/RPETC +============= + +Concepts and acronyms used interchangeably: + +* "Registro Transferencia de Crédito" (RTC) +* "Registro Público Electrónico de Transferencia de Crédito" (RPETC) +* "Registro Electrónico de Cesión de Créditos" +""" diff --git a/cl_sii/rtc/constants.py b/cl_sii/rtc/constants.py new file mode 100644 index 00000000..73ce4909 --- /dev/null +++ b/cl_sii/rtc/constants.py @@ -0,0 +1,42 @@ +import enum +from typing import FrozenSet + +from cl_sii.dte.constants import TipoDteEnum + + +# The collection of "tipo DTE" for which it is possible to "ceder" a "DTE". +# They are defined in a document and also an XML schema. +# - Document "Formato Archivo Electrónico de Cesión (AEC)" +# (http://www.sii.cl/factura_electronica/cesion.pdf) are: +# > Sólo códigos 33, 34, 46 y 43 +# - XML element 'CesionDefType/DocumentoCesion/IdDTE/TipoDTE' +# - description: "Tipo de DTE" +# - XML type: 'SiiDte:DTEFacturasType' +# - source: +# https://github.com/fyntex/lib-cl-sii-python/blob/7e1c4b52/cl_sii/data/ref/factura_electronica/schemas-xml/Cesion_v10.xsd#L38-L42 +# - XML type 'SiiDte:DTEFacturasType' in official schema 'SiiTypes_v10.xsd' +# - source: +# https://github.com/fyntex/lib-cl-sii-python/blob/7e1c4b52/cl_sii/data/ref/factura_electronica/schemas-xml/SiiTypes_v10.xsd#L100-L126 +TIPO_DTE_CEDIBLES: FrozenSet[TipoDteEnum] = frozenset({ + TipoDteEnum.FACTURA_ELECTRONICA, + TipoDteEnum.FACTURA_NO_AFECTA_O_EXENTA_ELECTRONICA, + TipoDteEnum.FACTURA_COMPRA_ELECTRONICA, + TipoDteEnum.LIQUIDACION_FACTURA_ELECTRONICA, +}) + + +@enum.unique +class RolContribuyenteEnCesion(enum.Enum): + + """ + "Rol" of "contribuyente" in a "cesion". + """ + + CEDENTE = 'CEDENTE' + """Cesiones en las que el contribuyente ha sido cedente i.e. ha cedido""" + + CESIONARIO = 'CESIONARIO' + """Cesiones en las que el contribuyente ha sido cesionario i.e. le han cedido""" + + DEUDOR = 'DEUDOR' + """Cesiones de DTEs en que el contribuyente es el deudor.""" diff --git a/docs/project-maintenance.rst b/docs/project-maintenance.rst index e49f51f9..c673a4c7 100644 --- a/docs/project-maintenance.rst +++ b/docs/project-maintenance.rst @@ -95,6 +95,9 @@ Push commit ``abcd1234`` and tag ``vX.Y.Z`` automatically created by ``bumpversi 4) Publish to PyPI +++++++++++++++++++ +.. warning:: + Only perform this step if the CI system failed to upload the package. + (local workstation) Run:: diff --git a/requirements/base.txt b/requirements/base.txt index b8e1b349..b26ffd96 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -9,7 +9,7 @@ lxml==4.5.0 marshmallow==2.19.5 pyOpenSSL==18.0.0 pytz==2019.3 -signxml==2.6.0 +signxml==2.8.0 # Packages dependencies: # - cryptography: @@ -24,16 +24,13 @@ signxml==2.6.0 # - setuptools # - six # - signxml: -# - asn1crypto # - certifi # - cryptography -# - defusedxml # - eight # - future # - lxml # - pyOpenSSL # - six -asn1crypto==1.3.0 attrs==19.3.0 certifi==2020.4.5.1 cffi==1.14.0 @@ -43,5 +40,5 @@ importlib-metadata==1.6.0; python_version<'3.8' pycparser==2.20 pyrsistent==0.16.0 # setuptools -six==1.14.0 +six==1.15.0 zipp==3.1.0 diff --git a/requirements/test.txt b/requirements/test.txt index afdfb56c..f438c60c 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -3,11 +3,11 @@ -r extras.txt # Required packages: -codecov==2.0.22 +codecov==2.1.9 coverage==4.5.3 flake8==3.7.9 mypy==0.770 -tox==3.14.6 +tox==3.16.1 # Packages dependencies: # - codecov: @@ -60,9 +60,9 @@ pyflakes==2.1.1 # pyparsing # requests # six -toml==0.10.0 +toml==0.10.1 typed-ast==1.4.1 typing-extensions==3.7.4.2 # urllib3 -virtualenv==20.0.16 +virtualenv==20.0.26 # zipp diff --git a/setup.py b/setup.py index f5094afb..36f65e71 100755 --- a/setup.py +++ b/setup.py @@ -27,10 +27,10 @@ def get_version(*file_paths: Sequence[str]) -> str: 'jsonschema>=3.1.1', 'lxml>=4.5.0,<5', 'marshmallow>=2.19.2,<3', - # TODO: remove upper-bound after a new release of 'signxml' drops the requirement 'pyOpenSSL<19' - 'pyOpenSSL>=18.0.0,<19', + # TODO: remove upper-bound after a new release of 'signxml' drops the requirement 'pyOpenSSL<20' + 'pyOpenSSL>=18.0.0,<20', 'pytz>=2019.3', - 'signxml>=2.6.0', + 'signxml>=2.8.0', ] extras_requirements = { diff --git a/tests/test_rtc_constants.py b/tests/test_rtc_constants.py new file mode 100644 index 00000000..89b3f277 --- /dev/null +++ b/tests/test_rtc_constants.py @@ -0,0 +1,15 @@ +import unittest + +from cl_sii.rtc.constants import TIPO_DTE_CEDIBLES + + +class TipoDteCediblesTest(unittest.TestCase): + + # For 'TIPO_DTE_CEDIBLES' + + def test_all_are_factura(self) -> None: + for element in TIPO_DTE_CEDIBLES: + self.assertTrue(element.is_factura) + + # TODO: implement test that check that the values correspond to those defined in + # XML type 'SiiDte:DTEFacturasType' in official schema 'SiiTypes_v10.xsd'.