Class: CreditCardSanitizer

Inherits:
Object
  • Object
show all
Defined in:
lib/credit_card_sanitizer.rb

Defined Under Namespace

Classes: Candidate

Constant Summary collapse

CARD_COMPANIES =
{
  'visa'               => /^4\d{12}(\d{3})?(\d{3})?$/,
  'master'             => /^(5[1-5]\d{4}|677189|222[1-9]\d{2}|22[3-9]\d{3}|2[3-6]\d{4}|27[01]\d{3}|2720\d{2})\d{10}$/,
  'discover'           => /^((6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14}))$/,
  'american_express'   => /^3[47]\d{13}$/,
  'diners_club'        => /^3(0[0-5]|[68]\d)\d{11}$/,
  'jcb'                => /^35(28|29|[3-8]\d)\d{12}$/,
  'switch'             => /^6759\d{12}(\d{2,3})?$/,
  'solo'               => /^6767\d{12}(\d{2,3})?$/,
  'dankort'            => /^5019\d{12}$/,
  'maestro'            => /^(5[06-8]|6\d)\d{10,17}$/,
  'forbrugsforeningen' => /^600722\d{10}$/,
  'laser'              => /^(6304|6706|6709|6771(?!89))\d{8}(\d{4}|\d{6,7})?$/
}.freeze
CARD_NUMBER_GROUPINGS =
{
  'visa'               => [[4, 4, 4, 4]],
  'master'             => [[4, 4, 4, 4]],
  'discover'           => [[4, 4, 4, 4]],
  'american_express'   => [[4, 6, 5]],
  'diners_club'        => [[4, 6, 4]],
  'jcb'                => [[4, 4, 4, 4]],
  'switch'             => [[4, 4, 4, 4]],
  'solo'               => [[4, 4, 4, 4]],
  'dankort'            => [[4, 4, 4, 4]],
  'maestro'            => [[4], [5]],
  'forbrugsforeningen' => [[4, 4, 4, 4]],
  'laser'              => [[4, 4, 4, 4]]
}.freeze
ACCEPTED_PREFIX =
/(?:cc|card|visa|amex)\z/i
ACCEPTED_POSTFIX =
/\Aex/i
ALPHANUMERIC =
/[[:alnum:]]/i
VALID_COMPANY_PREFIXES =
Regexp.union(*CARD_COMPANIES.values)
EXPIRATION_DATE =
/\s(?:0?[1-9]|1[0-2])(?:\/|-)(?:\d{4}|\d{2})(?:\D|$)/
LINE_NOISE_CHAR =
/[^\w\n,()&.\/:;<>]/
LINE_NOISE =
/#{LINE_NOISE_CHAR}{,5}/
NONEMPTY_LINE_NOISE =
/#{LINE_NOISE_CHAR}{1,5}/
SCHEME_OR_PLUS =
/((?:&#43;|\+)|(?:[a-zA-Z][\-+.a-zA-Z\d]{,9}):[^\s>]+)/
NUMBERS_WITH_LINE_NOISE =
/#{SCHEME_OR_PLUS}?\d(?:#{LINE_NOISE}\d){10,30}/
DEFAULT_OPTIONS =
{
  replacement_token: '▇',
  expose_first: 6,
  expose_last: 4,
  use_groupings: false,
  exclude_tracking_numbers: false,
  parse_flanking: false
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ CreditCardSanitizer

Create a new CreditCardSanitizer

Options

:replacement_character - the character that will replace digits for redaction. :expose_first - the number of leading digits that will not be redacted. :expose_last - the number of ending digits that will not be redacted. :use_groupings - require card number groupings to match to redact. :exclude_tracking_numbers - do not redact valid shipping company tracking numbers.



73
74
75
# File 'lib/credit_card_sanitizer.rb', line 73

def initialize(options = {})
  @settings = DEFAULT_OPTIONS.merge(options)
end

Instance Attribute Details

#settingsObject (readonly)

Returns the value of attribute settings.



59
60
61
# File 'lib/credit_card_sanitizer.rb', line 59

def settings
  @settings
end

Class Method Details

.parameter_filter(options = {}) ⇒ Object

A proc that can be used

text - the text containing potential credit card numbers

Examples

Rails.app.config.filter_parameters = [:password, CreditCardSanitizer.parameter_filter]

env = {
  "action_dispatch.request.parameters" => {"credit_card_number" => "4111 1111 1111 1111", "password" => "123"},
  "action_dispatch.parameter_filter" => Rails.app.config.filter_parameters
}

>> ActionDispatch::Request.new(env).filtered_parameters
=> {"credit_card_number" => "4111 11▇▇ ▇▇▇▇ 1111", "password" => "[FILTERED]"}

Returns a Proc that takes the key/value of the request parameter.



135
136
137
# File 'lib/credit_card_sanitizer.rb', line 135

def self.parameter_filter(options = {})
  proc { |_, value| new(options).sanitize!(value) if value.is_a?(String) }
end

Instance Method Details

#sanitize!(text, options = {}) ⇒ Object

Finds credit card numbers and redacts digits from them

text - the text containing potential credit card numbers

Examples

# If the text contains a credit card number:
sanitize!("4111 1111 1111 1111")
#=> "4111 11▇▇ ▇▇▇▇ 1111"

# If the text does not contain a credit card number:
sanitize!("I want all your credit card numbers!")
#=> nil

Returns a String of the redacted text if a credit card number was detected. Returns nil if no credit card numbers were detected.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/credit_card_sanitizer.rb', line 93

def sanitize!(text, options = {})
  options = @settings.merge(options)

  text.force_encoding(Encoding::UTF_8)
  text.scrub!('�')
  redacted = nil

  without_expiration(text) do
    text.gsub!(NUMBERS_WITH_LINE_NOISE) do |match|
      next match if $1

      candidate = Candidate.new(match, match.tr('^0-9', ''), $`, $')

      if valid_context?(candidate, options) && valid_numbers?(candidate, options)
        redacted = true
        redact_numbers(candidate, options)
      else
        match
      end
    end
  end

  redacted && text
end