validators 3.1.1 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +11 -1
- data/README.md +49 -29
- data/Rakefile +0 -1
- data/data/reserved_subdomains.txt +2830 -0
- data/lib/validators.rb +12 -4
- data/lib/validators/constants.rb +1 -1
- data/lib/validators/disposable_domains.rb +18 -0
- data/lib/validators/disposable_emails.rb +22 -0
- data/lib/validators/locale/en.yml +5 -2
- data/lib/validators/locale/pt-BR.yml +1 -0
- data/lib/validators/{reserved_hostnames.rb → reserved_subdomains.rb} +3 -5
- data/lib/validators/tld.rb +7 -5
- data/lib/validators/validates_cnpj_format_of.rb +1 -2
- data/lib/validators/validates_cpf_format_of.rb +1 -2
- data/lib/validators/validates_email_format_of.rb +19 -4
- data/lib/validators/validates_ssh_private_key.rb +1 -3
- data/lib/validators/validates_ssh_public_key.rb +1 -3
- data/lib/validators/validates_subdomain.rb +69 -0
- data/lib/validators/validates_username.rb +15 -0
- data/lib/validators/version.rb +1 -1
- data/test/test_helper.rb +17 -2
- data/test/validators/disposable_email_test.rb +18 -5
- data/test/validators/validates_cnpj_format_of_test.rb +3 -3
- data/test/validators/validates_cpf_format_of_test.rb +3 -3
- data/test/validators/validates_email_format_of_test.rb +27 -2
- data/test/validators/validates_ssh_private_key/common_test.rb +3 -3
- data/test/validators/validates_ssh_public_key_test.rb +3 -3
- data/test/validators/validates_subdomain_test.rb +75 -0
- data/test/validators/validates_url_format_of/with_tld_validation_test.rb +18 -0
- data/test/validators/validates_url_format_of/without_tld_validation_test.rb +18 -0
- data/test/validators/{validates_reserved_username_test.rb → validates_username_test.rb} +27 -4
- data/validators.gemspec +11 -1
- metadata +116 -22
- data/bin/sync-disposable-hostnames +0 -35
- data/bin/sync-tld +0 -17
- data/data/disposable.json +0 -60403
- data/data/reserved_hostnames.json +0 -2836
- data/data/tld.json +0 -1516
- data/lib/validators/disposable_hostnames.rb +0 -11
- data/lib/validators/validates_reserved_hostname.rb +0 -45
- data/lib/validators/validates_reserved_username.rb +0 -29
- data/test/validators/validates_reserved_hostname_test.rb +0 -40
data/lib/validators.rb
CHANGED
@@ -7,8 +7,9 @@ module Validators
|
|
7
7
|
require "validators/ip"
|
8
8
|
require "validators/tld"
|
9
9
|
require "validators/hostname"
|
10
|
-
require "validators/
|
11
|
-
require "validators/
|
10
|
+
require "validators/disposable_domains"
|
11
|
+
require "validators/disposable_emails"
|
12
|
+
require "validators/reserved_subdomains"
|
12
13
|
|
13
14
|
require "validators/validates_datetime"
|
14
15
|
require "validators/validates_ip_address"
|
@@ -20,8 +21,15 @@ module Validators
|
|
20
21
|
require "validators/validates_ssh_private_key"
|
21
22
|
require "validators/validates_ssh_public_key"
|
22
23
|
require "validators/validates_hostname_format_of"
|
23
|
-
require "validators/
|
24
|
-
require "validators/
|
24
|
+
require "validators/validates_subdomain"
|
25
|
+
require "validators/validates_username"
|
25
26
|
|
26
27
|
I18n.load_path += Dir[File.join(__dir__, "validators/locale/*.yml")]
|
28
|
+
|
29
|
+
def self.require_dependency!(dep)
|
30
|
+
require dep
|
31
|
+
rescue LoadError
|
32
|
+
raise "#{dep} is not part of the bundle. " \
|
33
|
+
"Add it to your project's Gemfile."
|
34
|
+
end
|
27
35
|
end
|
data/lib/validators/constants.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Validators
|
4
4
|
EMAIL_FORMAT = /\A[a-z0-9]+([-._][a-z0-9]+)*(\+[^@]+)?@[a-z0-9]+([.-][a-z0-9]+)*\.[a-z]{2,}\z/i.freeze
|
5
|
-
MICROSOFT_EMAIL_FORMAT = /\A[
|
5
|
+
MICROSOFT_EMAIL_FORMAT = /\A[a-z0-9][a-z0-9._-]*[a-z0-9_-]+(\+[a-z0-9]+)?@(hotmail|outlook).com\z/i.freeze
|
6
6
|
|
7
7
|
# Source: https://github.com/henrik/validates_url_format_of
|
8
8
|
IPV4_PART = /\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]/.freeze # 0-255
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Validators
|
4
|
+
class DisposableDomains
|
5
|
+
def self.all
|
6
|
+
@all ||=
|
7
|
+
begin
|
8
|
+
Validators.require_dependency! "root_domain"
|
9
|
+
Validators.require_dependency! "email_data"
|
10
|
+
EmailData.disposable_domains
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.include?(domain)
|
15
|
+
all.include?(domain)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Validators
|
4
|
+
class DisposableEmails
|
5
|
+
def self.all
|
6
|
+
@all ||=
|
7
|
+
begin
|
8
|
+
Validators.require_dependency! "root_domain"
|
9
|
+
Validators.require_dependency! "email_data"
|
10
|
+
EmailData.disposable_emails
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.include?(email)
|
15
|
+
mailbox, domain = email.to_s.split("@")
|
16
|
+
mailbox = mailbox.to_s.gsub(".", "")
|
17
|
+
mailbox = mailbox.gsub(/\+(.+)?\Z/, "")
|
18
|
+
|
19
|
+
all.include?("#{mailbox}@#{domain}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -3,7 +3,8 @@ en:
|
|
3
3
|
activemodel: &activemodel
|
4
4
|
errors:
|
5
5
|
messages:
|
6
|
-
disposable_email: "is not allowed (high-bounce
|
6
|
+
disposable_email: "is not allowed (high-bounce email)"
|
7
|
+
disposable_domain: "is not allowed (high-bounce domain)"
|
7
8
|
invalid_cnpj: "is not a valid CNPJ"
|
8
9
|
invalid_cpf: "is not a valid CPF"
|
9
10
|
invalid_date: "is not a valid date"
|
@@ -20,8 +21,10 @@ en:
|
|
20
21
|
invalid_ssh_private_key_type: "must be a %{value} key"
|
21
22
|
invalid_ssh_public_key: "is not a valid public SSH key"
|
22
23
|
invalid_url: "is not a valid address"
|
23
|
-
|
24
|
+
reserved_subdomain: "%{value} is a reserved subdomain"
|
25
|
+
invalid_subdomain: "is invalid"
|
24
26
|
reserved_username: "%{value} is a reserved username"
|
27
|
+
invalid_username: "is invalid"
|
25
28
|
|
26
29
|
activerecord:
|
27
30
|
<<: *activemodel
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Validators
|
4
|
-
class
|
5
|
-
FILE_PATH = File.expand_path("../../data/
|
4
|
+
class ReservedSubdomains
|
5
|
+
FILE_PATH = File.expand_path("../../data/reserved_subdomains.txt", __dir__)
|
6
6
|
|
7
7
|
def self.reserved?(hostname, matchers = nil)
|
8
8
|
matchers = parse_list(matchers) if matchers
|
@@ -11,9 +11,7 @@ module Validators
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.all
|
14
|
-
@all ||=
|
15
|
-
.parse(File.read(FILE_PATH))
|
16
|
-
.map {|matcher| parse(matcher) }
|
14
|
+
@all ||= File.read(FILE_PATH).lines.map {|matcher| parse(matcher.chomp) }
|
17
15
|
end
|
18
16
|
|
19
17
|
def self.parse(matcher)
|
data/lib/validators/tld.rb
CHANGED
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
module Validators
|
4
4
|
class TLD
|
5
|
-
FILE_PATH = File.expand_path("../../data/tld.json", __dir__)
|
6
|
-
|
7
5
|
def self.all
|
8
|
-
@all ||=
|
6
|
+
@all ||=
|
7
|
+
begin
|
8
|
+
Validators.require_dependency! "email_data"
|
9
|
+
EmailData.tlds
|
10
|
+
end
|
9
11
|
end
|
10
12
|
|
11
13
|
def self.host_with_valid_tld?(host)
|
@@ -13,10 +15,10 @@ module Validators
|
|
13
15
|
|
14
16
|
return false if host.split(".").size == 1
|
15
17
|
|
16
|
-
|
18
|
+
include?(host[/\.([^.]+)$/, 1].to_s.downcase)
|
17
19
|
end
|
18
20
|
|
19
|
-
def self.
|
21
|
+
def self.include?(tld)
|
20
22
|
all.include?(tld)
|
21
23
|
end
|
22
24
|
end
|
@@ -25,10 +25,9 @@ module ActiveModel
|
|
25
25
|
# end
|
26
26
|
#
|
27
27
|
def validates_cnpj_format_of(*attr_names)
|
28
|
+
Validators.require_dependency! "cpf_cnpj"
|
28
29
|
require "cnpj"
|
29
30
|
validates_with CnpjValidator, _merge_attributes(attr_names)
|
30
|
-
rescue LoadError
|
31
|
-
raise "cpf_cnpj is not part of the bundle. Add it to Gemfile."
|
32
31
|
end
|
33
32
|
|
34
33
|
alias_method :validates_cnpj, :validates_cnpj_format_of
|
@@ -25,10 +25,9 @@ module ActiveModel
|
|
25
25
|
# end
|
26
26
|
#
|
27
27
|
def validates_cpf_format_of(*attr_names)
|
28
|
+
Validators.require_dependency! "cpf_cnpj"
|
28
29
|
require "cpf"
|
29
30
|
validates_with CpfValidator, _merge_attributes(attr_names)
|
30
|
-
rescue LoadError
|
31
|
-
raise "cpf_cnpj is not part of the bundle. Add it to Gemfile."
|
32
31
|
end
|
33
32
|
|
34
33
|
alias_method :validates_cpf, :validates_cpf_format_of
|
@@ -14,6 +14,7 @@ module ActiveModel
|
|
14
14
|
|
15
15
|
validate_tld(record, attribute, value, options) if check_tld
|
16
16
|
validate_email_format(record, attribute, value, options)
|
17
|
+
validate_disposable_domain(record, attribute, value, options) unless allow_disposable
|
17
18
|
validate_disposable_email(record, attribute, value, options) unless allow_disposable
|
18
19
|
end
|
19
20
|
|
@@ -41,12 +42,24 @@ module ActiveModel
|
|
41
42
|
)
|
42
43
|
end
|
43
44
|
|
44
|
-
def
|
45
|
+
def validate_disposable_domain(record, attribute, value, _options)
|
46
|
+
return unless value
|
47
|
+
|
45
48
|
hostname = value.to_s.split(AT_SIGN).last.to_s.downcase
|
49
|
+
root_domain = RootDomain.call(hostname)
|
50
|
+
|
51
|
+
return unless Validators::DisposableDomains.include?(root_domain)
|
52
|
+
|
53
|
+
record.errors.add(
|
54
|
+
attribute,
|
55
|
+
:disposable_domain,
|
56
|
+
value: value
|
57
|
+
)
|
58
|
+
end
|
46
59
|
|
47
|
-
|
48
|
-
|
49
|
-
|
60
|
+
def validate_disposable_email(record, attribute, value, _options)
|
61
|
+
return unless value
|
62
|
+
return unless Validators::DisposableEmails.include?(value)
|
50
63
|
|
51
64
|
record.errors.add(
|
52
65
|
attribute,
|
@@ -64,6 +77,8 @@ module ActiveModel
|
|
64
77
|
# end
|
65
78
|
#
|
66
79
|
def validates_email_format_of(*attr_names)
|
80
|
+
Validators.require_dependency! "root_domain"
|
81
|
+
Validators.require_dependency! "email_data"
|
67
82
|
validates_with EmailValidator, _merge_attributes(attr_names)
|
68
83
|
end
|
69
84
|
|
@@ -56,10 +56,8 @@ module ActiveModel
|
|
56
56
|
# end
|
57
57
|
#
|
58
58
|
def validates_ssh_private_key(*attr_names)
|
59
|
-
|
59
|
+
Validators.require_dependency! "sshkey"
|
60
60
|
validates_with SshPrivateKeyValidator, _merge_attributes(attr_names)
|
61
|
-
rescue LoadError
|
62
|
-
raise "sshkey is not part of the bundle. Add it to Gemfile."
|
63
61
|
end
|
64
62
|
end
|
65
63
|
end
|
@@ -25,10 +25,8 @@ module ActiveModel
|
|
25
25
|
# end
|
26
26
|
#
|
27
27
|
def validates_ssh_public_key(*attr_names)
|
28
|
-
|
28
|
+
Validators.require_dependency! "sshkey"
|
29
29
|
validates_with SshPublicKeyValidator, _merge_attributes(attr_names)
|
30
|
-
rescue LoadError
|
31
|
-
raise "sshkey is not part of the bundle. Add it to Gemfile."
|
32
30
|
end
|
33
31
|
end
|
34
32
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class SubdomainValidator < EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
return if value.blank? && options[:allow_blank]
|
8
|
+
return if value.nil? && options[:allow_nil]
|
9
|
+
|
10
|
+
value = value.to_s
|
11
|
+
|
12
|
+
validate_reserved_subdomain(record, attribute, value)
|
13
|
+
validate_format(record, attribute, value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def reserved?(subdomain)
|
17
|
+
::Validators::ReservedSubdomains.reserved?(subdomain, options[:in])
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_reserved_subdomain(record, attribute, value)
|
21
|
+
return unless options.fetch(:reserved, true)
|
22
|
+
return unless reserved?(value)
|
23
|
+
|
24
|
+
record.errors.add(
|
25
|
+
attribute,
|
26
|
+
:"reserved_#{options[:error_name]}",
|
27
|
+
message: options[:message],
|
28
|
+
value: value
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_format(record, attribute, value)
|
33
|
+
return if Validators::Hostname.valid_label?(value)
|
34
|
+
|
35
|
+
record.errors.add(
|
36
|
+
attribute,
|
37
|
+
:"invalid_#{options[:error_name]}",
|
38
|
+
message: options[:message],
|
39
|
+
value: value
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
# Validates whether or not the specified host label is valid.
|
46
|
+
# The `in: array` can have strings and patterns. A pattern is everything
|
47
|
+
# that starts with `/` and will be parsed as a regular expression.
|
48
|
+
#
|
49
|
+
# Notice that subdomains will be normalized; it'll be downcased and have
|
50
|
+
# its underscores and hyphens stripped before validating.
|
51
|
+
#
|
52
|
+
# class User < ActiveRecord::Base
|
53
|
+
# # Validates format and rejects reserved subdomains.
|
54
|
+
# validates_subdomain :subdomain
|
55
|
+
#
|
56
|
+
# # Validates against a custom list.
|
57
|
+
# validates_subdomain :subdomain, in: %w[www]
|
58
|
+
#
|
59
|
+
# # Rejects reserved domains validation.
|
60
|
+
# validates_subdomain :subdomain, reserved: false
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
def validates_subdomain(*attr_names)
|
64
|
+
options = _merge_attributes(attr_names).merge(error_name: :subdomain)
|
65
|
+
validates_with SubdomainValidator, options
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class UsernameValidator < SubdomainValidator
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def validates_username(*attr_names)
|
10
|
+
options = _merge_attributes(attr_names).merge(error_name: :username)
|
11
|
+
validates_with UsernameValidator, options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/validators/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
$VERBOSE = nil
|
4
4
|
|
5
5
|
require "simplecov"
|
6
|
-
SimpleCov.start
|
7
6
|
|
8
7
|
SimpleCov.start do
|
9
8
|
add_filter "test/support"
|
@@ -17,9 +16,25 @@ require "active_support/all"
|
|
17
16
|
require "minitest/utils"
|
18
17
|
require "minitest/autorun"
|
19
18
|
|
19
|
+
def build_email_with_filter(email)
|
20
|
+
mailbox, domain = email.split("@")
|
21
|
+
"#{mailbox}+#{SecureRandom.hex(3)}@#{domain}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_email_with_dots(email)
|
25
|
+
mailbox, domain = email.split("@")
|
26
|
+
new_mailbox = mailbox.chars.map {|char| [char, "."] }.flatten[0..-2].join
|
27
|
+
|
28
|
+
"#{new_mailbox}@#{domain}"
|
29
|
+
end
|
30
|
+
|
20
31
|
Time.zone = "America/Sao_Paulo"
|
21
32
|
TLDs = Validators::TLD.all.sample(10)
|
22
|
-
|
33
|
+
DISPOSABLE_DOMAINS = Validators::DisposableDomains.all.sample(10)
|
34
|
+
DISPOSABLE_EMAILS = Validators::DisposableEmails.all +
|
35
|
+
Validators::DisposableEmails.all.sample(10).map {|email| build_email_with_filter(email) } +
|
36
|
+
Validators::DisposableEmails.all.sample(10).map {|email| build_email_with_dots(email) } +
|
37
|
+
Validators::DisposableEmails.all.sample(10).map {|email| build_email_with_filter(build_email_with_dots(email)) }
|
23
38
|
|
24
39
|
Dir[File.join(__dir__, "support/**/*.rb")].sort.each {|f| require f }
|
25
40
|
|
@@ -3,18 +3,19 @@
|
|
3
3
|
require "test_helper"
|
4
4
|
|
5
5
|
class DisposableEmailTest < Minitest::Test
|
6
|
-
|
7
|
-
test "rejects disposable
|
6
|
+
DISPOSABLE_DOMAINS.each do |domain|
|
7
|
+
test "rejects disposable domain (#{domain})" do
|
8
8
|
User.validates_email_format_of :email
|
9
9
|
|
10
10
|
user = User.new(email: "user@#{domain}")
|
11
11
|
user.valid?
|
12
12
|
|
13
|
-
assert_includes user.errors[:email],
|
13
|
+
assert_includes user.errors[:email],
|
14
|
+
I18n.t("activerecord.errors.messages.disposable_domain")
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
+
DISPOSABLE_DOMAINS.each do |domain|
|
18
19
|
test "rejects disposable e-mail with subdomain (custom.#{domain})" do
|
19
20
|
User.validates_email_format_of :email
|
20
21
|
|
@@ -22,7 +23,7 @@ class DisposableEmailTest < Minitest::Test
|
|
22
23
|
user.valid?
|
23
24
|
|
24
25
|
assert_includes user.errors[:email],
|
25
|
-
"
|
26
|
+
I18n.t("activerecord.errors.messages.disposable_domain")
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
@@ -34,4 +35,16 @@ class DisposableEmailTest < Minitest::Test
|
|
34
35
|
|
35
36
|
assert user.errors[:email].empty?
|
36
37
|
end
|
38
|
+
|
39
|
+
DISPOSABLE_EMAILS.each do |email|
|
40
|
+
test "rejects disposable e-mail (#{email})" do
|
41
|
+
User.validates_email_format_of :email
|
42
|
+
|
43
|
+
user = User.new(email: email)
|
44
|
+
user.valid?
|
45
|
+
|
46
|
+
assert_includes user.errors[:email],
|
47
|
+
I18n.t("activerecord.errors.messages.disposable_email")
|
48
|
+
end
|
49
|
+
end
|
37
50
|
end
|
@@ -15,10 +15,10 @@ class ValidatesCnpjFormatOfTest < Minitest::Test
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
test "fails when
|
19
|
-
assert_raises do
|
18
|
+
test "fails when cpf_cnpj is not available" do
|
19
|
+
assert_raises(StandardError, /cpf_cnpj is not part of the bundle/) do
|
20
20
|
Class.new do
|
21
|
-
expects(:require).with("
|
21
|
+
Validators.expects(:require).with("cpf_cnpj").raises(LoadError, "-- cpf_cnpj")
|
22
22
|
|
23
23
|
include ActiveModel::Model
|
24
24
|
validates_cnpj_format_of :document
|