From 7b69a2dc5a08446da5a17b20090a00cf5abdaa1d Mon Sep 17 00:00:00 2001 From: Luis Urrea Date: Fri, 3 Nov 2023 20:10:54 -0500 Subject: [PATCH] Cecabank: Add scrub implementation Description ------------------------- Add scrub logic to protect sensitive data from the credit card For Spreedly reference: SER-956 Unit test ------------------------- Finished in 25.362182 seconds. 5664 tests, 78328 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote test ------------------------- 223.32 tests/s, 3088.38 assertions/s Rubocop ------------------------- Inspecting 778 files 778 files inspected, no offenses detected --- CHANGELOG | 1 + .../gateways/cecabank/cecabank_common.rb | 4 +++ .../gateways/cecabank/cecabank_json.rb | 25 ++++++++++++++++--- .../billing/gateways/cecabank/cecabank_xml.rb | 4 --- .../remote_cecabank_rest_json_test.rb | 10 ++++++++ test/unit/gateways/cecabank_rest_json_test.rb | 12 +++++++++ 6 files changed, 49 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2f0c1587b21..c1087174eb5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,7 @@ * Quickbooks: Remove raise OAuth from extract_response_body_or_raise [almalee24] #4935 * Cecabank: Add new Cecabank gateway to use the JSON REST API [sinourain] #4920 * Cecabank: Add 3DS Global to Cecabank REST JSON gateway [sinourain] #4940 +* Cecabank: Add scrub implementation [sinourain] #4945 == Version 1.135.0 (August 24, 2023) * PaymentExpress: Correct endpoints [steveh] #4827 diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb index d71268649d6..a397c2955c8 100644 --- a/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb +++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_common.rb @@ -29,4 +29,8 @@ def initialize(options = {}) requires!(options, :merchant_id, :acquirer_bin, :terminal_id, :cypher_key) super end + + def supports_scrubbing? + true + end end diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb index a621e93f8ce..86c27e3db58 100644 --- a/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb +++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_json.rb @@ -63,6 +63,21 @@ def refund(money, identification, options = {}) handle_cancellation(:refund, money, authorization, options) end + def scrub(transcript) + before_message = transcript.gsub(%r(\\\")i, "'").scan(/{[^>]*}/).first.gsub("'", '"') + request_data = JSON.parse(before_message) + params = decode_params(request_data['parametros']). + gsub(%r(("pan\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("caducidad\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("cvv2\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]'). + gsub(%r(("csc\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]') + request_data['parametros'] = encode_params(params) + + before_message = before_message.gsub(%r(\")i, '\\\"') + after_message = request_data.to_json.gsub(%r(\")i, '\\\"') + transcript.sub(before_message, after_message) + end + private def handle_purchase(action, money, creditcard, options) @@ -176,7 +191,7 @@ def commit(action, post, method = :post) add_encryption(post) add_merchant_data(post) - params_encoded = encode_params(post) + params_encoded = encode_post_parameters(post) add_signature(post, params_encoded, options) response = parse(ssl_request(method, url(action), post.to_json, headers)) @@ -214,8 +229,12 @@ def parse(string) parse(decode_params(string)) end - def encode_params(post) - post[:parametros] = Base64.strict_encode64(post[:parametros].to_json) + def encode_post_parameters(post) + post[:parametros] = encode_params(post[:parametros].to_json) + end + + def encode_params(params) + Base64.strict_encode64(params) end def decode_params(params) diff --git a/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb b/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb index ba9f727d98f..d670e23ab49 100644 --- a/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb +++ b/lib/active_merchant/billing/gateways/cecabank/cecabank_xml.rb @@ -71,10 +71,6 @@ def refund(money, identification, options = {}) commit(CECA_ACTION_REFUND, post) end - def supports_scrubbing? - true - end - def scrub(transcript) transcript. gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). diff --git a/test/remote/gateways/remote_cecabank_rest_json_test.rb b/test/remote/gateways/remote_cecabank_rest_json_test.rb index 22249741485..3abd1878553 100644 --- a/test/remote/gateways/remote_cecabank_rest_json_test.rb +++ b/test/remote/gateways/remote_cecabank_rest_json_test.rb @@ -144,6 +144,16 @@ def test_failure_stored_credential_invalid_cit_transaction_id assert_match '810', purchase.error_code end + def test_transcript_scrubbing + transcript = capture_transcript(@gateway) do + @gateway.purchase(@amount, @credit_card, @options) + end + transcript = @gateway.scrub(transcript) + + assert_scrubbed(@credit_card.number, transcript) + assert_scrubbed(@credit_card.verification_value, transcript) + end + private def three_d_secure diff --git a/test/unit/gateways/cecabank_rest_json_test.rb b/test/unit/gateways/cecabank_rest_json_test.rb index 98b34b5963b..87ba73ef771 100644 --- a/test/unit/gateways/cecabank_rest_json_test.rb +++ b/test/unit/gateways/cecabank_rest_json_test.rb @@ -110,8 +110,20 @@ def test_failed_void assert response.test? end + def test_transcript_scrubbing + assert_equal scrubbed_transcript, @gateway.scrub(transcript) + end + private + def transcript + "opening connection to tpv.ceca.es:443...\nopened\nstarting SSL for tpv.ceca.es:443...\nSSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384\n<- \"POST /tpvweb/rest/procesos/compra HTTP/1.1\\r\\nContent-Type: application/json\\r\\nHost: tpv.ceca.es\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nContent-Length: 1145\\r\\n\\r\\n\"\n<- \"{\\\"parametros\\\":\\\"eyJhY2Npb24iOiJSRVNUX0FVVE9SSVpBQ0lPTiIsIm51bU9wZXJhY2lvbiI6IjcwNDBhYjJhMGFkOTQ5NmM2MjhiMTAyZTgzNzEyMGIxIiwiaW1wb3J0ZSI6IjEwMCIsInRpcG9Nb25lZGEiOiI5NzgiLCJleHBvbmVudGUiOiIyIiwicGFuIjoiNDUwNzY3MDAwMTAwMDAwOSIsImNhZHVjaWRhZCI6IjIwMjMxMiIsImN2djIiOiI5ODkiLCJleGVuY2lvblNDQSI6bnVsbCwiVGhyZWVEc1Jlc3BvbnNlIjoie1wiZXhlbXB0aW9uX3R5cGVcIjpudWxsLFwidGhyZWVfZHNfdmVyc2lvblwiOlwiMi4yLjBcIixcImF1dGhlbnRpY2F0aW9uX3ZhbHVlXCI6XCI0RjgwREY1MEFEQjBGOTUwMkI5MTYxOEU5QjcwNDc5MEVBQkEzNUZERkM5NzJEREREMEJGNDk4QzZBNzVFNDkyXCIsXCJkaXJlY3Rvcnlfc2VydmVyX3RyYW5zYWN0aW9uX2lkXCI6XCJhMmJmMDg5Zi1jZWZjLTRkMmMtODUwZi05MTUzODI3ZmUwNzBcIixcImFjc190cmFuc2FjdGlvbl9pZFwiOlwiMThjMzUzYjAtNzZlMy00YTRjLTgwMzMtZjE0ZmU5Y2UzOWRjXCIsXCJhdXRoZW50aWNhdGlvbl9yZXNwb25zZV9zdGF0dXNcIjpcIllcIixcInRocmVlX2RzX3NlcnZlcl90cmFuc19pZFwiOlwiOWJkOWFhOWMtM2JlYi00MDEyLThlNTItMjE0Y2NjYjI1ZWM1XCIsXCJlY29tbWVyY2VfaW5kaWNhdG9yXCI6XCIwMlwiLFwiZW5yb2xsZWRcIjpudWxsLFwiYW1vdW50XCI6XCIxMDBcIn0iLCJtZXJjaGFudElEIjoiMTA2OTAwNjQwIiwiYWNxdWlyZXJCSU4iOiIwMDAwNTU0MDAwIiwidGVybWluYWxJRCI6IjAwMDAwMDAzIn0=\\\",\\\"cifrado\\\":\\\"SHA2\\\",\\\"firma\\\":\\\"712cc9dcc17af686d220f36d68605f91e27fb0ffee448d2d8701aaa9a5068448\\\"}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Sat, 04 Nov 2023 00:34:09 GMT\\r\\n\"\n-> \"Server: Apache\\r\\n\"\n-> \"Strict-Transport-Security: max-age=31536000; includeSubDomains\\r\\n\"\n-> \"X-XSS-Protection: 1; mode=block\\r\\n\"\n-> \"X-Content-Type-Options: nosniff\\r\\n\"\n-> \"Content-Length: 300\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"\\r\\n\"\nreading 300 bytes...\n-> \"{\\\"cifrado\\\":\\\"SHA2\\\",\\\"parametros\\\":\\\"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQyMjM3MTIzMTEwNDAxMzQxMDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==\\\",\\\"firma\\\":\\\"6be9465e38a4bd28935688fdd3e34cf703c4f23f0e104eae03824838efa583b5\\\",\\\"fecha\\\":\\\"231104013412182\\\",\\\"idProceso\\\":\\\"106900640-7040ab2a0ad9496c628b102e837120b1\\\"}\"\nread 300 bytes\nConn close\n" + end + + def scrubbed_transcript + "opening connection to tpv.ceca.es:443...\nopened\nstarting SSL for tpv.ceca.es:443...\nSSL established, protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384\n<- \"POST /tpvweb/rest/procesos/compra HTTP/1.1\\r\\nContent-Type: application/json\\r\\nHost: tpv.ceca.es\\r\\nConnection: close\\r\\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\\r\\nAccept: */*\\r\\nUser-Agent: Ruby\\r\\nContent-Length: 1145\\r\\n\\r\\n\"\n<- \"{\\\"parametros\\\":\\\"eyJhY2Npb24iOiJSRVNUX0FVVE9SSVpBQ0lPTiIsIm51bU9wZXJhY2lvbiI6IjcwNDBhYjJhMGFkOTQ5NmM2MjhiMTAyZTgzNzEyMGIxIiwiaW1wb3J0ZSI6IjEwMCIsInRpcG9Nb25lZGEiOiI5NzgiLCJleHBvbmVudGUiOiIyIiwicGFuIjoiW0ZJTFRFUkVEXSIsImNhZHVjaWRhZCI6IltGSUxURVJFRF0iLCJjdnYyIjoiW0ZJTFRFUkVEXSIsImV4ZW5jaW9uU0NBIjpudWxsLCJUaHJlZURzUmVzcG9uc2UiOiJ7XCJleGVtcHRpb25fdHlwZVwiOm51bGwsXCJ0aHJlZV9kc192ZXJzaW9uXCI6XCIyLjIuMFwiLFwiYXV0aGVudGljYXRpb25fdmFsdWVcIjpcIjRGODBERjUwQURCMEY5NTAyQjkxNjE4RTlCNzA0NzkwRUFCQTM1RkRGQzk3MkREREQwQkY0OThDNkE3NUU0OTJcIixcImRpcmVjdG9yeV9zZXJ2ZXJfdHJhbnNhY3Rpb25faWRcIjpcImEyYmYwODlmLWNlZmMtNGQyYy04NTBmLTkxNTM4MjdmZTA3MFwiLFwiYWNzX3RyYW5zYWN0aW9uX2lkXCI6XCIxOGMzNTNiMC03NmUzLTRhNGMtODAzMy1mMTRmZTljZTM5ZGNcIixcImF1dGhlbnRpY2F0aW9uX3Jlc3BvbnNlX3N0YXR1c1wiOlwiWVwiLFwidGhyZWVfZHNfc2VydmVyX3RyYW5zX2lkXCI6XCI5YmQ5YWE5Yy0zYmViLTQwMTItOGU1Mi0yMTRjY2NiMjVlYzVcIixcImVjb21tZXJjZV9pbmRpY2F0b3JcIjpcIjAyXCIsXCJlbnJvbGxlZFwiOm51bGwsXCJhbW91bnRcIjpcIjEwMFwifSIsIm1lcmNoYW50SUQiOiIxMDY5MDA2NDAiLCJhY3F1aXJlckJJTiI6IjAwMDA1NTQwMDAiLCJ0ZXJtaW5hbElEIjoiMDAwMDAwMDMifQ==\\\",\\\"cifrado\\\":\\\"SHA2\\\",\\\"firma\\\":\\\"712cc9dcc17af686d220f36d68605f91e27fb0ffee448d2d8701aaa9a5068448\\\"}\"\n-> \"HTTP/1.1 200 OK\\r\\n\"\n-> \"Date: Sat, 04 Nov 2023 00:34:09 GMT\\r\\n\"\n-> \"Server: Apache\\r\\n\"\n-> \"Strict-Transport-Security: max-age=31536000; includeSubDomains\\r\\n\"\n-> \"X-XSS-Protection: 1; mode=block\\r\\n\"\n-> \"X-Content-Type-Options: nosniff\\r\\n\"\n-> \"Content-Length: 300\\r\\n\"\n-> \"Connection: close\\r\\n\"\n-> \"Content-Type: application/json\\r\\n\"\n-> \"\\r\\n\"\nreading 300 bytes...\n-> \"{\\\"cifrado\\\":\\\"SHA2\\\",\\\"parametros\\\":\\\"eyJudW1BdXQiOiIxMDEwMDAiLCJyZWZlcmVuY2lhIjoiMTIwMDQyMjM3MTIzMTEwNDAxMzQxMDYwMDcwMDAiLCJjb2RBdXQiOiIwMDAifQ==\\\",\\\"firma\\\":\\\"6be9465e38a4bd28935688fdd3e34cf703c4f23f0e104eae03824838efa583b5\\\",\\\"fecha\\\":\\\"231104013412182\\\",\\\"idProceso\\\":\\\"106900640-7040ab2a0ad9496c628b102e837120b1\\\"}\"\nread 300 bytes\nConn close\n" + end + def successful_authorize_response <<~RESPONSE {