credit_card_sanitizer 0.6.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/credit_card_sanitizer.rb +63 -45
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c39e3333f13f9c1130b5f1b6f6499b7eff6c0dca6c8541e481f8d41c4686a7e9
4
- data.tar.gz: 69fa1024561a947ffebbad419e8cfe2fdaba89f218f6e6a5dbeade420df77cbf
3
+ metadata.gz: ed9cddb911074ee952f3f65184cd75c50b81376f5c57b6aa9d198f0bc5859aff
4
+ data.tar.gz: 735236460e25ffcb4f458046a664a174bfdffc460c6eebbbec2c9a950ef06697
5
5
  SHA512:
6
- metadata.gz: db8e96b0272a415c3300c2311ba458c6335df036c41e5aa5e0bb8bb8d6157fceca29a90740918526521f73be133910402ce65c470c3c9a6a669ee3fd6550824b
7
- data.tar.gz: a2a5d648461566fc101087c12603e361fdc3b38a026c9a920ca1a0ed83819b61d57ab441b63abfa91ad4fbd733b46dd773c13832d231bc106507e41851602ab4
6
+ metadata.gz: 2536400933a1b3c5ee9a6df3070eb31f4b2447a780c335a3429055fb046df072a416c34813d9e4bd00238beaac3db626a285080b7f7063d1af45b87ef29656b4
7
+ data.tar.gz: d9f623e2b39d0ca88430bc15407d664c40569b8802dc85c10ca2c3795a339f3b129f95fe0d120e6623eeca1c28f02a9203159bd42c2446fab4000bf8cdd5b6a5
@@ -1,39 +1,48 @@
1
- # encoding: utf-8
2
-
3
- require 'luhn_checksum'
4
- require 'securerandom'
5
- require 'tracking_number'
1
+ require "luhn_checksum"
2
+ require "securerandom"
3
+ require "tracking_number"
6
4
 
7
5
  class CreditCardSanitizer
8
- # https://github.com/Shopify/active_merchant/blob/master/lib/active_merchant/billing/credit_card_methods.rb#L5-L18
9
6
  CARD_COMPANIES = {
10
- 'visa' => /^4\d{12}(\d{3})?(\d{3})?$/,
11
- '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}$/,
12
- 'discover' => /^((6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14}))$/,
13
- 'american_express' => /^3[47]\d{13}$/,
14
- 'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
15
- 'jcb' => /^35(28|29|[3-8]\d)\d{12}$/,
16
- 'switch' => /^6759\d{12}(\d{2,3})?$/,
17
- 'solo' => /^6767\d{12}(\d{2,3})?$/,
18
- 'dankort' => /^5019\d{12}$/,
19
- 'maestro' => /^(5[06-8]|6\d)\d{10,17}$/,
20
- 'forbrugsforeningen' => /^600722\d{10}$/,
21
- 'laser' => /^(6304|6706|6709|6771(?!89))\d{8}(\d{4}|\d{6,7})?$/
7
+ "visa" => /^4\d{12}(\d{3})?(\d{3})?$/,
8
+ "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}$/,
9
+ "discover" => /^((6011\d{12})|(65[4-9]\d{13})|(64[4-9]\d{13})|(622(?:12[6-9]|1[3-9]\d|[2-8]\d{2}|9[01]\d|92[0-5])\d{10}))$/,
10
+ "american_express" => /^3[47]\d{13}$/,
11
+ "diners_club" => /^3(0[0-5]|[68]\d)\d{11}$/,
12
+ "jcb" => /^35(28|29|[3-8]\d)\d{12}$/,
13
+ "switch" => /^(6759\d{12}(\d{2,3})?|(4903|4905|4911|4936|6333|6759)\d{12}|(4903|4905|4911|4936|6333|6759)\d{14}|(4903|4905|4911|4936|6333|6759)\d{15}|564182\d{10}|564182\d{12}|564182\d{13}|633110\d{10}|633110\d{12}|633110\d{13})$/,
14
+ "solo" => /^(6767\d{12}(\d{2,3})?|6334\d{12}|6334\d{14}|6334\d{15}|6767\d{14}|6767\d{15})$/,
15
+ "dankort" => /^5019\d{12}$/,
16
+ "maestro" => /^(5[06-8]\d{10,17}|6\d\d{10,17}|5018|5020|5038|5893|6304|6759|6761|6762|6763\d{8,15})$/,
17
+ "forbrugsforeningen" => /^600722\d{10}$/,
18
+ "laser" => /^(6304|6706|6709|6771(?!89))(\d{12,15}|\d{8}(\d{4}|\d{6,7})?)$/,
19
+ "bc_global" => /^(6541|6556)\d{12}$/,
20
+ "carte_blanche" => /^389\d{11}$/,
21
+ "insta_payment" => /^63[7-9]\d{13}$/,
22
+ "korean_local" => /^9\d{15}$/,
23
+ "union_pay" => /^62\d{14,17}$/,
24
+ "visa_master" => /^(4\d{12}(\d{3})?|5[1-5]\d{14})$/
22
25
  }.freeze
