From 6a9657ca6e2b4d24462485b1323f8c59b1159a09 Mon Sep 17 00:00:00 2001 From: Antoni Stroinski <55943882+antolo-arch@users.noreply.github.com> Date: Mon, 9 Jan 2023 16:41:19 +0100 Subject: [PATCH 1/4] removed unnecessary functions --- Adyen/util.py | 51 +-------------------------------------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/Adyen/util.py b/Adyen/util.py index faaed913..a695deba 100644 --- a/Adyen/util.py +++ b/Adyen/util.py @@ -1,53 +1,11 @@ from __future__ import absolute_import, division, unicode_literals -from itertools import chain -from collections import OrderedDict import base64 import hmac import hashlib import binascii -def generate_hpp_sig(dict_object, hmac_key): - if 'issuerId' in dict_object: - if dict_object['issuerId'] == "": - del dict_object['issuerId'] - - if not isinstance(dict_object, dict): - raise ValueError("Must Provide dictionary object") - - def escape_val(val): - if isinstance(val, int): - return val - return val.replace('\\', '\\\\').replace(':', '\\:') - - hmac_key = binascii.a2b_hex(hmac_key) - - ordered_request = OrderedDict(sorted(dict_object.items(), - key=lambda t: t[0])) - - signing_string = ':'.join( - map(escape_val, chain(map(str, ordered_request.keys()), - map(str, ordered_request.values())))) - - hm = hmac.new(hmac_key, signing_string.encode('utf-8'), hashlib.sha256) - return base64.b64encode(hm.digest()) - - -def is_valid_hmac(dict_object, hmac_key): - dict_object = dict_object.copy() - - if 'additionalData' in dict_object: - if dict_object['additionalData']['hmacSignature'] == "": - raise ValueError("Must Provide hmacSignature in additionalData") - else: - expected_sign = dict_object['additionalData']['hmacSignature'] - del dict_object['additionalData'] - merchant_sign = generate_hpp_sig(dict_object, hmac_key) - merchant_sign_str = merchant_sign.decode("utf-8") - return hmac.compare_digest(merchant_sign_str, expected_sign) - - def generate_notification_sig(dict_object, hmac_key): if 'issuerId' in dict_object: if dict_object['issuerId'] == "": @@ -56,11 +14,6 @@ def generate_notification_sig(dict_object, hmac_key): if not isinstance(dict_object, dict): raise ValueError("Must Provide dictionary object") - def escape_val(val): - if isinstance(val, int): - return val - return val.replace('\\', '\\\\') - hmac_key = binascii.a2b_hex(hmac_key) request_dict = dict(dict_object) @@ -78,9 +31,7 @@ def escape_val(val): 'success', ] - signing_string = ':'.join( - map(escape_val, map(str, ( - request_dict.get(element, '') for element in element_orders)))) + signing_string = ':'.join(map(str, (request_dict.get(element, '') for element in element_orders))) hm = hmac.new(hmac_key, signing_string.encode('utf-8'), hashlib.sha256) return base64.b64encode(hm.digest()) From 50baea2796787f71d72064aca8aa43c08f967608 Mon Sep 17 00:00:00 2001 From: Antoni Stroinski <55943882+antolo-arch@users.noreply.github.com> Date: Tue, 10 Jan 2023 09:26:54 +0100 Subject: [PATCH 2/4] fix unit tests --- Adyen/__init__.py | 1 - test/UtilTest.py | 28 ---------------------------- 2 files changed, 29 deletions(-) diff --git a/Adyen/__init__.py b/Adyen/__init__.py index 5b6ad1c8..e50d5944 100644 --- a/Adyen/__init__.py +++ b/Adyen/__init__.py @@ -3,7 +3,6 @@ from __future__ import absolute_import, division, unicode_literals from . import util -from .util import generate_hpp_sig from .exceptions import ( AdyenAPICommunicationError, AdyenAPIAuthenticationError, diff --git a/test/UtilTest.py b/test/UtilTest.py index 4aee2a49..564879dd 100644 --- a/test/UtilTest.py +++ b/test/UtilTest.py @@ -2,8 +2,6 @@ import Adyen from Adyen.util import ( - generate_hpp_sig, - is_valid_hmac, generate_notification_sig, is_valid_hmac_notification, get_query @@ -14,32 +12,6 @@ class UtilTest(unittest.TestCase): ady = Adyen.Adyen() client = ady.client - def test_hpp_request_item_hmac(self): - request = { - "pspReference": "pspReference", - "originalReference": "originalReference", - "merchantAccount": "merchantAccount", - "amount": { - "currency": "EUR", - "value": 100000 - }, - "eventCode": "EVENT", - "Success": "true" - } - key = "DFB1EB5485895CFA84146406857104AB" \ - "B4CBCABDC8AAF103A624C8F6A3EAAB00" - hmac_calculation = generate_hpp_sig(request, key) - hmac_calculation_str = hmac_calculation.decode("utf-8") - expected_hmac = "+xK25vgc9XcZFwu7WNLIwqVewyumVsgp+X+C0a2e+DE=" - self.assertTrue(hmac_calculation_str != "") - self.assertEqual(hmac_calculation_str, expected_hmac) - request['additionalData'] = {'hmacSignature': hmac_calculation_str} - hmac_validate = is_valid_hmac(request, key) - self.assertIn('additionalData', request) - self.assertDictEqual(request['additionalData'], - {'hmacSignature': hmac_calculation_str}) - self.assertTrue(hmac_validate) - def test_notification_request_item_hmac(self): request = { "pspReference": "7914073381342284", From 1bf7e7a5c4738695d5eda8d14517e4acfaa490bb Mon Sep 17 00:00:00 2001 From: Antoni Stroinski <55943882+antolo-arch@users.noreply.github.com> Date: Tue, 10 Jan 2023 13:16:14 +0100 Subject: [PATCH 3/4] Update util.py --- Adyen/util.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Adyen/util.py b/Adyen/util.py index a695deba..347fc07b 100644 --- a/Adyen/util.py +++ b/Adyen/util.py @@ -7,9 +7,6 @@ def generate_notification_sig(dict_object, hmac_key): - if 'issuerId' in dict_object: - if dict_object['issuerId'] == "": - del dict_object['issuerId'] if not isinstance(dict_object, dict): raise ValueError("Must Provide dictionary object") From 68531496933830eb36e0c161d1213c3018ac1cd3 Mon Sep 17 00:00:00 2001 From: Antoni Stroinski <55943882+antolo-arch@users.noreply.github.com> Date: Tue, 10 Jan 2023 13:52:18 +0100 Subject: [PATCH 4/4] add unit tests --- test/UtilTest.py | 18 +++++++- test/mocks/util/backslash_notification.json | 41 +++++++++++++++++++ test/mocks/util/colon_notification.json | 41 +++++++++++++++++++ .../mocks/util/forwardslash_notification.json | 41 +++++++++++++++++++ test/mocks/util/mixed_notification.json | 41 +++++++++++++++++++ 5 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 test/mocks/util/backslash_notification.json create mode 100644 test/mocks/util/colon_notification.json create mode 100644 test/mocks/util/forwardslash_notification.json create mode 100644 test/mocks/util/mixed_notification.json diff --git a/test/UtilTest.py b/test/UtilTest.py index 564879dd..0efc8101 100644 --- a/test/UtilTest.py +++ b/test/UtilTest.py @@ -1,5 +1,5 @@ import unittest - +from json import load import Adyen from Adyen.util import ( generate_notification_sig, @@ -45,6 +45,22 @@ def test_notification_request_item_hmac(self): {'hmacSignature': hmac_calculation_str}) self.assertTrue(hmac_validate) + def test_notifications_with_slashes(self): + hmac_key = "74F490DD33F7327BAECC88B2947C011FC02D014A473AAA33A8EC93E4DC069174" + with open('test/mocks/util/backslash_notification.json') as file: + backslash_notification = load(file) + self.assertTrue(is_valid_hmac_notification(backslash_notification, hmac_key)) + with open('test/mocks/util/colon_notification.json') as file: + colon_notification = load(file) + self.assertTrue(is_valid_hmac_notification(colon_notification, hmac_key)) + with open('test/mocks/util/forwardslash_notification.json') as file: + forwardslash_notification = load(file) + self.assertTrue(is_valid_hmac_notification(forwardslash_notification, hmac_key)) + with open('test/mocks/util/mixed_notification.json') as file: + mixed_notification = load(file) + self.assertTrue(is_valid_hmac_notification(mixed_notification, hmac_key)) + + def test_query_string_creation(self): query_parameters = { "pageSize":7, diff --git a/test/mocks/util/backslash_notification.json b/test/mocks/util/backslash_notification.json new file mode 100644 index 00000000..f183465e --- /dev/null +++ b/test/mocks/util/backslash_notification.json @@ -0,0 +1,41 @@ +{ + "additionalData": { + "acquirerCode": "TestPmmAcquirer", + "acquirerReference": "DZMKWLXW6N6", + "authCode": "076181", + "avsResult": "5 No AVS data provided", + "avsResultRaw": "5", + "cardSummary": "1111", + "checkout.cardAddedBrand": "visa", + "cvcResult": "1 Matches", + "cvcResultRaw": "M", + "expiryDate": "03/2030", + "hmacSignature": "nIgT81gaB5oJpn2jPXupDq68iRo2wUlBsuYjtYfwKqo=", + "paymentMethod": "visa", + "refusalReasonRaw": "AUTHORISED", + "retry.attempt1.acquirer": "TestPmmAcquirer", + "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount", + "retry.attempt1.avsResultRaw": "5", + "retry.attempt1.rawResponse": "AUTHORISED", + "retry.attempt1.responseCode": "Approved", + "retry.attempt1.scaExemptionRequested": "lowValue", + "scaExemptionRequested": "lowValue" + }, + "amount": { + "currency": "EUR", + "value": 1000 + }, + "eventCode": "AUTHORISATION", + "eventDate": "2023-01-09T16:27:29+01:00", + "merchantAccountCode": "AntoniStroinski", + "merchantReference": "\\\\slashes are fun", + "operations": [ + "CANCEL", + "CAPTURE", + "REFUND" + ], + "paymentMethod": "visa", + "pspReference": "T7FD4VM4D3RZNN82", + "reason": "076181:1111:03/2030", + "success": "true" +} \ No newline at end of file diff --git a/test/mocks/util/colon_notification.json b/test/mocks/util/colon_notification.json new file mode 100644 index 00000000..839baf76 --- /dev/null +++ b/test/mocks/util/colon_notification.json @@ -0,0 +1,41 @@ +{ + "additionalData": { + "acquirerCode": "TestPmmAcquirer", + "acquirerReference": "8NQH5BNF58M", + "authCode": "039404", + "avsResult": "5 No AVS data provided", + "avsResultRaw": "5", + "cardSummary": "1111", + "checkout.cardAddedBrand": "visa", + "cvcResult": "1 Matches", + "cvcResultRaw": "M", + "expiryDate": "03/2030", + "hmacSignature": "2EQYm7YJpKO4EtHSPu55SQTyWf8dkW5u2nD1tJFpViA=", + "paymentMethod": "visa", + "refusalReasonRaw": "AUTHORISED", + "retry.attempt1.acquirer": "TestPmmAcquirer", + "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount", + "retry.attempt1.avsResultRaw": "5", + "retry.attempt1.rawResponse": "AUTHORISED", + "retry.attempt1.responseCode": "Approved", + "retry.attempt1.scaExemptionRequested": "lowValue", + "scaExemptionRequested": "lowValue" + }, + "amount": { + "currency": "EUR", + "value": 1000 + }, + "eventCode": "AUTHORISATION", + "eventDate": "2023-01-10T13:40:54+01:00", + "merchantAccountCode": "AntoniStroinski", + "merchantReference": ":slashes are fun", + "operations": [ + "CANCEL", + "CAPTURE", + "REFUND" + ], + "paymentMethod": "visa", + "pspReference": "M8NB66SBZSGLNK82", + "reason": "039404:1111:03/2030", + "success": "true" +} \ No newline at end of file diff --git a/test/mocks/util/forwardslash_notification.json b/test/mocks/util/forwardslash_notification.json new file mode 100644 index 00000000..bf9eb17d --- /dev/null +++ b/test/mocks/util/forwardslash_notification.json @@ -0,0 +1,41 @@ +{ + "amount": { + "value": 1000, + "currency": "EUR" + }, + "reason": "087330:1111:03/2030", + "success": "true", + "eventCode": "AUTHORISATION", + "eventDate": "2023-01-10T13:37:30+01:00", + "operations": [ + "CANCEL", + "CAPTURE", + "REFUND" + ], + "pspReference": "X3GWNS6KJ8NKGK82", + "paymentMethod": "visa", + "additionalData": { + "authCode": "087330", + "avsResult": "5 No AVS data provided", + "cvcResult": "1 Matches", + "expiryDate": "03/2030", + "cardSummary": "1111", + "acquirerCode": "TestPmmAcquirer", + "avsResultRaw": "5", + "cvcResultRaw": "M", + "hmacSignature": "9Z0xdpG9Xi3zcmXv14t/BvMBut77O/Xq9D4CQXSDUi4=", + "paymentMethod": "visa", + "refusalReasonRaw": "AUTHORISED", + "acquirerReference": "HHCCC326PH6", + "scaExemptionRequested": "lowValue", + "checkout.cardAddedBrand": "visa", + "retry.attempt1.acquirer": "TestPmmAcquirer", + "retry.attempt1.rawResponse": "AUTHORISED", + "retry.attempt1.avsResultRaw": "5", + "retry.attempt1.responseCode": "Approved", + "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount", + "retry.attempt1.scaExemptionRequested": "lowValue" + }, + "merchantReference": "//slashes are fun", + "merchantAccountCode": "AntoniStroinski" +} \ No newline at end of file diff --git a/test/mocks/util/mixed_notification.json b/test/mocks/util/mixed_notification.json new file mode 100644 index 00000000..cbcfde3c --- /dev/null +++ b/test/mocks/util/mixed_notification.json @@ -0,0 +1,41 @@ +{ + "additionalData": { + "acquirerCode": "TestPmmAcquirer", + "acquirerReference": "J8DXDJ2PV6P", + "authCode": "052095", + "avsResult": "5 No AVS data provided", + "avsResultRaw": "5", + "cardSummary": "1111", + "checkout.cardAddedBrand": "visa", + "cvcResult": "1 Matches", + "cvcResultRaw": "M", + "expiryDate": "03/2030", + "hmacSignature": "CZErGCNQaSsxbaQfZaJlakqo7KPP+mIa8a+wx3yNs9A=", + "paymentMethod": "visa", + "refusalReasonRaw": "AUTHORISED", + "retry.attempt1.acquirer": "TestPmmAcquirer", + "retry.attempt1.acquirerAccount": "TestPmmAcquirerAccount", + "retry.attempt1.avsResultRaw": "5", + "retry.attempt1.rawResponse": "AUTHORISED", + "retry.attempt1.responseCode": "Approved", + "retry.attempt1.scaExemptionRequested": "lowValue", + "scaExemptionRequested": "lowValue" + }, + "amount": { + "currency": "EUR", + "value": 1000 + }, + "eventCode": "AUTHORISATION", + "eventDate": "2023-01-10T13:42:29+01:00", + "merchantAccountCode": "AntoniStroinski", + "merchantReference": "\\:/\\/slashes are fun", + "operations": [ + "CANCEL", + "CAPTURE", + "REFUND" + ], + "paymentMethod": "visa", + "pspReference": "ZVWN7D3WSMK2WN82", + "reason": "052095:1111:03/2030", + "success": "true" +} \ No newline at end of file