summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
Diffstat (limited to 'addons')
-rw-r--r--addons/l10n_it_edi/__init__.py1
-rw-r--r--addons/l10n_it_edi/models/ir_mail_server.py18
-rw-r--r--addons/l10n_it_edi/tests/expected_xmls/IT01234567890_FPR01.xml.p7mbin0 -> 7083 bytes
-rw-r--r--addons/l10n_it_edi/tests/test_ir_mail_server.py15
-rw-r--r--addons/l10n_it_edi/tools/__init__.py4
-rw-r--r--addons/l10n_it_edi/tools/remove_signature.py47
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
new file mode 100644
index 00000000000..ecb48417c5a
--- /dev/null
+++ b/addons/l10n_it_edi/tests/expected_xmls/IT01234567890_FPR01.xml.p7m
Binary files differ
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