Skip to content

Add in ability to dynamically load in deny_listed domains #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/valid_email2/address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ def allow_listed?
end

def deny_listed?
valid? && domain_is_in?(ValidEmail2.deny_list)
valid? && (
DynamicOptionValues.domain_is_in?(:deny_list_function, address) || domain_is_in?(ValidEmail2.deny_list)
)
end

def valid_mx?
Expand Down
47 changes: 47 additions & 0 deletions lib/valid_email2/dynamic_option_values.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal:true

module ValidEmail2
class DynamicOptionValues
class << self
def deny_list_function
@deny_list_function ||= ->(_domain) { false }
end

def deny_list_function=(lambda_function)
return unless lambda_function.is_a?(Proc)
return unless lambda_function.arity == 1

@deny_list_function = lambda_function
end

def parse_option_for_additional_items(type, value)
return false unless respond_to?("#{type}_function=")

case value
when NilClass
return false
when TrueClass, FalseClass
return value
when Set, Array
self.deny_list_function = ->(domain) { value.include?(domain) }
when Proc
self.deny_list_function = value
else
return false
end

true
end

def domain_is_in?(type, address)
return false unless type.is_a?(Symbol)
return false unless respond_to?(type)
return false unless address.is_a?(Mail::Address)

downcase_domain = address.domain.downcase

send(type).call(downcase_domain)
end
end
end
end
2 changes: 2 additions & 0 deletions lib/valid_email2/email_validator.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "valid_email2/address"
require "valid_email2/dynamic_option_values"
require "active_model"
require "active_model/validations"

Expand Down Expand Up @@ -41,6 +42,7 @@ def validate_each(record, attribute, value)
end

if options[:deny_list]
ValidEmail2::DynamicOptionValues.parse_option_for_additional_items(:deny_list, options[:deny_list])
error(record, attribute) && return if addresses.any?(&:deny_listed?)
end

Expand Down
33 changes: 33 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,36 @@ def read_attribute_for_validation(key)
@attributes[key]
end
end

class TestDynamicDomainModel
def self.where(*); end

def self.column_names
[domain_attribute].compact
end

def self.exists?(hash)
value = hash[self.domain_attribute.to_sym]
return false if value.nil?

existng_array = self.domain_attribute_values
existng_array.include?(value)
end

def self.domain_attribute
@domain_attribute ||= "domain"
end

def self.domain_attribute_values
@domain_attribute_values ||= []
end

def self.domain_attribute=(new_domain_attribute)
@domain_attribute = new_domain_attribute
@domain_attribute_values = domain_attribute_values
end

def self.domain_attribute_values=(new_domain_attribute_values)
@domain_attribute_values = new_domain_attribute_values
end
end
Comment on lines +25 to +56
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of a fake class we can use to test the hash for setting an active record query

44 changes: 44 additions & 0 deletions spec/valid_email2_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ class TestUserDisallowDenyListed < TestModel
validates :email, 'valid_email_2/email': { deny_list: true }
end

class TestUserDisallowDenyListedWithDynamicArray < TestModel
validates :email, 'valid_email_2/email': { deny_list: proc { |domain| ["test-dynamic-array.com"].include?(domain) } }
end

class TestUserDisallowDenyListedWithDynamicSet < TestModel
validates :email, 'valid_email_2/email': { deny_list: proc { |domain| Set.new(["test-dynamic-set.com"]).include?(domain) } }
end

class TestUserDisallowDenyListedWithDynamicLambda < TestModel
validates :email, 'valid_email_2/email': { deny_list: ->(domain) { Set.new(["test-dynamic-lambda.com"]).include?(domain) } }
end

class TestUserDisallowDenyListedWithDynamicRailsModel < TestModel
validates :email, 'valid_email_2/email': { deny_list: ->(domain) { TestDynamicDomainModel.exists?(domain: domain) } }
end

class TestUserMessage < TestModel
validates :email, 'valid_email_2/email': { message: "custom message" }
end
Expand Down Expand Up @@ -262,6 +278,34 @@ def set_allow_list
user = TestUserDisallowDenyListed.new(email: "[email protected]")
expect(user.valid?).to be_falsey
end

it "is invalid if the domain is deny-listed via a dynamic array option" do
user = TestUserDisallowDenyListedWithDynamicArray.new(email: "[email protected]")
expect(user.valid?).to be_falsy
end

it "is invalid if the domain is deny-listed via a dynamic set option" do
user = TestUserDisallowDenyListedWithDynamicSet.new(email: "[email protected]")
expect(user.valid?).to be_falsy
end

it "is invalid if the domain is deny-listed via a dynamic proc option" do
user = TestUserDisallowDenyListedWithDynamicLambda.new(email: "[email protected]")
expect(user.valid?).to be_falsy
end

it "is invalid if the domain is deny-listed via a dynamic rails model" do
invalid_dynamic_domain = "test-dynamic-rails-model.com"
TestDynamicDomainModel.domain_attribute_values = [invalid_dynamic_domain]
user = TestUserDisallowDenyListedWithDynamicRailsModel.new(email: "foo@#{invalid_dynamic_domain}")
expect(user.valid?).to be_falsy
end

it "is valid if the dynamic domain list does not include the email domain" do
TestDynamicDomainModel.domain_attribute_values = ["not-deny-listed.com"]
user = TestUserDisallowDenyListedWithDynamicRailsModel.new(email: "[email protected]")
expect(user.valid?).to be_truthy
end
end

describe "with mx validation" do
Expand Down
Loading