diff options
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/l10n_it_edi/__init__.py | 1 | ||||
| -rw-r--r-- | addons/l10n_it_edi/models/ir_mail_server.py | 18 | ||||
| -rw-r--r-- | addons/l10n_it_edi/tests/expected_xmls/IT01234567890_FPR01.xml.p7m | bin | 0 -> 7083 bytes | |||
| -rw-r--r-- | addons/l10n_it_edi/tests/test_ir_mail_server.py | 15 | ||||
| -rw-r--r-- | addons/l10n_it_edi/tools/__init__.py | 4 | ||||
| -rw-r--r-- | addons/l10n_it_edi/tools/remove_signature.py | 47 |
6 files changed, 82 insertions, 3 deletions
diff --git a/addons/l10n_it_edi/__init__.py b/addons/l10n_it_edi/__init__.py index dc5e6b693d1..54a1e867bac 100644 --- a/addons/l10n_it_edi/__init__.py +++ b/addons/l10n_it_edi/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import models +from . import tools diff --git a/addons/l10n_it_edi/models/ir_mail_server.py b/addons/l10n_it_edi/models/ir_mail_server.py index 57f6eb6cb1b..a2a6205e31e 100644 --- a/addons/l10n_it_edi/models/ir_mail_server.py +++ b/addons/l10n_it_edi/models/ir_mail_server.py @@ -16,6 +16,7 @@ from xmlrpc import client as xmlrpclib from odoo import api, fields, models, tools, _ from odoo.exceptions import ValidationError, UserError +from odoo.addons.l10n_it_edi.tools.remove_signature import remove_signature _logger = logging.getLogger(__name__) @@ -133,9 +134,20 @@ class FetchmailServer(models.Model): if split_underscore[1] in ['RC', 'NS', 'MC', 'MT', 'EC', 'SE', 'NE', 'DT']: # we have a receipt self._message_receipt_invoice(split_underscore[1], attachment) - elif re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", attachment.fname): - # we have a new E-invoice - att_content_data = attachment.content.encode() + else: + match = re.search("([A-Z]{2}[A-Za-z0-9]{2,28}_[A-Za-z0-9]{0,5}.(xml.p7m|xml))", attachment.fname) + # If match, we have an invoice. + if match: + # If it's signed, the content has a bytes type and we just remove the signature's envelope + if match.groups()[1] == 'xml.p7m': + att_content_data = remove_signature(attachment.content) + # If the envelope cannot be removed, the remove_signature returns None, so we skip + if not att_content_data: + _logger.warning("E-invoice couldn't be read: %s", attachment.fname) + continue + # Otherwise, it should be an utf-8 encoded XML string + else: + att_content_data = attachment.content.encode() self._create_invoice_from_mail(att_content_data, attachment.fname, from_address) else: if split_underscore[1] == 'AT': diff --git a/addons/l10n_it_edi/tests/expected_xmls/IT01234567890_FPR01.xml.p7m b/addons/l10n_it_edi/tests/expected_xmls/IT01234567890_FPR01.xml.p7m Binary files differnew file mode 100644 index 00000000000..ecb48417c5a --- /dev/null +++ b/addons/l10n_it_edi/tests/expected_xmls/IT01234567890_FPR01.xml.p7m diff --git a/addons/l10n_it_edi/tests/test_ir_mail_server.py b/addons/l10n_it_edi/tests/test_ir_mail_server.py index c6dd9efe989..8e803d9ad56 100644 --- a/addons/l10n_it_edi/tests/test_ir_mail_server.py +++ b/addons/l10n_it_edi/tests/test_ir_mail_server.py @@ -1,12 +1,14 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import datetime import logging from collections import namedtuple from unittest.mock import patch from odoo import tools from odoo.addons.account_edi.tests.common import AccountEdiTestCommon +from odoo.addons.l10n_it_edi.tools.remove_signature import remove_signature _logger = logging.getLogger(__name__) @@ -51,7 +53,9 @@ class PecMailServerTests(AccountEdiTestCommon): # invoice_filename2 is used for vendor bill tests cls.invoice_filename1 = 'IT01234567890_FPR01.xml' cls.invoice_filename2 = 'IT01234567890_FPR02.xml' + cls.signed_invoice_filename = 'IT01234567890_FPR01.xml.p7m' cls.invoice_content = cls._get_test_file_content(cls.invoice_filename1) + cls.signed_invoice_content = cls._get_test_file_content(cls.signed_invoice_filename) cls.invoice = cls.env['account.move'].create({ 'move_type': 'in_invoice', 'ref': '01234567890' @@ -85,6 +89,8 @@ class PecMailServerTests(AccountEdiTestCommon): def _create_invoice(self, content, filename): """ Create an invoice from given attachment content """ with patch.object(self.server._cr, 'commit', return_value=None): + if filename.endswith(".p7m"): + content = remove_signature(content) return self.server._create_invoice_from_mail(content, filename, 'fake@address.be') # ----------------------------- @@ -98,6 +104,15 @@ class PecMailServerTests(AccountEdiTestCommon): invoices = self._create_invoice(self.invoice_content, self.invoice_filename2) self.assertTrue(bool(invoices)) + def test_receive_signed_vendor_bill(self): + """ Test a signed (P7M) sample e-invoice file from https://www.fatturapa.gov.it/export/documenti/fatturapa/v1.2/IT01234567890_FPR01.xml """ + invoices = self._create_invoice(self.signed_invoice_content, self.signed_invoice_filename) + self.assertRecordValues(invoices, [{ + 'name': 'BILL/2014/12/0001', + 'date': datetime.date(2014, 12, 18), + 'ref': '01234567890', + }]) + def test_receive_same_vendor_bill_twice(self): """ Test that the second time we are receiving a PEC mail with the same attachment, the second is discarded """ content = self.fake_test_content.encode() diff --git a/addons/l10n_it_edi/tools/__init__.py b/addons/l10n_it_edi/tools/__init__.py new file mode 100644 index 00000000000..8fee2c7329f --- /dev/null +++ b/addons/l10n_it_edi/tools/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import remove_signature diff --git a/addons/l10n_it_edi/tools/remove_signature.py b/addons/l10n_it_edi/tools/remove_signature.py new file mode 100644 index 00000000000..1816b7fd1a7 --- /dev/null +++ b/addons/l10n_it_edi/tools/remove_signature.py @@ -0,0 +1,47 @@ +# -*- coding:utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +import warnings + +_logger = logging.getLogger(__name__) + +try: + from OpenSSL import crypto as ssl_crypto + import OpenSSL._util as ssl_util +except ImportError: + ssl_crypto = None + _logger.warning("Cannot import library 'OpenSSL' for PKCS#7 envelope extraction.") + + +def remove_signature(content): + """ Remove the PKCS#7 envelope from given content, making a '.xml.p7m' file content readable as it was '.xml'. + As OpenSSL may not be installed, in that case a warning is issued and None is returned. """ + + # Prevent using the library if it had import errors + if not ssl_crypto: + _logger.warning("Error reading the content, check if the OpenSSL library is installed for for PKCS#7 envelope extraction.") + return None + + # Load some tools from the library + null = ssl_util.ffi.NULL + verify = ssl_util.lib.PKCS7_verify + + # By default ignore the validity of the certificates, just validate the structure + flags = ssl_util.lib.PKCS7_NOVERIFY | ssl_util.lib.PKCS7_NOSIGS + + # Read the signed data fron the content + out_buffer = ssl_crypto._new_mem_buf() + + # This method is deprecated, but there are actually no alternatives + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + loaded_data = ssl_crypto.load_pkcs7_data(ssl_crypto.FILETYPE_ASN1, content) + + # Verify the signature + if verify(loaded_data._pkcs7, null, null, null, out_buffer, flags) != 1: + ssl_crypto._raise_current_error() + + # Get the content as a byte-string + decoded_content = ssl_crypto._bio_to_string(out_buffer) + return decoded_content |
