From 6ad84403c81648bcb36e7ec0f6188bff91862c78 Mon Sep 17 00:00:00 2001 From: Mark Coburn <mark.coburn@contractors.mx.com> Date: Mon, 27 Jan 2025 08:13:36 -0800 Subject: [PATCH] fix: initial log masking support --- .../mx/path/model/mdx/model/MdxLogMasker.java | 427 +++++++++ .../model/mdx/model/MdxLogMaskerTest.groovy | 878 ++++++++++++++++++ .../web/filter/PathRequestLoggingFilter.java | 15 +- .../PathRequestLoggingFilterTest.groovy | 4 +- 4 files changed, 1314 insertions(+), 10 deletions(-) create mode 100644 mdx-models/src/main/java/com/mx/path/model/mdx/model/MdxLogMasker.java create mode 100644 mdx-models/src/test/groovy/com/mx/path/model/mdx/model/MdxLogMaskerTest.groovy diff --git a/mdx-models/src/main/java/com/mx/path/model/mdx/model/MdxLogMasker.java b/mdx-models/src/main/java/com/mx/path/model/mdx/model/MdxLogMasker.java new file mode 100644 index 00000000..e4ebbda7 --- /dev/null +++ b/mdx-models/src/main/java/com/mx/path/model/mdx/model/MdxLogMasker.java @@ -0,0 +1,427 @@ +package com.mx.path.model.mdx.model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.mx.path.core.common.lang.Strings; +import com.mx.path.core.common.security.LogValueRegex; + +public class MdxLogMasker { + + private static final HashSet<String> MDX_PAYLOAD_REGEX = new HashSet<>(); + private static final HashSet<String> HEADER_KEY_SET = new HashSet<>(); + private static final List<Pattern> PAYLOAD_PATTERN_SET = new ArrayList<>(); + private static final String MASK = "**MASKED**"; + + public static String maskHeaderValue(String header, String value) { + if (HEADER_KEY_SET.contains(header.toLowerCase(Locale.ENGLISH))) { + return MASK; + } + + return value; + } + + public static String maskPayload(String payload) { + return applyPatternsToPayload(payload); + } + + private static void registerHeaderKeys() { + HEADER_KEY_SET.add("mdx-session-key"); + HEADER_KEY_SET.add("mx-auth-token"); + HEADER_KEY_SET.add("mx-refresh-token"); + HEADER_KEY_SET.add("mx-session-key"); + HEADER_KEY_SET.add("x-csrf-token"); + HEADER_KEY_SET.add("x-request-token"); + } + + private static void registerPayloadPatterns() { + buildAccountPayloadPatterns(); + buildAchTransferPayloadPatterns(); + buildAuthorizationPayloadPatterns(); + buildChallengesPayloadPatterns(); + buildCheckPayloadPatterns(); + buildCreditReportPayloadPatterns(); + buildCrossAccountTransferPayloadPatterns(); + buildDevicePayloadPatterns(); + buildDisputePayloadPatterns(); + buildDocumentPayloadPatterns(); + buildIdentificationPayloadPatterns(); + buildManagedCardsPayloadPatterns(); + buildOriginationPayloadPatterns(); + buildPaymentPayloadPatterns(); + buildPayoutPayloadPatterns(); + buildProfilePayloadPatterns(); + buildRemoteDepositPayloadPatterns(); + buildTransferPayloadPatterns(); + + for (String regex : MDX_PAYLOAD_REGEX) { + Pattern mask = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); + PAYLOAD_PATTERN_SET.add(mask); + } + } + + private static void buildAccountPayloadPatterns() { + // Account - https://developer.mx.com/mdx/v5/#mdx-data-models-account-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("nickname")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_transit_number")); + + // Account - MDX v5 + MDX_PAYLOAD_REGEX.add(xmlElementRegex("account_number")); + MDX_PAYLOAD_REGEX.add(xmlElementRegex("id")); + MDX_PAYLOAD_REGEX.add(xmlElementRegex("name")); + MDX_PAYLOAD_REGEX.add(xmlElementRegex("nickname")); + MDX_PAYLOAD_REGEX.add(xmlElementRegex("routing_number")); + MDX_PAYLOAD_REGEX.add(xmlElementRegex("routing_transit_number")); + + // AccountDetails - https://developer.mx.com/drafts/mdx/accounts/#accounts-account-details-additional + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("extended_fields")); + + // AccountNumbers - https://developer.mx.com/drafts/mdx/accounts/#accounts-retrieve-full-account-and-routing-numbers + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_number")); + + // AccountOwnerDetails - https://developer.mx.com/mdx/v5/index.html#mdx-data-models-account-owners + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("address")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("city")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("owner_name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("phone")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("state")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("zip_code")); + + // DeliveryMethod (Alert) - https://developer.mx.com/drafts/mdx/accounts/#accounts-alerts-delivery-method + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("description")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("target")); + + // Transaction - https://developer.mx.com/mdx/v5/#mdx-data-models-transaction-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("check_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("description")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + + // Transaction - MDX v5 + MDX_PAYLOAD_REGEX.add(xmlElementRegex("account_id")); + MDX_PAYLOAD_REGEX.add(xmlElementRegex("check_number")); + MDX_PAYLOAD_REGEX.add(xmlElementRegex("description")); + MDX_PAYLOAD_REGEX.add(xmlElementRegex("id")); + MDX_PAYLOAD_REGEX.add(xmlElementRegex("memo")); + } + + private static void buildAchTransferPayloadPatterns() { + // AccountListOptions|AchAccountListOptions - https://developer.mx.com/drafts/mdx/ach_transfer/#accounts-ach-accounts-list-ach-accounts + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("ach_account_id")); + + // AchAccount - https://developer.mx.com/drafts/mdx/ach_transfer/#accounts-ach-accounts-vs-held-accounts + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("bank_name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("nickname")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_number")); + + // AchScheduledTransfer - https://developer.mx.com/drafts/mdx/ach_transfer/#ach-scheduled-transfers + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_ach_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_ach_account_id")); + + // AchTransfer - https://developer.mx.com/drafts/mdx/ach_transfer/#ach-transfers + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_ach_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_ach_account_id")); + } + + private static void buildAuthorizationPayloadPatterns() { + // Authorization - https://developer.mx.com/drafts/mdx/authorization/#authorizations + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("cookies")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("headers")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("token")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("tokens")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("url")); + } + + private static void buildChallengesPayloadPatterns() { + // Question - https://developer.mx.com/drafts/mdx/challenge/#draft-documentation-question-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer")); + } + + private static void buildCheckPayloadPatterns() { + // CheckImage - https://developer.mx.com/drafts/mdx/accounts/index.html#check-images-check-image-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("back_image")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("check_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("front_image")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("transaction_id")); + } + + private static void buildCreditReportPayloadPatterns() { + // CreditReportSettings - https://developer.mx.com/drafts/mdx/credit_report/index.html#credit-report-settings-credit-report-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("first_name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("last_name")); + } + + private static void buildCrossAccountTransferPayloadPatterns() { + // CrossAccountRecurringTransfer - https://developer.mx.com/drafts/mdx/cross_account_transfer/index.html#recurring-cross-account-transfers-delete-a-destination-data-flow + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("destination_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + + // CrossAccountTransfer - https://developer.mx.com/drafts/mdx/cross_account_transfer/index.html#cross-account-transfers-cross-account-transfer-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("destination_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + + // DestinationAccount - https://developer.mx.com/drafts/mdx/cross_account_transfer/index.html#destinations + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_holder")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("id")); + + // FeeListOptions - https://developer.mx.com/drafts/mdx/cross_account_transfer/index.html#fees-list-cross-account-transfer-fees + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("destination_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id")); + } + + private static void buildDevicePayloadPatterns() { + // VerificationMethod - https://developer.mx.com/drafts/mdx/device/index.html#verification-methods + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("phone_number")); + } + + private static void buildDisputePayloadPatterns() { + // Dispute - https://developer.mx.com/drafts/mdx/accounts/index.html#disputes-dispute + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("card_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("case_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("contact_phone")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("member_name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("source_image")); + } + + private static void buildDocumentPayloadPatterns() { + // DeliveryPreferences - https://developer.mx.com/drafts/mdx/documents/index.html#documents-update-delivery-preferences + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + + // Document - https://developer.mx.com/drafts/mdx/documents/index.html#documents-document-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("file_data")); + + // DocumentSearch - https://developer.mx.com/drafts/mdx/documents/index.html#documents-list-documents + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + } + + private static void buildIdentificationPayloadPatterns() { + // Authentication - https://developer.mx.com/drafts/mdx/id/#authentications-authenticate + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("access_token")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("client_device_token")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_make")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_model")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_operating_system")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_operating_system_version")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonNumber("device_latitude")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonNumber("device_longitude")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("device_iovation_token")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("login")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("password")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("refresh_token")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("token")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("userkey")); + + // MfaChallenge - https://developer.mx.com/drafts/mdx/id/#authentications-multi-factor-authentication-version-20240213-mfa-challenge-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer")); + + // MfaChallengeQuestion - https://developer.mx.com/drafts/mdx/id/#authentications-multi-factor-authentication-version-20240213-question-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer")); + } + + private static void buildManagedCardsPayloadPatterns() { + // ManagedCard - https://developer.mx.com/drafts/mdx/managed_cards/index.html#managed-cards + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("expiration_on_card")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("image_url")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("name_on_card")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("new_pin")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("pin")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("unmasked_cvv")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("unmasked_number_on_card")); + + // TravelSchedule - https://developer.mx.com/drafts/mdx/managed_cards/index.html#destinations-list-global-destinations + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonArray("card_ids")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("primary_phone_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("secondary_phone_number")); + } + + private static void buildOriginationPayloadPatterns() { + // Origination - https://developer.mx.com/drafts/mdx/origination/index.html#mdx-origination-origination-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("login_token")); + } + + private static void buildPaymentPayloadPatterns() { + // Payee - https://developer.mx.com/drafts/mdx/payment/#payees-payee-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number")); + + // Payment - https://developer.mx.com/drafts/mdx/payment/#payments + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_transit_number")); + + // RecurringPayment - https://developer.mx.com/drafts/mdx/payment/#recurring-payments-recurring-payment-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + } + + private static void buildPayoutPayloadPatterns() { + // ChallengeAnswer - https://developer.mx.com/drafts/mdx/payout/#dealing-with-challenges-answer-a-challenge + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer")); + + // Payout - https://developer.mx.com/drafts/mdx/payout/#payouts-payout-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("challenge_answer")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("sender_name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("token")); + + // PayoutContactMethod - https://developer.mx.com/drafts/mdx/payout/#payout-contact-methods-payout-contact-method-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("phone_number")); + + // PayoutMethod - https://developer.mx.com/drafts/mdx/payout/#payout-methods-payout-method-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("routing_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("send_to")); + + // PayoutRequest - https://developer.mx.com/drafts/mdx/payout/#payout-requests-payout-request-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + + // PayoutSettings + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address")); + + // Question + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("answer")); + + // Recipient - https://developer.mx.com/drafts/mdx/payout/#recipients-recipient-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("first_name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("last_name")); + + // RecurringPayout - https://developer.mx.com/drafts/mdx/payout/#recurring-payouts-recurring-payout-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("challenge_answer")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("token")); + } + + private static void buildProfilePayloadPatterns() { + // Address - https://developer.mx.com/drafts/mdx/profile/#addresses-address-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("address_line_one")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("address_line_two")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("city")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("country")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("postal_code")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("state")); + + // Email - https://developer.mx.com/drafts/mdx/profile/#email-addresses-email-address-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("email_address")); + + // NewPassword - https://developer.mx.com/drafts/mdx/profile/#update-password-new-update-password-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("current_password")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("new_password")); + + // NewUserName - https://developer.mx.com/drafts/mdx/profile/#update-username-update-username-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("new_username")); + + // Phone - https://developer.mx.com/drafts/mdx/profile/#phone-numbers-phone-number-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("phone_number")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("work_extension")); + + // Profile - https://developer.mx.com/drafts/mdx/profile/#profile-profile-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("birth_date_on")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("first_name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("gender")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("last_name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("middle_name")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("ssn")); + } + + private static void buildRemoteDepositPayloadPatterns() { + // RemoteDeposit - https://developer.mx.com/drafts/mdx/remote_deposit/#remote-deposits-remote-deposit-fields + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("back_of_check_image")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("front_of_check_image")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + } + + private static void buildTransferPayloadPatterns() { + // AccountListOptions - https://developer.mx.com/drafts/mdx/transfer/#transfer-accounts-list-accounts + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("account_id")); + + // FeeListOptions - https://developer.mx.com/drafts/mdx/transfer/#fees-list-transfer-fees + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id")); + + // RecurringTransfer - https://developer.mx.com/drafts/mdx/transfer/#recurring-transfers-data-flows + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id")); + + // Transfer - https://developer.mx.com/drafts/mdx/transfer/#transfers-data-flows + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("from_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("memo")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("repayment_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("to_account_id")); + + // TransferAmountOptionListOptions - https://developer.mx.com/drafts/mdx/transfer/#transfer-amount-options-list-all-transfer-amount-options + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("destination_account_id")); + MDX_PAYLOAD_REGEX.add(LogValueRegex.jsonString("source_account_id")); + } + + private static String xmlElementRegex(String fieldName) { + return String.format("\\<[\\w:]*%1$s\\>([\\s\\S]+?)\\<\\/[\\w:]*%1$s\\>", fieldName); + } + + private static String applyPatternsToPayload(String payload) { + for (Pattern p : PAYLOAD_PATTERN_SET) { + Matcher m = p.matcher(payload); + int start = 0; + + while (m.find(start)) { + String patternMatch = m.group(); + + // Apply masking to all matching groups + for (int i = 1; i <= m.groupCount(); i++) { + if (!Strings.isBlank(m.group(i))) { + patternMatch = patternMatch.replace(m.group(i), MASK); + } + } + payload = payload.replace(m.group(), patternMatch); + start = m.start() + 1; + } + } + + return payload; + } + + static { + registerHeaderKeys(); + registerPayloadPatterns(); + } +} diff --git a/mdx-models/src/test/groovy/com/mx/path/model/mdx/model/MdxLogMaskerTest.groovy b/mdx-models/src/test/groovy/com/mx/path/model/mdx/model/MdxLogMaskerTest.groovy new file mode 100644 index 00000000..c38788e0 --- /dev/null +++ b/mdx-models/src/test/groovy/com/mx/path/model/mdx/model/MdxLogMaskerTest.groovy @@ -0,0 +1,878 @@ +package com.mx.path.model.mdx.model + +import spock.lang.Specification +import spock.lang.Unroll + +class MdxLogMaskerTest extends Specification { + @Unroll + def "maskHeaderValue() masks #header to #expectedResult"() { + when: + String result = MdxLogMasker.maskHeaderValue(header, "something_sensitive") + + then: + result == expectedResult + + where: + header || expectedResult + "mx-refresh-token" || "**MASKED**" + } + + @Unroll + def "maskPayload() masks Account JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_number\":\"1234567890\"" || "\"account_number\":\"**MASKED**\"" + "\"id\":\"ACCOUNT-123\"" || "\"id\":\"**MASKED**\"" + "\"name\":\"Checking Account\"" || "\"name\":\"**MASKED**\"" + "\"nickname\":\"My Checking\"" || "\"nickname\":\"**MASKED**\"" + "\"routing_number\":\"121122676\"" || "\"routing_number\":\"**MASKED**\"" + "\"routing_transit_number\":\"0260-0959-3\"" || "\"routing_transit_number\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Account XML fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "<account_number>1234567890</account_number>" || "<account_number>**MASKED**</account_number>" + "<id>ACCOUNT-123</id>" || "<id>**MASKED**</id>" + "<name>Checking Account</name>" || "<name>**MASKED**</name>" + "<nickname>My Checking</nickname>" || "<nickname>**MASKED**</nickname>" + "<routing_number>121122676</routing_number>" || "<routing_number>**MASKED**</routing_number>" + "<routing_transit_number>0260-0959-3</routing_transit_number>" || "<routing_transit_number>**MASKED**</routing_transit_number>" + } + + @Unroll + def "maskPayload() masks AccountDetails JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"extended_fields\":[{\n \"type\": \"STRING\",\n \"name\": \"secret field\",\n \"string_value\": \"secret value\"\n}]" || "\"extended_fields\":[**MASKED**]" + } + + @Unroll + def "maskPayload() masks AccountNumbers JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_number\":\"1234567890\"" || "\"account_number\":\"**MASKED**\"" + "\"routing_number\":\"121122676\"" || "\"routing_number\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks AccountOwnerDetails JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"address\":\"3401 N Thanksgiving Way #500\"" || "\"address\":\"**MASKED**\"" + "\"city\":\"Lehi\"" || "\"city\":\"**MASKED**\"" + "\"email\":\"john.doe@example.com\"" || "\"email\":\"**MASKED**\"" + "\"owner_name\":\"John Doe\"" || "\"owner_name\":\"**MASKED**\"" + "\"phone\":\"1-222-333-4444\"" || "\"phone\":\"**MASKED**\"" + "\"zip_code\":\"84043\"" || "\"zip_code\":\"**MASKED**\"" + "\"state\":\"UT\"" || "\"state\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks DeliveryMethod JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"description\":\"3401 N Thanksgiving Way #500\"" || "\"description\":\"**MASKED**\"" + "\"target\":\"Email - *1@example.com\"" || "\"target\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Transaction JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"check_number\":\"1234\"" || "\"check_number\":\"**MASKED**\"" + "\"description\":\"Test transaction\"" || "\"description\":\"**MASKED**\"" + "\"id\":\"TRANSACTION-123\"" || "\"id\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Transaction XML fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "<account_id>ACCOUNT-123</account_id>" || "<account_id>**MASKED**</account_id>" + "<check_number>1234</check_number>" || "<check_number>**MASKED**</check_number>" + "<description>Test transaction</description>" || "<description>**MASKED**</description>" + "<id>TRANSACTION-123</id>" || "<id>**MASKED**</id>" + "<memo>Test memo</memo>" || "<memo>**MASKED**</memo>" + } + + @Unroll + def "maskPayload() masks AccountListOptions JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"ach_account_id\":\"ACCOUNT-456\"" || "\"ach_account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks AchAccount JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"account_number\":\"1234567890\"" || "\"account_number\":\"**MASKED**\"" + "\"bank_name\":\"Test Credit Union\"" || "\"bank_name\":\"**MASKED**\"" + "\"id\":\"ACH-ACCOUNT-123\"" || "\"id\":\"**MASKED**\"" + "\"name\":\"Test Name\"" || "\"name\":\"**MASKED**\"" + "\"nickname\":\"Test Nickname\"" || "\"nickname\":\"**MASKED**\"" + "\"routing_number\":\"121122676\"" || "\"routing_number\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks AchScheduledTransfer JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"from_account_id\":\"ACCOUNT-1\"" || "\"from_account_id\":\"**MASKED**\"" + "\"from_ach_account_id\":\"ACH-ACCOUNT-1\"" || "\"from_ach_account_id\":\"**MASKED**\"" + "\"id\":\"ACH-TRANSFER-1\"" || "\"id\":\"**MASKED**\"" + "\"memo\":\"Test ACH Transfer\"" || "\"memo\":\"**MASKED**\"" + "\"to_account_id\":\"ACCOUNT-2\"" || "\"to_account_id\":\"**MASKED**\"" + "\"to_ach_account_id\":\"ACH-ACCOUNT-2\"" || "\"to_ach_account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks AchTransfer JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"from_account_id\":\"ACCOUNT-1\"" || "\"from_account_id\":\"**MASKED**\"" + "\"from_ach_account_id\":\"ACH-ACCOUNT-1\"" || "\"from_ach_account_id\":\"**MASKED**\"" + "\"id\":\"ACH-TRANSFER-1\"" || "\"id\":\"**MASKED**\"" + "\"memo\":\"Test ACH Transfer\"" || "\"memo\":\"**MASKED**\"" + "\"to_account_id\":\"ACCOUNT-2\"" || "\"to_account_id\":\"**MASKED**\"" + "\"to_ach_account_id\":\"ACH-ACCOUNT-2\"" || "\"to_ach_account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Authorization JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-1\"" || "\"account_id\":\"**MASKED**\"" + "\"cookies\":[{\n \"cookie1\": \"value1\",\n \"cookie2\": \"value2\"\n}]" || "\"cookies\":[**MASKED**]" + "\"device_id\":\"ACH-TRANSFER-1\"" || "\"device_id\":\"**MASKED**\"" + "\"headers\":[{\n \"header1\": \"value1\",\n \"header2\": \"value2\"\n}]" || "\"headers\":[**MASKED**]" + "\"token\":\"j8tGkb0STI827r0aMBOvJN9tBU08nFsc8gSivZLIfBw=\"" || "\"token\":\"**MASKED**\"" + "\"tokens\":[{\n \"token1\": \"value1\",\n \"token2\": \"value2\"\n}]" || "\"tokens\":[**MASKED**]" + "\"url\":\"http:/test.example.com/sso?token=b1d36291310644fe921b8bdff8d08d61\"" || "\"url\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Question JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"answer\":\"Test answer\"" || "\"answer\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks CheckImage JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-1\"" || "\"account_id\":\"**MASKED**\"" + "\"back_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"back_image\":\"**MASKED**\"" + "\"check_number\":\"1234\"" || "\"check_number\":\"**MASKED**\"" + "\"front_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"front_image\":\"**MASKED**\"" + "\"transaction_id\":\"TRANSACTION-1\"" || "\"transaction_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks CreditReportSettings JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"first_name\":\"John\"" || "\"first_name\":\"**MASKED**\"" + "\"last_name\":\"Doe\"" || "\"last_name\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks CrossAccountRecurringTransfer JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"destination_id\":\"DESTINATION-123\"" || "\"destination_id\":\"**MASKED**\"" + "\"from_account_id\":\"ACCOUNT-123\"" || "\"from_account_id\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks CrossAccountRecurringTransfer JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"destination_id\":\"DESTINATION-123\"" || "\"destination_id\":\"**MASKED**\"" + "\"from_account_id\":\"ACCOUNT-123\"" || "\"from_account_id\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks CrossAccountTransfer JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"destination_id\":\"DESTINATION-123\"" || "\"destination_id\":\"**MASKED**\"" + "\"from_account_id\":\"ACCOUNT-123\"" || "\"from_account_id\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks DestinationAccount JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_holder\":\"Test account holder\"" || "\"account_holder\":\"**MASKED**\"" + "\"account_number\":\"1234567890\"" || "\"account_number\":\"**MASKED**\"" + "\"id\":\"ACCOUNT-123\"" || "\"id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks FeeListOptions JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"destination_id\":\"DESTINATION-123\"" || "\"destination_id\":\"**MASKED**\"" + "\"from_account_id\":\"ACCOUNT-123\"" || "\"from_account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks VerificationMethod JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"email_address\":\"john.doe@example.com\"" || "\"email_address\":\"**MASKED**\"" + "\"phone_number\":\"1-222-333-4444\"" || "\"phone_number\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Dispute JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"card_id\":\"CARD-123\"" || "\"card_id\":\"**MASKED**\"" + "\"case_number\":\"182744AAQ093\"" || "\"case_number\":\"**MASKED**\"" + "\"contact_phone\":\"1-222-333-4444\"" || "\"contact_phone\":\"**MASKED**\"" + "\"member_name\":\"John Doe\"" || "\"member_name\":\"**MASKED**\"" + "\"source_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"source_image\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks DeliveryPreferences JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Document JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"file_data\":\"data:application/pdf;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"file_data\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks DocumentSearch JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Authentication JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"access_token\":\"167f0a358b054953ae7c9527e2d273a9\"" || "\"access_token\":\"**MASKED**\"" + "\"client_device_token\":\"UNIQUE_TOKEN_FOR_THIS_DEVICE\"" || "\"client_device_token\":\"**MASKED**\"" + "\"device_make\":\"Apple\"" || "\"device_make\":\"**MASKED**\"" + "\"device_model\":\"iPhone X\"" || "\"device_model\":\"**MASKED**\"" + "\"device_operating_system\":\"iOS\"" || "\"device_operating_system\":\"**MASKED**\"" + "\"device_operating_system_version\":\"11.4.1\"" || "\"device_operating_system_version\":\"**MASKED**\"" + "\"device_latitude\":40.4296944" || "\"device_latitude\":**MASKED**" + "\"device_longitude\":-111.8931454" || "\"device_longitude\":**MASKED**" + "\"device_iovation_token\":\"ac1d3a2455a9444ebc942c7842660ed2\"" || "\"device_iovation_token\":\"**MASKED**\"" + "\"login\":\"johndoe\"" || "\"login\":\"**MASKED**\"" + "\"password\":\"topsecret\"" || "\"password\":\"**MASKED**\"" + "\"refresh_token\":\"2ed7c86985d0404a8728fe4c621711b5\"" || "\"refresh_token\":\"**MASKED**\"" + "\"token\":\"b43cac3cfbcc45d4abecef87064b63ce\"" || "\"token\":\"**MASKED**\"" + "\"userkey\":\"25ff030acfba4cdf91618dc23b4795e4\"" || "\"userkey\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks MfaChallenge JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"answer\":\"12345\"" || "\"answer\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks MfaChallengeQuestion JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"answer\":\"12345\"" || "\"answer\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks ManagedCard JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"expiration_on_card\":\"06/97\"" || "\"expiration_on_card\":\"**MASKED**\"" + "\"image_url\":\"https://logos.co/customers/123/card_image.jpg\"" || "\"image_url\":\"**MASKED**\"" + "\"name_on_card\":\"John Doe\"" || "\"name_on_card\":\"**MASKED**\"" + "\"new_pin\":\"12345\"" || "\"new_pin\":\"**MASKED**\"" + "\"pin\":\"12345\"" || "\"pin\":\"**MASKED**\"" + "\"unmasked_cvv\":\"123\"" || "\"unmasked_cvv\":\"**MASKED**\"" + "\"unmasked_number_on_card\":\"1111222233334444\"" || "\"unmasked_number_on_card\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks TravelSchedule JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"card_ids\":[\"CARD_ID_1\", \"CARD_ID_2\"]" || "\"card_ids\":[**MASKED**]" + "\"email_address\":\"john.doe@example.com\"" || "\"email_address\":\"**MASKED**\"" + "\"primary_phone_number\":\"1-222-333-4444\"" || "\"primary_phone_number\":\"**MASKED**\"" + "\"secondary_phone_number\":\"1-333-444-5555\"" || "\"secondary_phone_number\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Origination JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"login_token\":\"123456789\"" || "\"login_token\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Payee JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_number\":\"123456789\"" || "\"account_number\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Payment JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + "\"routing_transit_number\":\"0260-0959-3\"" || "\"routing_transit_number\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks RecurringPayment JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks ChallengeAnswer (Payout) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"answer\":\"Test answer\"" || "\"answer\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Payout JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"challenge_answer\":\"Test answer\"" || "\"challenge_answer\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + "\"sender_name\":\"John Doe\"" || "\"sender_name\":\"**MASKED**\"" + "\"token\":\"f48c8aa0e66d4d53979b36c4c46c8abc\"" || "\"token\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks PayoutContactMethod JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"email_address\":\"john.doe@example.com\"" || "\"email_address\":\"**MASKED**\"" + "\"phone_number\":\"1-222-333-4444\"" || "\"phone_number\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks PayoutMethod JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_number\":\"1234567890\"" || "\"account_number\":\"**MASKED**\"" + "\"routing_number\":\"121122676\"" || "\"routing_number\":\"**MASKED**\"" + "\"send_to\":\"john.doe@example.com\"" || "\"send_to\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks PayoutRequest JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks PayoutSettings JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"email_address\":\"john.doe@example.com\"" || "\"email_address\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Question (Payout) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"answer\":\"Test answer\"" || "\"answer\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Recipient (Payout) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"first_name\":\"John\"" || "\"first_name\":\"**MASKED**\"" + "\"last_name\":\"Doe\"" || "\"last_name\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks RecurringPayout JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"challenge_answer\":\"Test answer\"" || "\"challenge_answer\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + "\"token\":\"41bb602a0ab44896ad28d7de270e24ce\"" || "\"token\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Address (Profile) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"ACCOUNT-123\"" || "\"account_id\":\"**MASKED**\"" + "\"address_line_one\":\"3401 N Thanksgiving Way #500\"" || "\"address_line_one\":\"**MASKED**\"" + "\"address_line_two\":\"Test address line 2\"" || "\"address_line_two\":\"**MASKED**\"" + "\"city\":\"Lehi\"" || "\"city\":\"**MASKED**\"" + "\"country\":\"US\"" || "\"country\":\"**MASKED**\"" + "\"postal_code\":\"84043\"" || "\"postal_code\":\"**MASKED**\"" + "\"state\":\"UT\"" || "\"state\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Email (Profile) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"email_address\":\"john.doe@example.com\"" || "\"email_address\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks NewPassword (Profile) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"current_password\":\"topsecret1\"" || "\"current_password\":\"**MASKED**\"" + "\"new_password\":\"topsecret2\"" || "\"new_password\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks NewUserName (Profile) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"new_username\":\"johndoe\"" || "\"new_username\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Phone (Profile) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"phone_number\":\"1-222-333-4444\"" || "\"phone_number\":\"**MASKED**\"" + "\"work_extension\":\"1234\"" || "\"work_extension\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Profile JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"birth_date_on\":\"2015-01-01\"" || "\"birth_date_on\":\"**MASKED**\"" + "\"first_name\":\"John\"" || "\"first_name\":\"**MASKED**\"" + "\"gender\":\"MALE\"" || "\"gender\":\"**MASKED**\"" + "\"last_name\":\"Doe\"" || "\"last_name\":\"**MASKED**\"" + "\"middle_name\":\"William\"" || "\"middle_name\":\"**MASKED**\"" + "\"ssn\":\"123456789\"" || "\"ssn\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks RemoteDeposit JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"Account-123\"" || "\"account_id\":\"**MASKED**\"" + "\"back_of_check_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"back_of_check_image\":\"**MASKED**\"" + "\"front_of_check_image\":\"data:image/png;base64,iVBORw0KGAAAUAAAAFCJggg==\"" || "\"front_of_check_image\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks AccountListOptions (Transfer) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"account_id\":\"Account-123\"" || "\"account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks FeeListOptions (Transfer) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"from_account_id\":\"Account-123\"" || "\"from_account_id\":\"**MASKED**\"" + "\"to_account_id\":\"Account-456\"" || "\"to_account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks RecurringTransfer JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"from_account_id\":\"Account-123\"" || "\"from_account_id\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + "\"to_account_id\":\"Account-456\"" || "\"to_account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks Transfer JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"from_account_id\":\"Account-123\"" || "\"from_account_id\":\"**MASKED**\"" + "\"memo\":\"Test memo\"" || "\"memo\":\"**MASKED**\"" + "\"repayment_account_id\":\"Account-456\"" || "\"repayment_account_id\":\"**MASKED**\"" + "\"to_account_id\":\"Account-789\"" || "\"to_account_id\":\"**MASKED**\"" + } + + @Unroll + def "maskPayload() masks TransferAmountOptionListOptions (Transfer) JSON fields"() { + when: + String result = MdxLogMasker.maskPayload(payload) + + then: + result == expectedResult + + where: + payload || expectedResult + "\"destination_account_id\":\"Account-123\"" || "\"destination_account_id\":\"**MASKED**\"" + "\"source_account_id\":\"Account-456\"" || "\"source_account_id\":\"**MASKED**\"" + } +} diff --git a/mdx-web/src/main/java/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilter.java b/mdx-web/src/main/java/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilter.java index 30e542bf..36ff3017 100644 --- a/mdx-web/src/main/java/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilter.java +++ b/mdx-web/src/main/java/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilter.java @@ -18,9 +18,9 @@ import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.mx.path.core.common.security.LogValueMasker; import com.mx.path.core.context.RequestContext; import com.mx.path.gateway.util.LoggingExceptionFormatter; +import com.mx.path.model.mdx.model.MdxLogMasker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,7 +43,6 @@ public class PathRequestLoggingFilter extends OncePerRequestFilter { // Statics private static final Gson GSON; - private static final LogValueMasker LOG_MASKER; private static Logger logger; public static void setLogger(Logger logger) { @@ -85,6 +84,8 @@ protected final void doFilterInternal(HttpServletRequest request, HttpServletRes } finally { resetMDC(); } + + responseWrapper.copyBodyToResponse(); } } @@ -130,7 +131,7 @@ private void logRequest(ContentCachingRequestWrapper request, ContentCachingResp } MDC.put("request_method", request.getMethod()); - MDC.put("request_uri", request.getRequestURI()); + MDC.put("request_uri", String.valueOf(request.getRequestURL())); if (request.getQueryString() != null) { final Map<String, String> queryParams = this.buildQueryStringMap(request.getQueryString()); @@ -151,7 +152,7 @@ private void logRequest(ContentCachingRequestWrapper request, ContentCachingResp final String requestBody = new String(request.getContentAsByteArray(), StandardCharsets.UTF_8); if (!requestBody.isEmpty()) { - MDC.put("request_body", LOG_MASKER.maskPayload(requestBody)); + MDC.put("request_body", MdxLogMasker.maskPayload(requestBody)); } else { MDC.remove("request_body"); } @@ -178,11 +179,10 @@ private void logRequest(ContentCachingRequestWrapper request, ContentCachingResp final String responseBody = new String(response.getContentAsByteArray(), StandardCharsets.UTF_8); if (!responseBody.isEmpty()) { - MDC.put("response_body", LOG_MASKER.maskPayload(responseBody)); + MDC.put("response_body", MdxLogMasker.maskPayload(responseBody)); } else { MDC.remove("response_body"); } - response.copyBodyToResponse(); logger.info("Incoming request"); } @@ -285,14 +285,13 @@ private Map<String, String> maskHeaders(Map<String, String> headers) { final Map<String, String> maskedHeaders = new HashMap<>(); headers.forEach((name, value) -> { - maskedHeaders.put(name, LOG_MASKER.maskHeaderValue(name, value)); + maskedHeaders.put(name, MdxLogMasker.maskHeaderValue(name, value)); }); return maskedHeaders; } static { GSON = new GsonBuilder().disableHtmlEscaping().setFieldNamingPolicy(FieldNamingPolicy.IDENTITY).create(); - LOG_MASKER = new LogValueMasker(); logger = LoggerFactory.getLogger(PathRequestLoggingFilter.class); } } diff --git a/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilterTest.groovy b/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilterTest.groovy index 0a197372..4721e722 100644 --- a/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilterTest.groovy +++ b/mdx-web/src/test/groovy/com/mx/path/model/mdx/web/filter/PathRequestLoggingFilterTest.groovy @@ -147,7 +147,7 @@ class PathRequestLoggingFilterTest extends Specification { def "captures expected data in MDC"() { given: def testNoMDCClearingSubject = new PathRequestLoggingFilterWithNoMDCClearing() - when(request.getRequestURI()).thenReturn("/testing") + when(request.getRequestURL()).thenReturn(new StringBuffer("https://localhost:13024/testing")) when(request.getQueryString()).thenReturn("param1=value1¶m2=value2") when(request.getMethod()).thenReturn("GET") @@ -189,7 +189,7 @@ class PathRequestLoggingFilterTest extends Specification { MDC.get("session_trace_id") == "ebebebe" MDC.get("device_trace_id") == "device123" MDC.get("request_method") == "GET" - MDC.get("request_uri") == "/testing" + MDC.get("request_uri") == "https://localhost:13024/testing" MDC.get("query_params") == "param1: value1\nparam2: value2\n" MDC.get("request_headers_json") == "{\"Accept\":\"application/vnd.mx.mdx.v6+json\",\"x-request-token\":\"**MASKED**\",\"mx-device-ip-address\":\"10.10.10.1\"}" MDC.get("request_headers") == "Accept: application/vnd.mx.mdx.v6+json\nx-request-token: **MASKED**\nmx-device-ip-address: 10.10.10.1\n"