23
26
 
24
27
  CARD_NUMBER_GROUPINGS = {
25
- 'visa' => [[4, 4, 4, 4]],
26
- 'master' => [[4, 4, 4, 4]],
27
- 'discover' => [[4, 4, 4, 4]],
28
- 'american_express' => [[4, 6, 5]],
29
- 'diners_club' => [[4, 6, 4]],
30
- 'jcb' => [[4, 4, 4, 4]],
31
- 'switch' => [[4, 4, 4, 4]],
32
- 'solo' => [[4, 4, 4, 4]],
33
- 'dankort' => [[4, 4, 4, 4]],
34
- 'maestro' => [[4], [5]],
35
- 'forbrugsforeningen' => [[4, 4, 4, 4]],
36
- 'laser' => [[4, 4, 4, 4]]
28
+ "visa" => [[4, 4, 4, 4]],
29
+ "master" => [[4, 4, 4, 4]],
30
+ "discover" => [[4, 4, 4, 4]],
31
+ "american_express" => [[4, 6, 5]],
32
+ "diners_club" => [[4, 6, 4]],
33
+ "jcb" => [[4, 4, 4, 4]],
34
+ "switch" => [[4, 4, 4, 4]],
35
+ "solo" => [[4, 4, 4, 4], [4, 4, 4, 4, 2], [4, 4, 4, 4, 3]],
36
+ "dankort" => [[4, 4, 4, 4]],
37
+ "maestro" => [[4], [5], [4, 4, 4, 4], [4, 4, 4, 4, 1], [4, 4, 4, 4, 2], [4, 4, 4, 4, 3]],
38
+ "forbrugsforeningen" => [[4, 4, 4, 4]],
39
+ "laser" => [[4, 4, 4, 4], [4, 4, 4, 4, 1], [4, 4, 4, 4, 2], [4, 4, 4, 4, 3]],
40
+ "bc_global" => [[4, 4, 4, 4]],
41
+ "carte_blanche" => [[4, 6, 4]],
42
+ "insta_payment" => [[4, 4, 4, 4]],
43
+ "korean_local" => [[4, 4, 4, 4]],
44
+ "union_pay" => [[4, 4, 4, 4], [4, 4, 4, 4, 1], [4, 4, 4, 4, 2], [4, 4, 4, 4, 3]],
45
+ "visa_master" => [[4, 4, 4, 4], [4, 4, 4, 4, 3]]
37
46
  }.freeze
38
47
 
39
48
  ACCEPTED_PREFIX = /(?:cc|card|visa|amex)\z/i
@@ -44,11 +53,11 @@ class CreditCardSanitizer
44
53
  LINE_NOISE_CHAR = /[^\w\n,()&.\/:;<>]/
45
54
  LINE_NOISE = /#{LINE_NOISE_CHAR}{,5}/
46
55
  NONEMPTY_LINE_NOISE = /#{LINE_NOISE_CHAR}{1,5}/
