From 6915d65cb2ba935faa2a2ab63cc759ca34b8e8ad Mon Sep 17 00:00:00 2001 From: Wassilios Lytras Date: Sat, 6 Jun 2020 17:35:03 +0200 Subject: [PATCH 1/4] Adding new setting ERROR_ON_DUPLICATE with default value True. If False, duplicate messages will be handled like success messages, but message_id to still have _duplicate added to it. --- pyas2/settings.py | 3 ++ pyas2/tests/test_advanced.py | 55 ++++++++++++++++++++++++++++++++++++ pyas2/views.py | 19 +++++++++++-- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/pyas2/settings.py b/pyas2/settings.py index 1977b56..ce2cb10 100644 --- a/pyas2/settings.py +++ b/pyas2/settings.py @@ -20,3 +20,6 @@ # Max number of days worth of messages to be saved in archive MAX_ARCH_DAYS = APP_SETTINGS.get("MAX_ARCH_DAYS", 30) + +# Send positive MDN when duplicate message is received +ERROR_ON_DUPLICATE = APP_SETTINGS.get("ERROR_ON_DUPLICATE", True) \ No newline at end of file diff --git a/pyas2/tests/test_advanced.py b/pyas2/tests/test_advanced.py index bb1044e..a13c043 100644 --- a/pyas2/tests/test_advanced.py +++ b/pyas2/tests/test_advanced.py @@ -261,6 +261,61 @@ def test_duplicate_error(self, mock_request): ) self.assertEqual(out_message.status, "E") + @mock.patch("requests.post") + def test_duplicate_success(self, mock_request): + with override_settings(PYAS2={"ERROR_ON_DUPLICATE": False}): + importlib.reload(settings) + partner = Partner.objects.create( + name="AS2 Server", + as2_name="as2server", + target_url="http://localhost:8080/pyas2/as2receive", + signature="sha1", + signature_cert=self.server_crt, + encryption="tripledes_192_cbc", + encryption_cert=self.server_crt, + mdn=True, + mdn_mode="SYNC", + mdn_sign="sha1", + ) + + # Send the message once + as2message = As2Message( + sender=self.organization.as2org, receiver=partner.as2partner + ) + as2message.build( + self.payload, + filename="testmessage.edi", + subject=partner.subject, + content_type=partner.content_type, + ) + in_message, _ = Message.objects.create_from_as2message( + as2message=as2message, payload=self.payload, direction="OUT", status="P" + ) + + mock_request.side_effect = SendMessageMock(self.client) + in_message.send_message(as2message.headers, as2message.content) + + # Check the status of the message + self.assertEqual(in_message.status, "S") + out_message = Message.objects.get( + message_id=in_message.message_id, direction="IN" + ) + self.assertEqual(out_message.status, "S") + + # send it again to, should not cause duplicate error + in_message.send_message(as2message.headers, as2message.content) + + # Make sure out message was created + self.assertEqual(in_message.status, "S") + out_message = Message.objects.get( + message_id=in_message.message_id + "_duplicate", direction="IN" + ) + self.assertEqual(out_message.status, "S") + + with override_settings(PYAS2={"ERROR_ON_DUPLICATE": True}): + importlib.reload(settings) + self.assertEqual(settings.ERROR_ON_DUPLICATE, True) + def test_org_missing_error(self): # Create the client partner and send the command partner = Partner.objects.create( diff --git a/pyas2/views.py b/pyas2/views.py index cf08805..f341e2b 100644 --- a/pyas2/views.py +++ b/pyas2/views.py @@ -25,6 +25,7 @@ from pyas2.utils import run_post_receive from pyas2.utils import run_post_send from pyas2.forms import SendAs2MessageForm +from pyas2 import settings logger = logging.getLogger("pyas2") @@ -47,7 +48,17 @@ def find_message(message_id, partner_id): return message.as2message @staticmethod - def check_message_exists(message_id, partner_id): + def check_success_message_exists(message_id, partner_id): + """ Check if the message already exists in the system """ + if settings.ERROR_ON_DUPLICATE: + return Message.objects.filter( + message_id=message_id, partner_id=partner_id.strip(), status__in=("S", "P") + ).exists() + else: + return False + + @staticmethod + def check_same_message_exists(message_id, partner_id): """ Check if the message already exists in the system """ return Message.objects.filter( message_id=message_id, partner_id=partner_id.strip() @@ -125,7 +136,7 @@ def post(self, request, *args, **kwargs): request_body, self.find_organization, self.find_partner, - self.check_message_exists, + self.check_success_message_exists, ) logger.info( @@ -135,7 +146,9 @@ def post(self, request, *args, **kwargs): ) # In case of duplicates update message id - if isinstance(exception[0], DuplicateDocument): + if isinstance(exception[0], DuplicateDocument) or (not settings.ERROR_ON_DUPLICATE and + self.check_same_message_exists(message_id=as2message.message_id, + partner_id=as2message.sender.as2_name)): as2message.message_id += "_duplicate" # Create the Message and MDN objects From 18e95b6832c4920d4f1ac03b604ee7dbd3fa45f5 Mon Sep 17 00:00:00 2001 From: Wassilios Lytras Date: Sat, 6 Jun 2020 18:16:19 +0200 Subject: [PATCH 2/4] File Formatting --- pyas2/settings.py | 2 +- pyas2/views.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pyas2/settings.py b/pyas2/settings.py index ce2cb10..a2bee6c 100644 --- a/pyas2/settings.py +++ b/pyas2/settings.py @@ -22,4 +22,4 @@ MAX_ARCH_DAYS = APP_SETTINGS.get("MAX_ARCH_DAYS", 30) # Send positive MDN when duplicate message is received -ERROR_ON_DUPLICATE = APP_SETTINGS.get("ERROR_ON_DUPLICATE", True) \ No newline at end of file +ERROR_ON_DUPLICATE = APP_SETTINGS.get("ERROR_ON_DUPLICATE", True) diff --git a/pyas2/views.py b/pyas2/views.py index f341e2b..80e94aa 100644 --- a/pyas2/views.py +++ b/pyas2/views.py @@ -52,7 +52,9 @@ def check_success_message_exists(message_id, partner_id): """ Check if the message already exists in the system """ if settings.ERROR_ON_DUPLICATE: return Message.objects.filter( - message_id=message_id, partner_id=partner_id.strip(), status__in=("S", "P") + message_id=message_id, + partner_id=partner_id.strip(), + status__in=("S", "P"), ).exists() else: return False @@ -146,9 +148,13 @@ def post(self, request, *args, **kwargs): ) # In case of duplicates update message id - if isinstance(exception[0], DuplicateDocument) or (not settings.ERROR_ON_DUPLICATE and - self.check_same_message_exists(message_id=as2message.message_id, - partner_id=as2message.sender.as2_name)): + if isinstance(exception[0], DuplicateDocument) or ( + not settings.ERROR_ON_DUPLICATE + and self.check_same_message_exists( + message_id=as2message.message_id, + partner_id=as2message.sender.as2_name, + ) + ): as2message.message_id += "_duplicate" # Create the Message and MDN objects From a7fde795b7cfa2fc2d24f4b38da1ea58c1cece1a Mon Sep 17 00:00:00 2001 From: Wassilios Lytras Date: Mon, 8 Jun 2020 09:29:42 +0200 Subject: [PATCH 3/4] Updating documentation --- CHANGELOG.rst | 8 ++++++++ docs/detailed-guide/configuration.rst | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2e95f8f..543c9ae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Release History =============== +dev - +------------------ + +* Updated requirements files to have current dependencies +* Added global option to switch off error on duplicate +* Limited duplicate detection to successful previous transmissions only + + 1.2.0 - 2020-04-12 ------------------ diff --git a/docs/detailed-guide/configuration.rst b/docs/detailed-guide/configuration.rst index 39e5992..0066799 100644 --- a/docs/detailed-guide/configuration.rst +++ b/docs/detailed-guide/configuration.rst @@ -34,6 +34,11 @@ The available settings along with their usage is described below: | MAX_ARCH_DAYS | 30 | Number of days files and messages are kept in | | | | storage. | +------------------------+----------------------------+------------------------------------------------+ +| ERROR_ON_DUPLICATE | True | When set to true, duplicate messages are | +| | | handled as error with negative MDN. | +| | | When set to false, duplicates are handled as | +| | | successful transmissions. | ++------------------------+----------------------------+------------------------------------------------+ The Data Directory From 7438a5203c7b7d04f4c874b3eeed141370d1c858 Mon Sep 17 00:00:00 2001 From: Wassilios Lytras Date: Tue, 9 Jun 2020 18:00:12 +0200 Subject: [PATCH 4/4] Adding random name to duplicate messages. --- CHANGELOG.rst | 1 + pyas2/tests/test_advanced.py | 28 ++++++++++++++++++++++++++-- pyas2/views.py | 3 ++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 543c9ae..c41cc5b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ dev - * Updated requirements files to have current dependencies * Added global option to switch off error on duplicate * Limited duplicate detection to successful previous transmissions only +* Giving random name to duplicate messages 1.2.0 - 2020-04-12 diff --git a/pyas2/tests/test_advanced.py b/pyas2/tests/test_advanced.py index a13c043..64e945c 100644 --- a/pyas2/tests/test_advanced.py +++ b/pyas2/tests/test_advanced.py @@ -257,10 +257,21 @@ def test_duplicate_error(self, mock_request): # Make sure out message was created self.assertEqual(in_message.status, "E") out_message = Message.objects.get( - message_id=in_message.message_id + "_duplicate", direction="IN" + message_id__startswith=in_message.message_id + "_duplicate_", direction="IN" ) self.assertEqual(out_message.status, "E") + # send it a third time to cause another duplicate error + in_message.send_message(as2message.headers, as2message.content) + + # Make sure out message was created + self.assertEqual(in_message.status, "E") + out_messages = Message.objects.filter( + message_id__startswith=in_message.message_id + "_duplicate_", direction="IN" + ) + for out_message in out_messages: + self.assertEqual(out_message.status, "E") + @mock.patch("requests.post") def test_duplicate_success(self, mock_request): with override_settings(PYAS2={"ERROR_ON_DUPLICATE": False}): @@ -308,10 +319,23 @@ def test_duplicate_success(self, mock_request): # Make sure out message was created self.assertEqual(in_message.status, "S") out_message = Message.objects.get( - message_id=in_message.message_id + "_duplicate", direction="IN" + message_id__startswith=in_message.message_id + "_duplicate_", + direction="IN", ) self.assertEqual(out_message.status, "S") + # send it again to, should not cause duplicate error, and no create error + in_message.send_message(as2message.headers, as2message.content) + + # Make sure out message was created + self.assertEqual(in_message.status, "S") + out_messages = Message.objects.filter( + message_id__startswith=in_message.message_id + "_duplicate_", + direction="IN", + ) + for out_message in out_messages: + self.assertEqual(out_message.status, "S") + with override_settings(PYAS2={"ERROR_ON_DUPLICATE": True}): importlib.reload(settings) self.assertEqual(settings.ERROR_ON_DUPLICATE, True) diff --git a/pyas2/views.py b/pyas2/views.py index 80e94aa..47cc6b5 100644 --- a/pyas2/views.py +++ b/pyas2/views.py @@ -12,6 +12,7 @@ from django.views.decorators.csrf import csrf_exempt from django.views.decorators.clickjacking import xframe_options_exempt from django.views.generic import FormView +from django.utils.crypto import get_random_string from pyas2lib import Message as As2Message from pyas2lib import Mdn as As2Mdn from pyas2lib.exceptions import DuplicateDocument @@ -155,7 +156,7 @@ def post(self, request, *args, **kwargs): partner_id=as2message.sender.as2_name, ) ): - as2message.message_id += "_duplicate" + as2message.message_id += "_duplicate_" + get_random_string(5) # Create the Message and MDN objects message, full_fn = Message.objects.create_from_as2message(