mini_defender 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile +10 -0
  4. data/Gemfile.lock +70 -0
  5. data/LICENSE.md +21 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +33 -0
  8. data/RULES.md +92 -0
  9. data/Rakefile +12 -0
  10. data/lib/mini_defender/extensions/enumerable.rb +34 -0
  11. data/lib/mini_defender/extensions/hash.rb +28 -0
  12. data/lib/mini_defender/rule.rb +77 -0
  13. data/lib/mini_defender/rules/accepted.rb +17 -0
  14. data/lib/mini_defender/rules/accepted_if.rb +26 -0
  15. data/lib/mini_defender/rules/alpha.rb +15 -0
  16. data/lib/mini_defender/rules/alpha_dash.rb +15 -0
  17. data/lib/mini_defender/rules/alpha_num.rb +15 -0
  18. data/lib/mini_defender/rules/array.rb +15 -0
  19. data/lib/mini_defender/rules/bail.rb +19 -0
  20. data/lib/mini_defender/rules/between.rb +29 -0
  21. data/lib/mini_defender/rules/boolean.rb +26 -0
  22. data/lib/mini_defender/rules/confirmed.rb +24 -0
  23. data/lib/mini_defender/rules/country_code.rb +23 -0
  24. data/lib/mini_defender/rules/credit_card.rb +13 -0
  25. data/lib/mini_defender/rules/date.rb +27 -0
  26. data/lib/mini_defender/rules/date_eq.rb +43 -0
  27. data/lib/mini_defender/rules/date_format.rb +38 -0
  28. data/lib/mini_defender/rules/date_gt.rb +24 -0
  29. data/lib/mini_defender/rules/date_gte.rb +24 -0
  30. data/lib/mini_defender/rules/date_lt.rb +24 -0
  31. data/lib/mini_defender/rules/date_lte.rb +24 -0
  32. data/lib/mini_defender/rules/declined.rb +17 -0
  33. data/lib/mini_defender/rules/declined_if.rb +26 -0
  34. data/lib/mini_defender/rules/default.rb +33 -0
  35. data/lib/mini_defender/rules/different.rb +31 -0
  36. data/lib/mini_defender/rules/digits.rb +26 -0
  37. data/lib/mini_defender/rules/digits_between.rb +29 -0
  38. data/lib/mini_defender/rules/distinct.rb +18 -0
  39. data/lib/mini_defender/rules/email.rb +19 -0
  40. data/lib/mini_defender/rules/ending_with.rb +31 -0
  41. data/lib/mini_defender/rules/equal.rb +9 -0
  42. data/lib/mini_defender/rules/excluded.rb +15 -0
  43. data/lib/mini_defender/rules/excluded_if.rb +28 -0
  44. data/lib/mini_defender/rules/excluded_unless.rb +28 -0
  45. data/lib/mini_defender/rules/excluded_with.rb +27 -0
  46. data/lib/mini_defender/rules/excluded_without.rb +27 -0
  47. data/lib/mini_defender/rules/exists.rb +29 -0
  48. data/lib/mini_defender/rules/expiry_date.rb +28 -0
  49. data/lib/mini_defender/rules/file.rb +17 -0
  50. data/lib/mini_defender/rules/filled.rb +22 -0
  51. data/lib/mini_defender/rules/greater_than.rb +34 -0
  52. data/lib/mini_defender/rules/greater_than_or_equal.rb +34 -0
  53. data/lib/mini_defender/rules/hash.rb +15 -0
  54. data/lib/mini_defender/rules/image.rb +19 -0
  55. data/lib/mini_defender/rules/in.rb +27 -0
  56. data/lib/mini_defender/rules/in_field.rb +40 -0
  57. data/lib/mini_defender/rules/integer.rb +19 -0
  58. data/lib/mini_defender/rules/ip.rb +43 -0
  59. data/lib/mini_defender/rules/ipv4.rb +43 -0
  60. data/lib/mini_defender/rules/ipv6.rb +43 -0
  61. data/lib/mini_defender/rules/json.rb +19 -0
  62. data/lib/mini_defender/rules/less_than.rb +34 -0
  63. data/lib/mini_defender/rules/less_than_or_equal.rb +34 -0
  64. data/lib/mini_defender/rules/luhn.rb +33 -0
  65. data/lib/mini_defender/rules/mac_address.rb +39 -0
  66. data/lib/mini_defender/rules/max.rb +9 -0
  67. data/lib/mini_defender/rules/max_digits.rb +32 -0
  68. data/lib/mini_defender/rules/mime_types.rb +37 -0
  69. data/lib/mini_defender/rules/min.rb +9 -0
  70. data/lib/mini_defender/rules/min_digits.rb +32 -0
  71. data/lib/mini_defender/rules/national_id.rb +13 -0
  72. data/lib/mini_defender/rules/not_ending_with.rb +31 -0
  73. data/lib/mini_defender/rules/not_in.rb +27 -0
  74. data/lib/mini_defender/rules/not_regex.rb +27 -0
  75. data/lib/mini_defender/rules/not_starting_with.rb +31 -0
  76. data/lib/mini_defender/rules/numeric.rb +19 -0
  77. data/lib/mini_defender/rules/present.rb +19 -0
  78. data/lib/mini_defender/rules/prohibited.rb +15 -0
  79. data/lib/mini_defender/rules/prohibited_if.rb +32 -0
  80. data/lib/mini_defender/rules/prohibited_unless.rb +32 -0
  81. data/lib/mini_defender/rules/regex.rb +27 -0
  82. data/lib/mini_defender/rules/required.rb +32 -0
  83. data/lib/mini_defender/rules/required_if.rb +26 -0
  84. data/lib/mini_defender/rules/required_unless.rb +26 -0
  85. data/lib/mini_defender/rules/required_with.rb +27 -0
  86. data/lib/mini_defender/rules/required_with_all.rb +27 -0
  87. data/lib/mini_defender/rules/required_without.rb +27 -0
  88. data/lib/mini_defender/rules/required_without_all.rb +27 -0
  89. data/lib/mini_defender/rules/size.rb +45 -0
  90. data/lib/mini_defender/rules/starting_with.rb +31 -0
  91. data/lib/mini_defender/rules/string.rb +15 -0
  92. data/lib/mini_defender/rules/timezone.rb +19 -0
  93. data/lib/mini_defender/rules/unique.rb +37 -0
  94. data/lib/mini_defender/rules/url.rb +17 -0
  95. data/lib/mini_defender/rules/uuid.rb +37 -0
  96. data/lib/mini_defender/rules.rb +8 -0
  97. data/lib/mini_defender/rules_expander.rb +19 -0
  98. data/lib/mini_defender/rules_factory.rb +36 -0
  99. data/lib/mini_defender/validates_input.rb +12 -0
  100. data/lib/mini_defender/validation_error.rb +10 -0
  101. data/lib/mini_defender/validator.rb +127 -0
  102. data/lib/mini_defender/version.rb +5 -0
  103. data/lib/mini_defender.rb +24 -0
  104. data/mini_defender.gemspec +38 -0
  105. data/sig/mini_defender.rbs +4 -0
  106. 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'less_than_or_equal'
4
+
5
+ class MiniDefender::Rules::Max < MiniDefender::Rules::LessThanOrEqual
6
+ def self.signature
7
+ 'max'
8
+ end
9
+ 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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'greater_than_or_equal'
4
+
5
+ class MiniDefender::Rules::Min < MiniDefender::Rules::GreaterThanOrEqual
6
+ def self.signature
7
+ 'min'
8
+ end
9
+ 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