47
- SCHEME_OR_PLUS = /((?:&#43;|\+|\/)|(?:[a-zA-Z][\-+.a-zA-Z\d]{,9}):[^\s>]+)/
56
+ SCHEME_OR_PLUS = /((?:&#43;|\+|\/)|(?:[a-zA-Z][-+.a-zA-Z\d]{,9}):[^\s>]+)/
48
57
  NUMBERS_WITH_LINE_NOISE = /#{SCHEME_OR_PLUS}?\d(?:#{LINE_NOISE}\d){10,30}/
49
58
 
50
59
  DEFAULT_OPTIONS = {
51
- replacement_token: '',
60
+ replacement_token: "",
52
61
  expose_first: 6,
53
62
  expose_last: 4,
54
63
  use_groupings: false,
@@ -88,31 +97,40 @@ class CreditCardSanitizer
88
97
  # sanitize!("I want all your credit card numbers!")
89
98
  # #=> nil
90
99
  #
91
- # Returns a String of the redacted text if a credit card number was detected.
92
- # Returns nil if no credit card numbers were detected.
100
+ # If options[:return_changes] is false, returns nil if no redaction happened,
101
+ # else the full text after redaction.
102
+ #
103
+ # If options[:return_changes] is true, returns nil if no redaction happened,
104
+ # else an array of [old_text, new_text] indicating what substrings were redacted.
93
105
  def sanitize!(text, options = {})
94
106
  options = @settings.merge(options)
95
107
 
96
108
  text.force_encoding(Encoding::UTF_8)
97
- text.scrub!('')
98
- redacted = nil
109
+ text.scrub!("")
110
+ changes = nil
99
111
 
100
112
  without_expiration(text) do
101
113
  text.gsub!(NUMBERS_WITH_LINE_NOISE) do |match|
102
114
  next match if $1
103
115
 
104
- candidate = Candidate.new(match, match.tr('^0-9', ''), $`, $')
116
+ candidate = Candidate.new(match, match.tr("^0-9", ""), $`, $')
105
117
 
106
118
  if valid_context?(candidate, options) && valid_numbers?(candidate, options)
107
- redacted = true
108
- redact_numbers(candidate, options)
119
+ redact_numbers(candidate, options).tap do |redacted_text|
120
+ changes ||= []
121
+ changes << [candidate.text, redacted_text]
122
+ end
109
123
  else
110
124
  match
111
125
  end
112
126
  end
113
127
  end
114
128
 
115
- redacted && text
129
+ if options[:return_changes]
130
+ changes
131
+ else
132
+ changes && text
133
+ end
116
134
  end
117
135
 
118
136
  # A proc that can be used
@@ -144,16 +162,16 @@ class CreditCardSanitizer
144
162
 
145
163
  def find_company(numbers)
146
164
  CARD_COMPANIES.each do |company, pattern|
147
- return company if numbers =~ pattern
165
+ return company if pattern.match?(numbers)
148
166
  end
149
167
  end
150
168
 
151
169
  def valid_grouping?(candidate, options)
152
170
  if options[:use_groupings]
153
- if company = find_company(candidate.numbers)
171
+ if (company = find_company(candidate.numbers))
154
172
  groupings = candidate.text.split(NONEMPTY_LINE_NOISE).map(&:length)
155
173
  return true if groupings.length == 1
156
- if company_groupings = CARD_NUMBER_GROUPINGS[company]
174
+ if (company_groupings = CARD_NUMBER_GROUPINGS[company])
157
175
  company_groupings.each do |company_grouping|
158
176
  return true if groupings.take(company_grouping.length) == company_grouping
159
177
  end
@@ -202,12 +220,12 @@ class CreditCardSanitizer
202
220
  end
203
221
 
204
222
  def without_expiration(text)
205
- expiration_date_boundary = SecureRandom.hex.tr('0123456789', 'ABCDEFGHIJ')
223
+ expiration_date_boundary = SecureRandom.hex.tr("0123456789", "ABCDEFGHIJ")
206
224
  text.gsub!(EXPIRATION_DATE) do |expiration_date|
207
225
  match = expiration_date.match(/(?<whitespace>\s*)(?<rest>.*)/m)
208
226
  "#{match[:whitespace]}#{expiration_date_boundary}#{match[:rest]}#{expiration_date_boundary}"
209
227
  end
210
228
  yield
211
- text.gsub!(expiration_date_boundary, '')
229
+ text.gsub!(expiration_date_boundary, "")
212
230
  end
213
231
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: credit_card_sanitizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.9
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Chapweske
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-06-02 00:00:00.000000000 Z
13
+ date: 2024-06-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: luhn_checksum
@@ -67,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  requirements: []
70
- rubygems_version: 3.1.6
70
+ rubygems_version: 3.5.11
71
71
  signing_key:
72
72
  specification_version: 4
73
73
  summary: Credit card sanitizer