mini_defender 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +70 -0
- data/LICENSE.md +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +33 -0
- data/RULES.md +92 -0
- data/Rakefile +12 -0
- data/lib/mini_defender/extensions/enumerable.rb +34 -0
- data/lib/mini_defender/extensions/hash.rb +28 -0
- data/lib/mini_defender/rule.rb +77 -0
- data/lib/mini_defender/rules/accepted.rb +17 -0
- data/lib/mini_defender/rules/accepted_if.rb +26 -0
- data/lib/mini_defender/rules/alpha.rb +15 -0
- data/lib/mini_defender/rules/alpha_dash.rb +15 -0
- data/lib/mini_defender/rules/alpha_num.rb +15 -0
- data/lib/mini_defender/rules/array.rb +15 -0
- data/lib/mini_defender/rules/bail.rb +19 -0
- data/lib/mini_defender/rules/between.rb +29 -0
- data/lib/mini_defender/rules/boolean.rb +26 -0
- data/lib/mini_defender/rules/confirmed.rb +24 -0
- data/lib/mini_defender/rules/country_code.rb +23 -0
- data/lib/mini_defender/rules/credit_card.rb +13 -0
- data/lib/mini_defender/rules/date.rb +27 -0
- data/lib/mini_defender/rules/date_eq.rb +43 -0
- data/lib/mini_defender/rules/date_format.rb +38 -0
- data/lib/mini_defender/rules/date_gt.rb +24 -0
- data/lib/mini_defender/rules/date_gte.rb +24 -0
- data/lib/mini_defender/rules/date_lt.rb +24 -0
- data/lib/mini_defender/rules/date_lte.rb +24 -0
- data/lib/mini_defender/rules/declined.rb +17 -0
- data/lib/mini_defender/rules/declined_if.rb +26 -0
- data/lib/mini_defender/rules/default.rb +33 -0
- data/lib/mini_defender/rules/different.rb +31 -0
- data/lib/mini_defender/rules/digits.rb +26 -0
- data/lib/mini_defender/rules/digits_between.rb +29 -0
- data/lib/mini_defender/rules/distinct.rb +18 -0
- data/lib/mini_defender/rules/email.rb +19 -0
- data/lib/mini_defender/rules/ending_with.rb +31 -0
- data/lib/mini_defender/rules/equal.rb +9 -0
- data/lib/mini_defender/rules/excluded.rb +15 -0
- data/lib/mini_defender/rules/excluded_if.rb +28 -0
- data/lib/mini_defender/rules/excluded_unless.rb +28 -0
- data/lib/mini_defender/rules/excluded_with.rb +27 -0
- data/lib/mini_defender/rules/excluded_without.rb +27 -0
- data/lib/mini_defender/rules/exists.rb +29 -0
- data/lib/mini_defender/rules/expiry_date.rb +28 -0
- data/lib/mini_defender/rules/file.rb +17 -0
- data/lib/mini_defender/rules/filled.rb +22 -0
- data/lib/mini_defender/rules/greater_than.rb +34 -0
- data/lib/mini_defender/rules/greater_than_or_equal.rb +34 -0
- data/lib/mini_defender/rules/hash.rb +15 -0
- data/lib/mini_defender/rules/image.rb +19 -0
- data/lib/mini_defender/rules/in.rb +27 -0
- data/lib/mini_defender/rules/in_field.rb +40 -0
- data/lib/mini_defender/rules/integer.rb +19 -0
- data/lib/mini_defender/rules/ip.rb +43 -0
- data/lib/mini_defender/rules/ipv4.rb +43 -0
- data/lib/mini_defender/rules/ipv6.rb +43 -0
- data/lib/mini_defender/rules/json.rb +19 -0
- data/lib/mini_defender/rules/less_than.rb +34 -0
- data/lib/mini_defender/rules/less_than_or_equal.rb +34 -0
- data/lib/mini_defender/rules/luhn.rb +33 -0
- data/lib/mini_defender/rules/mac_address.rb +39 -0
- data/lib/mini_defender/rules/max.rb +9 -0
- data/lib/mini_defender/rules/max_digits.rb +32 -0
- data/lib/mini_defender/rules/mime_types.rb +37 -0
- data/lib/mini_defender/rules/min.rb +9 -0
- data/lib/mini_defender/rules/min_digits.rb +32 -0
- data/lib/mini_defender/rules/national_id.rb +13 -0
- data/lib/mini_defender/rules/not_ending_with.rb +31 -0
- data/lib/mini_defender/rules/not_in.rb +27 -0
- data/lib/mini_defender/rules/not_regex.rb +27 -0
- data/lib/mini_defender/rules/not_starting_with.rb +31 -0
- data/lib/mini_defender/rules/numeric.rb +19 -0
- data/lib/mini_defender/rules/present.rb +19 -0
- data/lib/mini_defender/rules/prohibited.rb +15 -0
- data/lib/mini_defender/rules/prohibited_if.rb +32 -0
- data/lib/mini_defender/rules/prohibited_unless.rb +32 -0
- data/lib/mini_defender/rules/regex.rb +27 -0
- data/lib/mini_defender/rules/required.rb +32 -0
- data/lib/mini_defender/rules/required_if.rb +26 -0
- data/lib/mini_defender/rules/required_unless.rb +26 -0
- data/lib/mini_defender/rules/required_with.rb +27 -0
- data/lib/mini_defender/rules/required_with_all.rb +27 -0
- data/lib/mini_defender/rules/required_without.rb +27 -0
- data/lib/mini_defender/rules/required_without_all.rb +27 -0
- data/lib/mini_defender/rules/size.rb +45 -0
- data/lib/mini_defender/rules/starting_with.rb +31 -0
- data/lib/mini_defender/rules/string.rb +15 -0
- data/lib/mini_defender/rules/timezone.rb +19 -0
- data/lib/mini_defender/rules/unique.rb +37 -0
- data/lib/mini_defender/rules/url.rb +17 -0
- data/lib/mini_defender/rules/uuid.rb +37 -0
- data/lib/mini_defender/rules.rb +8 -0
- data/lib/mini_defender/rules_expander.rb +19 -0
- data/lib/mini_defender/rules_factory.rb +36 -0
- data/lib/mini_defender/validates_input.rb +12 -0
- data/lib/mini_defender/validation_error.rb +10 -0
- data/lib/mini_defender/validator.rb +127 -0
- data/lib/mini_defender/version.rb +5 -0
- data/lib/mini_defender.rb +24 -0
- data/mini_defender.gemspec +38 -0
- data/sig/mini_defender.rbs +4 -0
- metadata +206 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::Filled < MiniDefender::Rule
|
4
|
+
def self.signature
|
5
|
+
'filled'
|
6
|
+
end
|
7
|
+
|
8
|
+
def passes?(attribute, value, validator)
|
9
|
+
case value
|
10
|
+
when String
|
11
|
+
!value.strip.empty?
|
12
|
+
when Array, Hash
|
13
|
+
!value.empty?
|
14
|
+
else
|
15
|
+
!value.nil?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def message(attribute, value, validator)
|
20
|
+
'The field should not be empty.'
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_dispatch'
|
4
|
+
require_relative 'size'
|
5
|
+
|
6
|
+
class MiniDefender::Rules::GreaterThan < MiniDefender::Rules::Size
|
7
|
+
def self.signature
|
8
|
+
'gt'
|
9
|
+
end
|
10
|
+
|
11
|
+
def passes?(attribute, value, validator)
|
12
|
+
case value
|
13
|
+
when String, Array, Hash
|
14
|
+
value.length > @size
|
15
|
+
when ActionDispatch::Http::UploadedFile
|
16
|
+
value.size > @size
|
17
|
+
when Numeric
|
18
|
+
value > @size
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def message(attribute, value, validator)
|
25
|
+
case value
|
26
|
+
when ActionDispatch::Http::UploadedFile
|
27
|
+
"The file size must be greater than #{@size} bytes."
|
28
|
+
when Numeric
|
29
|
+
"The value must be greater than #{@size}."
|
30
|
+
else
|
31
|
+
"The value length must be greater than #{@size}."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_dispatch'
|
4
|
+
require_relative 'size'
|
5
|
+
|
6
|
+
class MiniDefender::Rules::GreaterThanOrEqual < MiniDefender::Rules::Size
|
7
|
+
def self.signature
|
8
|
+
'gte'
|
9
|
+
end
|
10
|
+
|
11
|
+
def passes?(attribute, value, validator)
|
12
|
+
case value
|
13
|
+
when String, Array, Hash
|
14
|
+
value.length >= @size
|
15
|
+
when ActionDispatch::Http::UploadedFile
|
16
|
+
value.size >= @size
|
17
|
+
when Numeric
|
18
|
+
value >= @size
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def message(attribute, value, validator)
|
25
|
+
case value
|
26
|
+
when ActionDispatch::Http::UploadedFile
|
27
|
+
"The file size must be greater than or equal to #{@size} bytes."
|
28
|
+
when Numeric
|
29
|
+
"The value must be greater than or equal to #{@size}."
|
30
|
+
else
|
31
|
+
"The value length must be greater than or equal to #{@size}."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::Hash < MiniDefender::Rule
|
4
|
+
def self.signature
|
5
|
+
'hash'
|
6
|
+
end
|
7
|
+
|
8
|
+
def passes?(attribute, value, validator)
|
9
|
+
value.is_a?(Hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
def message(attribute, value, validator)
|
13
|
+
"The field must be an object."
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_dispatch'
|
4
|
+
|
5
|
+
class MiniDefender::Rules::Image < MiniDefender::Rule
|
6
|
+
MIMES = %w[image/jpeg image/png image/gif image/bmp image/png image/svg+xml image/webp]
|
7
|
+
|
8
|
+
def self.signature
|
9
|
+
'image'
|
10
|
+
end
|
11
|
+
|
12
|
+
def passes?(attribute, value, validator)
|
13
|
+
value.is_a?(ActionDispatch::Http::UploadedFile) && MIMES.include?(value.content_type)
|
14
|
+
end
|
15
|
+
|
16
|
+
def message(attribute, value, validator)
|
17
|
+
"The field should be an image of type jpg, jpeg, png, bmp, gif, svg, or webp."
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::In < MiniDefender::Rule
|
4
|
+
def initialize(values)
|
5
|
+
raise ArgumentError, 'Expected an array of values.' unless values.is_a?(Array)
|
6
|
+
|
7
|
+
@values = values
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.signature
|
11
|
+
'in'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.make(args)
|
15
|
+
raise ArgumentError, 'Expected at least one argument.' unless args.length > 0
|
16
|
+
|
17
|
+
new(args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def passes?(attribute, value, validator)
|
21
|
+
@values.include?(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def message(attribute, value, validator)
|
25
|
+
"The value must be one of #{@values.to_sentence}."
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::InField < MiniDefender::Rule
|
4
|
+
def initialize(field)
|
5
|
+
raise ArgumentError, 'Field must be a string.' unless field.is_a?(String)
|
6
|
+
|
7
|
+
@field = field
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.signature
|
11
|
+
'in_field'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.make(args)
|
15
|
+
raise ArgumentError, 'Expected one argument, target field name.' unless args.length == 1
|
16
|
+
|
17
|
+
new(args[0])
|
18
|
+
end
|
19
|
+
|
20
|
+
def passes?(attribute, value, validator)
|
21
|
+
@field_value = nil
|
22
|
+
return false unless validator.data.key(@field)
|
23
|
+
|
24
|
+
@field_value = validator.data[@field]
|
25
|
+
return false unless field.is_a?(Array)
|
26
|
+
|
27
|
+
@field_value.include?(value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def message(attribute, value, validator)
|
31
|
+
case @field_value
|
32
|
+
when nil
|
33
|
+
"The field (#{@field}) is missing."
|
34
|
+
when Array
|
35
|
+
"The field (#{@field}) must be an array."
|
36
|
+
else
|
37
|
+
"The value must be one of the values found in #{@field}."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::Integer < MiniDefender::Rule
|
4
|
+
def self.signature
|
5
|
+
'integer'
|
6
|
+
end
|
7
|
+
|
8
|
+
def coerce(value)
|
9
|
+
value.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def passes?(attribute, value, validator)
|
13
|
+
value.is_a?(Integer) || value.is_a?(String) && value.match?(/^\d+$/)
|
14
|
+
end
|
15
|
+
|
16
|
+
def message(attribute, value, validator)
|
17
|
+
"The value must be an integer."
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
class MiniDefender::Rules::Ip < MiniDefender::Rule
|
6
|
+
MODES = %w[any public private]
|
7
|
+
|
8
|
+
def initialize(mode = 'any')
|
9
|
+
raise ArgumentError, 'Invalid mode' unless MODES.include?(mode)
|
10
|
+
|
11
|
+
@mode = mode
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.signature
|
15
|
+
'ip'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.make(args)
|
19
|
+
new(args[0] || 'any')
|
20
|
+
end
|
21
|
+
|
22
|
+
def passes?(attribute, value, validator)
|
23
|
+
ip = IPAddr.new(value.to_s)
|
24
|
+
(
|
25
|
+
@mode == 'any' ||
|
26
|
+
@mode == 'private' && (ip.private? || ip.link_local? || ip.loopback?) ||
|
27
|
+
@mode == 'public' && !(ip.private? || ip.link_local? || ip.loopback?)
|
28
|
+
)
|
29
|
+
rescue IPAddr::InvalidAddressError
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def message(attribute, value, validator)
|
34
|
+
case @mode
|
35
|
+
when 'public'
|
36
|
+
"The value must be a valid public IP address."
|
37
|
+
when 'private'
|
38
|
+
"The value must be a valid private IP address."
|
39
|
+
else
|
40
|
+
"The value must be a valid IP address."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
class MiniDefender::Rules::Ipv4 < MiniDefender::Rule
|
6
|
+
MODES = %w[any public private]
|
7
|
+
|
8
|
+
def initialize(mode = 'any')
|
9
|
+
raise ArgumentError, 'Invalid mode' unless MODES.include?(mode)
|
10
|
+
|
11
|
+
@mode = mode
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.signature
|
15
|
+
'ipv4'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.make(args)
|
19
|
+
new(args[0] || 'any')
|
20
|
+
end
|
21
|
+
|
22
|
+
def passes?(attribute, value, validator)
|
23
|
+
ip = IPAddr.new(value.to_s)
|
24
|
+
ip.ipv4? && (
|
25
|
+
@mode == 'any' ||
|
26
|
+
@mode == 'private' && (ip.private? || ip.link_local? || ip.loopback?) ||
|
27
|
+
@mode == 'public' && !(ip.private? || ip.link_local? || ip.loopback?)
|
28
|
+
)
|
29
|
+
rescue IPAddr::InvalidAddressError
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def message(attribute, value, validator)
|
34
|
+
case @mode
|
35
|
+
when 'public'
|
36
|
+
"The value must be a valid public IPv4 address."
|
37
|
+
when 'private'
|
38
|
+
"The value must be a valid private IPv4 address."
|
39
|
+
else
|
40
|
+
"The value must be a valid IPv4 address."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
class MiniDefender::Rules::Ipv6 < MiniDefender::Rule
|
6
|
+
MODES = %w[any public private]
|
7
|
+
|
8
|
+
def initialize(mode = 'any')
|
9
|
+
raise ArgumentError, 'Invalid mode' unless MODES.include?(mode)
|
10
|
+
|
11
|
+
@mode = mode
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.signature
|
15
|
+
'ipv6'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.make(args)
|
19
|
+
new(args[0] || 'any')
|
20
|
+
end
|
21
|
+
|
22
|
+
def passes?(attribute, value, validator)
|
23
|
+
ip = IPAddr.new(value.to_s)
|
24
|
+
ip.ipv6? && (
|
25
|
+
@mode == 'any' ||
|
26
|
+
@mode == 'private' && (ip.private? || ip.link_local? || ip.loopback?) ||
|
27
|
+
@mode == 'public' && !(ip.private? || ip.link_local? || ip.loopback?)
|
28
|
+
)
|
29
|
+
rescue IPAddr::InvalidAddressError
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def message(attribute, value, validator)
|
34
|
+
case @mode
|
35
|
+
when 'public'
|
36
|
+
"The value must be a valid public IPv6 address."
|
37
|
+
when 'private'
|
38
|
+
"The value must be a valid private IPv6 address."
|
39
|
+
else
|
40
|
+
"The value must be a valid IPv6 address."
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class MiniDefender::Rules::Json < MiniDefender::Rule
|
6
|
+
def self.signature
|
7
|
+
'json'
|
8
|
+
end
|
9
|
+
|
10
|
+
def passes?(attribute, value, validator)
|
11
|
+
JSON.parse(value)
|
12
|
+
rescue JSON::ParserError
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
def message(attribute, value, validator)
|
17
|
+
"The value should be a valid JSON string."
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_dispatch'
|
4
|
+
require_relative 'size'
|
5
|
+
|
6
|
+
class MiniDefender::Rules::LessThan < MiniDefender::Rules::Size
|
7
|
+
def self.signature
|
8
|
+
'lt'
|
9
|
+
end
|
10
|
+
|
11
|
+
def passes?(attribute, value, validator)
|
12
|
+
case value
|
13
|
+
when String, Array, Hash
|
14
|
+
value.length < @size
|
15
|
+
when ActionDispatch::Http::UploadedFile
|
16
|
+
value.size < @size
|
17
|
+
when Numeric
|
18
|
+
value < @size
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def message(attribute, value, validator)
|
25
|
+
case value
|
26
|
+
when ActionDispatch::Http::UploadedFile
|
27
|
+
"The file size must be less than #{@size} bytes."
|
28
|
+
when Numeric
|
29
|
+
"The value must be less than #{@size}."
|
30
|
+
else
|
31
|
+
"The value length must be less than #{@size}."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_dispatch'
|
4
|
+
require_relative 'size'
|
5
|
+
|
6
|
+
class MiniDefender::Rules::LessThanOrEqual < MiniDefender::Rules::Size
|
7
|
+
def self.signature
|
8
|
+
'lte'
|
9
|
+
end
|
10
|
+
|
11
|
+
def passes?(attribute, value, validator)
|
12
|
+
case value
|
13
|
+
when String, Array, Hash
|
14
|
+
value.length <= @size
|
15
|
+
when ActionDispatch::Http::UploadedFile
|
16
|
+
value.size <= @size
|
17
|
+
when Numeric
|
18
|
+
value <= @size
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def message(attribute, value, validator)
|
25
|
+
case value
|
26
|
+
when ActionDispatch::Http::UploadedFile
|
27
|
+
"The file size must be less than or equal to #{@size} bytes."
|
28
|
+
when Numeric
|
29
|
+
"The value must be less than or equal to #{@size}."
|
30
|
+
else
|
31
|
+
"The value length must be less than or equal to #{@size}."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::Luhn < MiniDefender::Rule
|
4
|
+
def self.signature
|
5
|
+
'luhn'
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.valid_luhn?(value)
|
9
|
+
value = value.to_s
|
10
|
+
return false unless value.match?(/\A\d+\z/)
|
11
|
+
|
12
|
+
double_sum = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
|
13
|
+
sum = 0
|
14
|
+
|
15
|
+
value.to_i.digits.each_with_index do |digit, index|
|
16
|
+
sum += index.even? ? digit : double_sum[digit]
|
17
|
+
end
|
18
|
+
|
19
|
+
sum % 10 === 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def coerce(value)
|
23
|
+
value.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def passes?(attribute, value, validator)
|
27
|
+
value.is_a?(String) && self.class.valid_luhn?(value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def message(attribute, value, validator)
|
31
|
+
'The value must be a valid Luhn string.'
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::MacAddress < MiniDefender::Rule
|
4
|
+
MODES = %w[any local universal]
|
5
|
+
|
6
|
+
def initialize(mode = 'any')
|
7
|
+
raise ArgumentError, 'Invalid mode' unless MODES.include?(mode)
|
8
|
+
|
9
|
+
@mode = mode
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.signature
|
13
|
+
'mac'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.make(args)
|
17
|
+
new(args[0] || 'any')
|
18
|
+
end
|
19
|
+
|
20
|
+
def passes?(attribute, value, validator)
|
21
|
+
clean = value.to_s.gsub(/[-:]/)
|
22
|
+
clean.match?(/[0-9A-F]{12}/i) && (
|
23
|
+
@mode == 'any' ||
|
24
|
+
@mode == 'local' && (clean.hex & 0x020000000000 > 0) ||
|
25
|
+
@mode == 'universal' && (clean.hex & 0x020000000000 == 0)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def message(attribute, value, validator)
|
30
|
+
case @mode
|
31
|
+
when 'local'
|
32
|
+
"The value must be a locally administrated MAC address."
|
33
|
+
when 'universal'
|
34
|
+
"The value must be a universally administrated MAC address."
|
35
|
+
else
|
36
|
+
"The value must be a properly formatted mac address."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::MaxDigits < MiniDefender::Rule
|
4
|
+
def initialize(limit)
|
5
|
+
raise ArgumentError, 'Limit must be a string.' unless limit.is_a?(Integer)
|
6
|
+
|
7
|
+
@limit = limit
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.signature
|
11
|
+
'max_digits'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.make(args)
|
15
|
+
raise ArgumentError, 'Expected at least one argument for max_digits.' unless args.length == 1
|
16
|
+
|
17
|
+
new(args[0].to_i)
|
18
|
+
end
|
19
|
+
|
20
|
+
def passes?(attribute, value, validator)
|
21
|
+
@integers = valid = value.is_a?(Integer) || value.is_a?(String) && value.match?(/^\d+$/)
|
22
|
+
valid && value.to_s.length <= @limit
|
23
|
+
end
|
24
|
+
|
25
|
+
def message(attribute, value, validator)
|
26
|
+
if @integers
|
27
|
+
'The field should only contain digits.'
|
28
|
+
else
|
29
|
+
"The field should have at most #{@limit} digits."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_dispatch'
|
4
|
+
|
5
|
+
class MiniDefender::Rules::MimeTypes < MiniDefender::Rule
|
6
|
+
def initialize(types)
|
7
|
+
unless types.is_a?(Array) && types.all?{ |t| t.is_a?(String) }
|
8
|
+
raise ArgumentError, 'Expected an array of strings.'
|
9
|
+
end
|
10
|
+
|
11
|
+
@types = types
|
12
|
+
@file = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.signature
|
16
|
+
'mime'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.make(args)
|
20
|
+
raise ArgumentError, 'Expected at least one MIME type.' unless args.length > 0
|
21
|
+
|
22
|
+
new(args.split(',').map(&:downcase).map(&:strip))
|
23
|
+
end
|
24
|
+
|
25
|
+
def passes?(attribute, value, validator)
|
26
|
+
@file = value.is_a?(ActionDispatch::Http::UploadedFile)
|
27
|
+
@file && @types.include?(@file.content_type)
|
28
|
+
end
|
29
|
+
|
30
|
+
def message(attribute, value, validator)
|
31
|
+
if @file
|
32
|
+
"The file should be one of the following types #{@types.to_sentence}"
|
33
|
+
else
|
34
|
+
'The field should be a file.'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::MinDigits < MiniDefender::Rule
|
4
|
+
def initialize(limit)
|
5
|
+
raise ArgumentError, 'Limit must be a string.' unless limit.is_a?(Integer)
|
6
|
+
|
7
|
+
@limit = limit
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.signature
|
11
|
+
'min_digits'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.make(args)
|
15
|
+
raise ArgumentError, 'Expected at least one argument for min_digits.' unless args.length == 1
|
16
|
+
|
17
|
+
new(args[0].to_i)
|
18
|
+
end
|
19
|
+
|
20
|
+
def passes?(attribute, value, validator)
|
21
|
+
@integers = valid = value.is_a?(Integer) || value.is_a?(String) && value.match?(/^\d+$/)
|
22
|
+
valid && value.to_s.length >= @limit
|
23
|
+
end
|
24
|
+
|
25
|
+
def message(attribute, value, validator)
|
26
|
+
if @integers
|
27
|
+
'The field should only contain digits.'
|
28
|
+
else
|
29
|
+
"The field should have at least #{@limit} digits."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'luhn'
|
4
|
+
|
5
|
+
class MiniDefender::Rules::NationalId < MiniDefender::Rules::Luhn
|
6
|
+
def self.signature
|
7
|
+
'national_id'
|
8
|
+
end
|
9
|
+
|
10
|
+
def message(attribute, value, validator)
|
11
|
+
'The value must be a proper national ID number.'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class MiniDefender::Rules::NotEndingWith < MiniDefender::Rule
|
4
|
+
def initialize(fragments)
|
5
|
+
unless fragments.is_a?(Array) && !fragments.empty? && fragments.all? { |f| f.is_a?(String) }
|
6
|
+
raise ArgumentError, 'Expected an array of strings.'
|
7
|
+
end
|
8
|
+
|
9
|
+
@fragments = fragments
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.signature
|
13
|
+
'not_ending_with'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.make(args)
|
17
|
+
new(args)
|
18
|
+
end
|
19
|
+
|
20
|
+
def passes?(attribute, value, validator)
|
21
|
+
@fragments.any? { |f| !value.to_s.end_with?(f) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def message(attribute, value, validator)
|
25
|
+
if @fragments.length == 1
|
26
|
+
"The value should not end with #{@fragments[0]}."
|
27
|
+
else
|
28
|
+
"The value should not end with one of the following #{@fragments.join(', ')}."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|