diff --git a/lib/authorize_net/api/SensitiveDataFilter.rb b/lib/authorize_net/api/SensitiveDataFilter.rb index 07c2301..db1eb24 100644 --- a/lib/authorize_net/api/SensitiveDataFilter.rb +++ b/lib/authorize_net/api/SensitiveDataFilter.rb @@ -19,7 +19,11 @@ def initialize SensitiveTag.new("expirationDate", "", "XXX", false), SensitiveTag.new("accountNumber", "(\\p{N}+)(\\p{N}{4})", "XXXX-\\2", false), SensitiveTag.new("nameOnAccount", "", "XXX", false), - SensitiveTag.new("transactionKey", "", "XXX", false)]).freeze + SensitiveTag.new("transactionKey", "", "XXX", false), + SensitiveTag.new("accessToken", "", "XXX", false), + SensitiveTag.new("connectedAccessToken", "", "XXX", false), + SensitiveTag.new("sessionToken", "", "XXX", false), + SensitiveTag.new("token", "", "XXX", false)]).freeze @sensitiveStringRegexes = ["4\\p{N}{3}([\\ \\-]?)\\p{N}{4}\\1\\p{N}{4}\\1\\p{N}{4}", "4\\p{N}{3}([\\ \\-]?)(?:\\p{N}{4}\\1){2}\\p{N}(?:\\p{N}{3})?", "5[1-5]\\p{N}{2}([\\ \\-]?)\\p{N}{4}\\1\\p{N}{4}\\1\\p{N}{4}", @@ -33,26 +37,24 @@ class SensitiveDataFilter < Logger::Formatter @@sensitiveTagConfig = nil @@tagPatterns = nil @@tagReplacements = nil + @@tagValuePatterns = nil @@cardPatterns = nil def initialize @@sensitiveTagConfig = SensitiveDataConfigType.new @@cardPatterns = @@sensitiveTagConfig.sensitiveStringRegexes - @@tagPatterns = Array.new(@@sensitiveTagConfig.sensitiveStringRegexes.length) + @@tagPatterns = Array.new(@@sensitiveTagConfig.sensitiveTags.length) @@tagReplacements = Array.new(@@sensitiveTagConfig.sensitiveTags.length) + @@tagValuePatterns = Array.new(@@sensitiveTagConfig.sensitiveTags.length) @@sensitiveTagConfig.sensitiveTags.each_with_index do |sensitiveTag, index| tagName = sensitiveTag.tagName replacement = sensitiveTag.replacement + contentPattern = sensitiveTag.pattern - if sensitiveTag.pattern.nil? || sensitiveTag.pattern.empty? - pattern = "(.*)" - else - pattern = sensitiveTag.pattern - end - - @@tagPatterns[index] = "<"+tagName+">"+pattern+"" - @@tagReplacements[index] = "<"+tagName+">"+replacement+"" + @@tagPatterns[index] = Regexp.new("(<(?:\\w+:)?#{Regexp.escape(tagName)}\\b[^>]*>)(.*?)()", Regexp::IGNORECASE | Regexp::MULTILINE) + @@tagReplacements[index] = replacement + @@tagValuePatterns[index] = contentPattern end end @@ -72,8 +74,24 @@ def maskCreditCards(input) def maskSensitiveXmlString(input) input = input.force_encoding("UTF-8") - @@tagPatterns.each_with_index do |item, index| - input = input.gsub(/#{item}/,@@tagReplacements[index]) + # Structurally mask the whole merchantAuthentication element to avoid leaking credentials by tag miss. + input = input.gsub(/(<(?:\w+:)?merchantAuthentication\b[^>]*>)(.*?)(<\/(?:\w+:)?merchantAuthentication\s*>)/im, "\\1XXX\\3") + + @@tagPatterns.each_with_index do |item, index| + replacement = @@tagReplacements[index] + valuePattern = @@tagValuePatterns[index] + input = input.gsub(item) do + openTag = $1 + value = $2 + closeTag = $3 + + if valuePattern.nil? || valuePattern.empty? + "#{openTag}#{replacement}#{closeTag}" + else + maskedValue = value.gsub(/#{valuePattern}/, replacement) + "#{openTag}#{maskedValue}#{closeTag}" + end + end end return input end diff --git a/spec/api_spec.rb b/spec/api_spec.rb index 0306aa4..8047eba 100644 --- a/spec/api_spec.rb +++ b/spec/api_spec.rb @@ -559,6 +559,45 @@ expect(response.transactions).not_to eq nil end + it "should mask sensitive token elements in request XML" do + filter = SensitiveDataFilter.new + xml = "oauth-access-tokenrequest-connected-tokenrequest-session-tokenrequest-hosted-page-tokenmerchant-loginrequest-extra-access-tokenrequest-extra-session-tokenrequest-extra-tokenrequest-extra-connected-token" + + masked = filter.maskSensitiveXmlString(xml) + + expect(masked).to include("XXX") + expect(masked).to include("XXX") + expect(masked).to include("XXX") + expect(masked).to include("XXX") + expect(masked).to include("XXX") + expect(masked).not_to include("oauth-access-token") + expect(masked).not_to include("request-connected-token") + expect(masked).not_to include("request-session-token") + expect(masked).not_to include("request-hosted-page-token") + expect(masked).not_to include("request-extra-access-token") + expect(masked).not_to include("request-extra-session-token") + expect(masked).not_to include("request-extra-token") + expect(masked).not_to include("request-extra-connected-token") + end + + it "should mask sensitive token elements in response XML" do + filter = SensitiveDataFilter.new + xml = "response-access-tokenresponse-connected-tokenresponse-session-tokenresponse-hosted-page-tokenresponse-hosted-page-token-ns" + + masked = filter.maskSensitiveXmlString(xml) + + expect(masked).to include("XXX") + expect(masked).to include("XXX") + expect(masked).to include("XXX") + expect(masked).to include("XXX") + expect(masked).to include("XXX") + expect(masked).not_to include("response-access-token") + expect(masked).not_to include("response-connected-token") + expect(masked).not_to include("response-session-token") + expect(masked).not_to include("response-hosted-page-token") + expect(masked).not_to include("response-hosted-page-token-ns") + end + def get_actual(expected, className, topElement) xmlText = @transaction.serialize(expected, topElement) className.from_xml(xmlText)