Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 30 additions & 12 deletions lib/authorize_net/api/SensitiveDataFilter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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}",
Expand All @@ -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+"</"+tagName+">"
@@tagReplacements[index] = "<"+tagName+">"+replacement+"</"+tagName+">"
@@tagPatterns[index] = Regexp.new("(<(?:\\w+:)?#{Regexp.escape(tagName)}\\b[^>]*>)(.*?)(</(?:\\w+:)?#{Regexp.escape(tagName)}\\s*>)", Regexp::IGNORECASE | Regexp::MULTILINE)
@@tagReplacements[index] = replacement
@@tagValuePatterns[index] = contentPattern
end
end

Expand All @@ -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
Expand Down
39 changes: 39 additions & 0 deletions spec/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<merchantAuthentication xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\"><accessToken>oauth-access-token</accessToken><connectedAccessToken>request-connected-token</connectedAccessToken><sessionToken>request-session-token</sessionToken><token>request-hosted-page-token</token><name>merchant-login</name></merchantAuthentication><ns:accessToken xmlns:ns=\"urn:test\" attr=\"1\">request-extra-access-token</ns:accessToken><sessionToken type=\"bearer\">request-extra-session-token</sessionToken><token source=\"hosted\">request-extra-token</token><connectedAccessToken source=\"oauth\">request-extra-connected-token</connectedAccessToken>"

masked = filter.maskSensitiveXmlString(xml)

expect(masked).to include("<merchantAuthentication xmlns=\"AnetApi/xml/v1/schema/AnetApiSchema.xsd\">XXX</merchantAuthentication>")
expect(masked).to include("<ns:accessToken xmlns:ns=\"urn:test\" attr=\"1\">XXX</ns:accessToken>")
expect(masked).to include("<sessionToken type=\"bearer\">XXX</sessionToken>")
expect(masked).to include("<token source=\"hosted\">XXX</token>")
expect(masked).to include("<connectedAccessToken source=\"oauth\">XXX</connectedAccessToken>")
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 = "<getHostedPaymentPageResponse><accessToken>response-access-token</accessToken><connectedAccessToken>response-connected-token</connectedAccessToken><sessionToken>response-session-token</sessionToken><token>response-hosted-page-token</token><ns:token xmlns:ns=\"urn:test\" attr=\"2\">response-hosted-page-token-ns</ns:token></getHostedPaymentPageResponse>"

masked = filter.maskSensitiveXmlString(xml)

expect(masked).to include("<accessToken>XXX</accessToken>")
expect(masked).to include("<connectedAccessToken>XXX</connectedAccessToken>")
expect(masked).to include("<token>XXX</token>")
expect(masked).to include("<sessionToken>XXX</sessionToken>")
expect(masked).to include("<ns:token xmlns:ns=\"urn:test\" attr=\"2\">XXX</ns:token>")
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)
Expand Down