From d1dbf3c5a83771ae27b025a1dfb4a5f2cae82d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Schr=C3=B6der?= Date: Wed, 26 Feb 2025 14:16:00 +0100 Subject: [PATCH 1/2] allow providing your own stuff --- README.md | 22 ++++++++++++++++------ lib/valid_email2.rb | 17 ++++++++++++----- spec/valid_email2_spec.rb | 7 +++++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 932443ed..ac0ab096 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,13 @@ To validate strictly that the domain has an MX record: ```ruby validates :email, 'valid_email_2/email': { strict_mx: true } ``` -`strict_mx` and `mx` both default to a 5 second timeout for DNS lookups. +`strict_mx` and `mx` both default to a 5 second timeout for DNS lookups. To override this timeout, specify a `dns_timeout` option: ```ruby validates :email, 'valid_email_2/email': { strict_mx: true, dns_timeout: 10 } ``` -Any checks that require DNS resolution will use the default `Resolv::DNS` nameservers for DNS lookups. +Any checks that require DNS resolution will use the default `Resolv::DNS` nameservers for DNS lookups. To override these, specify a `dns_nameserver` option: ```ruby validates :email, 'valid_email_2/email': { mx: true, dns_nameserver: ['8.8.8.8', '8.8.4.4'] } @@ -145,10 +145,20 @@ If you want to allow multibyte characters, set it explicitly. ValidEmail2::Address.permitted_multibyte_characters_regex = /[ÆæØøÅåÄäÖöÞþÐð]/ ``` +If you want to allow load any of the lists dynamically + +```ruby +ValidEmail2.reset_lists # reset the content +# make sure to use a Set for speedy access +ValidEmail2.disposable_emails = -> { Set.new(['your.com']) } +ValidEmail2.deny_list = -> { Set.new(['data.com']) } +ValidEmail2.allow_list = -> { Set.new(['provided.com']) } +``` + ### Test environment If you are validating `mx` then your specs will fail without an internet connection. -It is a good idea to stub out that validation in your test environment. +It is a good idea to stub out that validation in your test environment. Do so by adding this in your `spec_helper`: ```ruby config.before(:each) do @@ -168,7 +178,7 @@ This gem is tested against currently supported Ruby and Rails versions. For an u In version v5.3.0 the config directory files were renamed as follows: -`config/blacklisted_email_domains.yml` -> `config/deny_listed_email_domains.yml` +`config/blacklisted_email_domains.yml` -> `config/deny_listed_email_domains.yml` `config/whitelisted_email_domains.yml` -> `config/allow_listed_email_domains.yml` You won't need to make any changes yourself if you're installing this version for the first time. For individuals updating from earlier versions, make sure to update the file namings as per the above. In future versions this will be a breaking change. @@ -178,7 +188,7 @@ You won't need to make any changes yourself if you're installing this version fo In version v3.0.0 I decided to move __and__ rename the config files from the vendor directory to the config directory. That means: -`vendor/blacklist.yml` -> `config/blacklisted_email_domains.yml` +`vendor/blacklist.yml` -> `config/blacklisted_email_domains.yml` `vendor/whitelist.yml` -> `config/whitelisted_email_domains.yml` The `disposable` validation has been improved with a `mx` check. Apply the @@ -187,7 +197,7 @@ down or if they do not work without an internet connection. ## Upgrading to v2.0.0 -In version 1.0 of valid_email2 we only defined the `email` validator. +In version 1.0 of valid_email2 we only defined the `email` validator. But since other gems also define a `email` validator this can cause some unintended behaviours and emails that shouldn't be valid are regarded valid because the wrong validator is used by rails. diff --git a/lib/valid_email2.rb b/lib/valid_email2.rb index 9622d93a..849400fe 100644 --- a/lib/valid_email2.rb +++ b/lib/valid_email2.rb @@ -8,16 +8,24 @@ module ValidEmail2 DISPOSABLE_FILE = File.expand_path('../config/disposable_email_domains.txt', __dir__) class << self + attr_accessor :disposable_proc, :deny_proc, :allow_proc + + def reset_lists + @disposable_emails = nil + @deny_list = nil + @allow_list = nil + end + def disposable_emails - @disposable_emails ||= load_file(DISPOSABLE_FILE) + @disposable_emails ||= disposable_proc&.call || load_file(DISPOSABLE_FILE) end def deny_list - @deny_list ||= load_if_exists(DENY_LIST_FILE) || Set.new + @deny_list ||= deny_proc&.call || load_if_exists(DENY_LIST_FILE) || Set.new end def allow_list - @allow_list ||= load_if_exists(ALLOW_LIST_FILE) || Set.new + @allow_list ||= allow_proc&.call || load_if_exists(ALLOW_LIST_FILE) || Set.new end private @@ -27,8 +35,7 @@ def load_if_exists(path) end def load_file(path) - # This method MUST return a Set, otherwise the - # performance will suffer! + # This method MUST return a Set, otherwise the performance will suffer! if path.end_with?(".yml") Set.new(YAML.load_file(path)) else diff --git a/spec/valid_email2_spec.rb b/spec/valid_email2_spec.rb index 83c63d6f..57ae4617 100644 --- a/spec/valid_email2_spec.rb +++ b/spec/valid_email2_spec.rb @@ -275,6 +275,13 @@ def set_allow_list user = TestUserDisallowDenyListed.new(email: "foo@deny-listed-test.com") expect(user.valid?).to be_falsey end + + it "is invalid if the domain is deny-listed from a proc" do + ValidEmail2.deny_proc = -> { Set.new(['dubbi.com']) } + ValidEmail2.reset_lists + user = TestUserDisallowDenyListed.new(email: "foo@dubbi.com") + expect(user.valid?).to be_falsey + end end describe "with mx validation" do From 302a066336dfa21630b869b4236b900e7ba3de0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Schr=C3=B6der?= Date: Sat, 15 Mar 2025 09:46:45 +0100 Subject: [PATCH 2/2] FEAT: allow list configuration for disposable, deny and allow via procs --- .rubocop.yml | 2 ++ README.md | 11 +++++------ lib/valid_email2.rb | 24 +++++++++++++----------- spec/valid_email2_spec.rb | 13 +++++++++++-- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 6d5c013d..a50e7353 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,3 +15,5 @@ Style/StringLiterals: EnforcedStyle: double_quotes Gemspec/OrderedDependencies: Enabled: no +Metrics/BlockLength: + Enabled: no diff --git a/README.md b/README.md index ac0ab096..0820ae75 100644 --- a/README.md +++ b/README.md @@ -145,14 +145,13 @@ If you want to allow multibyte characters, set it explicitly. ValidEmail2::Address.permitted_multibyte_characters_regex = /[ÆæØøÅåÄäÖöÞþÐð]/ ``` -If you want to allow load any of the lists dynamically +If you want to load any of the lists dynamically: ```ruby -ValidEmail2.reset_lists # reset the content -# make sure to use a Set for speedy access -ValidEmail2.disposable_emails = -> { Set.new(['your.com']) } -ValidEmail2.deny_list = -> { Set.new(['data.com']) } -ValidEmail2.allow_list = -> { Set.new(['provided.com']) } +# make sure to use a Set for speedy access and cache the result +ValidEmail2.disposable_proc = -> { Set.new(['your.com']) } +ValidEmail2.deny_proc = -> { Set.new(['data.com']) } +ValidEmail2.allow_proc = -> { Set.new(['provided.com']) } ``` ### Test environment diff --git a/lib/valid_email2.rb b/lib/valid_email2.rb index 849400fe..5f557ee5 100644 --- a/lib/valid_email2.rb +++ b/lib/valid_email2.rb @@ -7,25 +7,27 @@ module ValidEmail2 ALLOW_LIST_FILE = "config/allow_listed_email_domains.yml" DISPOSABLE_FILE = File.expand_path('../config/disposable_email_domains.txt', __dir__) - class << self - attr_accessor :disposable_proc, :deny_proc, :allow_proc - - def reset_lists - @disposable_emails = nil - @deny_list = nil - @allow_list = nil - end + cattr_accessor :disposable_proc do + -> { @disposable_emails ||= load_file(DISPOSABLE_FILE) } + end + cattr_accessor :deny_proc do + -> { @deny_list ||= load_if_exists(DENY_LIST_FILE) || Set.new } + end + cattr_accessor :allow_proc do + -> { @allow_list ||= load_if_exists(ALLOW_LIST_FILE) || Set.new } + end + class << self def disposable_emails - @disposable_emails ||= disposable_proc&.call || load_file(DISPOSABLE_FILE) + disposable_proc&.call end def deny_list - @deny_list ||= deny_proc&.call || load_if_exists(DENY_LIST_FILE) || Set.new + deny_proc&.call end def allow_list - @allow_list ||= allow_proc&.call || load_if_exists(ALLOW_LIST_FILE) || Set.new + allow_proc&.call end private diff --git a/spec/valid_email2_spec.rb b/spec/valid_email2_spec.rb index 57ae4617..b888262a 100644 --- a/spec/valid_email2_spec.rb +++ b/spec/valid_email2_spec.rb @@ -275,10 +275,19 @@ def set_allow_list user = TestUserDisallowDenyListed.new(email: "foo@deny-listed-test.com") expect(user.valid?).to be_falsey end + end + + describe "with deny list validation" do + before do + @old_proc = ValidEmail2.deny_proc + end + + after do + ValidEmail2.deny_proc = @old_proc + end it "is invalid if the domain is deny-listed from a proc" do - ValidEmail2.deny_proc = -> { Set.new(['dubbi.com']) } - ValidEmail2.reset_lists + ValidEmail2.deny_proc = -> { @deny_list ||= Set.new(['dubbi.com']) } user = TestUserDisallowDenyListed.new(email: "foo@dubbi.com") expect(user.valid?).to be_falsey end