From 78abfbeaad899a11b664a43a31453c681a4e8425 Mon Sep 17 00:00:00 2001 From: Vladislav Trotsenko Date: Wed, 26 Jun 2019 14:26:28 +0300 Subject: [PATCH] Validation for whitelisted domains only (#51) --- .reek.yml | 1 + .rubocop.yml | 3 + Gemfile.lock | 2 +- README.md | 68 ++++++++++-- lib/truemail/configuration.rb | 31 ++++-- lib/truemail/validate/domain_list_match.rb | 14 ++- lib/truemail/version.rb | 2 +- .../shared_examples/has_attr_accessor.rb | 1 + .../sets_default_configuration.rb | 1 + spec/truemail/configuration_spec.rb | 4 + .../validate/domain_list_match_spec.rb | 104 +++++++++++++++--- 11 files changed, 190 insertions(+), 41 deletions(-) diff --git a/.reek.yml b/.reek.yml index 7bfd4af..2f82087 100644 --- a/.reek.yml +++ b/.reek.yml @@ -20,6 +20,7 @@ detectors: Attribute: exclude: + - Truemail::Configuration#whitelist_validation - Truemail::Configuration#smtp_safe_check - Truemail::Wrapper#attempts diff --git a/.rubocop.yml b/.rubocop.yml index 4a5d13e..be4999a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,6 +7,9 @@ AllCops: Metrics/LineLength: Max: 140 +Metrics/MethodLength: + Max: 15 + Metrics/CyclomaticComplexity: Enabled: false diff --git a/Gemfile.lock b/Gemfile.lock index ac60724..3a6a9ad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - truemail (1.1.0) + truemail (1.2.0) GEM remote: https://rubygems.org/ diff --git a/README.md b/README.md index 589c3e2..57ba9a9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Truemail -[![Maintainability](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/maintainability)](https://codeclimate.com/github/rubygarage/truemail/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/test_coverage)](https://codeclimate.com/github/rubygarage/truemail/test_coverage) [![Gem Version](https://badge.fury.io/rb/truemail.svg)](https://badge.fury.io/rb/truemail) [![CircleCI](https://circleci.com/gh/rubygarage/truemail/tree/master.svg?style=svg)](https://circleci.com/gh/rubygarage/truemail/tree/master) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) +[![Maintainability](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/maintainability)](https://codeclimate.com/github/rubygarage/truemail/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/test_coverage)](https://codeclimate.com/github/rubygarage/truemail/test_coverage) [![CircleCI](https://circleci.com/gh/rubygarage/truemail/tree/master.svg?style=svg)](https://circleci.com/gh/rubygarage/truemail/tree/master) [![Gem Version](https://badge.fury.io/rb/truemail.svg)](https://badge.fury.io/rb/truemail) [![Downloads](https://img.shields.io/gem/dt/truemail.svg?colorA=004d99&colorB=0073e6)](https://rubygems.org/gems/truemail) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md) The Truemail gem helps you validate emails by regex pattern, presence of domain mx-records, and real existence of email account on a current email server. Also Truemail gem allows performing an audit of the host in which runs. @@ -90,7 +90,12 @@ Truemail.configure do |config| # return true. Other validations will not processed even if it was defined in validation_type_for config.whitelisted_domains = ['somedomain1.com', 'somedomain2.com'] - # Optional parameter. Validation of email which contains whitelisted domain always will + # Optional parameter. With this option Truemail will validate email which contains whitelisted + # domain only, i.e. if domain whitelisted, validation will passed to Regex, MX or SMTP validators. + # Validation of email which not contains whitelisted domain always will return false. + config.whitelist_validation = true + + # Optional parameter. Validation of email which contains blacklisted domain always will # return false. Other validations will not processed even if it was defined in validation_type_for config.blacklisted_domains = ['somedomain1.com', 'somedomain2.com'] @@ -116,6 +121,7 @@ Truemail.configuration @connection_attempts=3, @validation_type_by_domain={}, @whitelisted_domains=[], + @whitelist_validation=true, @blacklisted_domains=[], @verifier_domain="somedomain.com", @verifier_email="verifier@example.com" @@ -141,6 +147,7 @@ Truemail.configuration @connection_attempts=1, @validation_type_by_domain={}, @whitelisted_domains=[], + @whitelist_validation=true, @blacklisted_domains=[], @verifier_domain="somedomain.com", @verifier_email="verifier@example.com", @@ -168,7 +175,8 @@ Please note, other validations will not processed even if it was defined in ```v **Sequence of domain list check:** 1. Whitelist check -2. Blacklist check +2. Whitelist validation check +3. Blacklist check Example of usage: @@ -201,6 +209,53 @@ Truemail.validate('email@white-domain.com') @validation_type=:whitelist> ``` +##### Whitelist validation case + +```ruby +require 'truemail' + +Truemail.configure do |config| + config.verifier_email = 'verifier@example.com' + config.whitelisted_domains = ['white-domain.com'] + config.whitelist_validation = true +end +``` + +When email domain in whitelist and ```whitelist_validation``` is sets equal to ```true``` validation type will be passed to other validators. +Validation of email which not contains whitelisted domain always will return ```false```. + +###### Email has whitelisted domain + +```ruby +Truemail.validate('email@white-domain.com', with: :regex) + +#, + @validation_type=:regex> +``` + +###### Email hasn't whitelisted domain + +```ruby +Truemail.validate('email@domain.com', with: :regex) + +#, + @validation_type=:blacklist> +``` + ##### Blacklist case When email in blacklist, validation type will be redefined too. Validation result returns ```false``` @@ -384,6 +439,7 @@ Truemail.validate('email@example.com') @smtp_safe_check=false, @validation_type_by_domain={}, @whitelisted_domains=[], + @whitelist_validation=false, @blacklisted_domains=[], @verifier_domain="example.com", @verifier_email="verifier@example.com">, @@ -440,6 +496,7 @@ Truemail.validate('email@example.com') @smtp_safe_check=true, @validation_type_by_domain={}, @whitelisted_domains=[], + @whitelist_validation=false, @blacklisted_domains=[], @verifier_domain="example.com", @verifier_email="verifier@example.com">, @@ -480,6 +537,7 @@ Truemail.validate('email@example.com') @smtp_safe_check=true, @validation_type_by_domain={}, @whitelisted_domains=[], + @whitelist_validation=false, @blacklisted_domains=[], @verifier_domain="example.com", @verifier_email="verifier@example.com">, @@ -553,10 +611,6 @@ end ``` --- -## ToDo - -Fail validations logger - ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/rubygarage/truemail. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. Please check the [open tikets](https://github.com/rubygarage/truemail/issues). Be shure to follow Contributor Code of Conduct below and our [Contributing Guidelines](CONTRIBUTING.md). diff --git a/lib/truemail/configuration.rb b/lib/truemail/configuration.rb index 11a89e9..0e3a839 100644 --- a/lib/truemail/configuration.rb +++ b/lib/truemail/configuration.rb @@ -19,21 +19,14 @@ class Configuration :whitelisted_domains, :blacklisted_domains - attr_accessor :smtp_safe_check + attr_accessor :whitelist_validation, :smtp_safe_check alias retry_count connection_attempts def initialize - @email_pattern = Truemail::RegexConstant::REGEX_EMAIL_PATTERN - @smtp_error_body_pattern = Truemail::RegexConstant::REGEX_SMTP_ERROR_BODY_PATTERN - @connection_timeout = Truemail::Configuration::DEFAULT_CONNECTION_TIMEOUT - @response_timeout = Truemail::Configuration::DEFAULT_RESPONSE_TIMEOUT - @connection_attempts = Truemail::Configuration::DEFAULT_CONNECTION_ATTEMPTS - @default_validation_type = Truemail::Configuration::DEFAULT_VALIDATION_TYPE - @validation_type_by_domain = {} - @whitelisted_domains = [] - @blacklisted_domains = [] - @smtp_safe_check = false + instance_initializer.each do |instace_variable, value| + instance_variable_set(:"@#{instace_variable}", value) + end end %i[email_pattern smtp_error_body_pattern].each do |method| @@ -84,6 +77,22 @@ def complete? private + def instance_initializer + { + email_pattern: Truemail::RegexConstant::REGEX_EMAIL_PATTERN, + smtp_error_body_pattern: Truemail::RegexConstant::REGEX_SMTP_ERROR_BODY_PATTERN, + connection_timeout: Truemail::Configuration::DEFAULT_CONNECTION_TIMEOUT, + response_timeout: Truemail::Configuration::DEFAULT_RESPONSE_TIMEOUT, + connection_attempts: Truemail::Configuration::DEFAULT_CONNECTION_ATTEMPTS, + default_validation_type: Truemail::Configuration::DEFAULT_VALIDATION_TYPE, + validation_type_by_domain: {}, + whitelisted_domains: [], + whitelist_validation: false, + blacklisted_domains: [], + smtp_safe_check: false + } + end + def raise_unless(argument_context, argument_name, condition) raise Truemail::ArgumentError.new(argument_context, argument_name) unless condition end diff --git a/lib/truemail/validate/domain_list_match.rb b/lib/truemail/validate/domain_list_match.rb index f205f0b..94787c2 100644 --- a/lib/truemail/validate/domain_list_match.rb +++ b/lib/truemail/validate/domain_list_match.rb @@ -6,8 +6,8 @@ class DomainListMatch < Truemail::Validate::Base ERROR = 'blacklisted email' def run - return success(true) if whitelisted_domain? - return unless blacklisted_domain? + return success(true) if whitelisted_domain? && !whitelist_validation? + return unless whitelist_validation_case? || blacklisted_domain? success(false) add_error(Truemail::Validate::DomainListMatch::ERROR) end @@ -19,7 +19,15 @@ def email_domain end def whitelisted_domain? - configuration.whitelisted_domains.include?(email_domain) + @whitelisted_domain ||= configuration.whitelisted_domains.include?(email_domain) + end + + def whitelist_validation? + configuration.whitelist_validation + end + + def whitelist_validation_case? + whitelist_validation? && !whitelisted_domain? end def blacklisted_domain? diff --git a/lib/truemail/version.rb b/lib/truemail/version.rb index ba3e1ff..33a790b 100644 --- a/lib/truemail/version.rb +++ b/lib/truemail/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Truemail - VERSION = '1.1.0' + VERSION = '1.2.0' end diff --git a/spec/support/shared_examples/has_attr_accessor.rb b/spec/support/shared_examples/has_attr_accessor.rb index 39efff9..abd7fc1 100644 --- a/spec/support/shared_examples/has_attr_accessor.rb +++ b/spec/support/shared_examples/has_attr_accessor.rb @@ -12,6 +12,7 @@ module Truemail connection_attempts default_validation_type whitelisted_domains + whitelist_validation blacklisted_domains smtp_safe_check ].each do |attribute| diff --git a/spec/support/shared_examples/sets_default_configuration.rb b/spec/support/shared_examples/sets_default_configuration.rb index 491b628..638e6d7 100644 --- a/spec/support/shared_examples/sets_default_configuration.rb +++ b/spec/support/shared_examples/sets_default_configuration.rb @@ -13,6 +13,7 @@ module Truemail expect(configuration_instance.default_validation_type).to eq(Truemail::Configuration::DEFAULT_VALIDATION_TYPE) expect(configuration_instance.validation_type_by_domain).to eq({}) expect(configuration_instance.whitelisted_domains).to eq([]) + expect(configuration_instance.whitelist_validation).to eq(false) expect(configuration_instance.blacklisted_domains).to eq([]) expect(configuration_instance.smtp_safe_check).to be(false) end diff --git a/spec/truemail/configuration_spec.rb b/spec/truemail/configuration_spec.rb index 6b6e77a..6a47cc1 100644 --- a/spec/truemail/configuration_spec.rb +++ b/spec/truemail/configuration_spec.rb @@ -38,6 +38,7 @@ expect(configuration_instance.default_validation_type).to eq(Truemail::Configuration::DEFAULT_VALIDATION_TYPE) expect(configuration_instance.validation_type_by_domain).to eq({}) expect(configuration_instance.whitelisted_domains).to eq([]) + expect(configuration_instance.whitelist_validation).to eq(false) expect(configuration_instance.blacklisted_domains).to eq([]) expect(configuration_instance.smtp_safe_check).to be(false) end @@ -57,6 +58,7 @@ .and not_change(configuration_instance, :default_validation_type) .and not_change(configuration_instance, :validation_type_by_domain) .and not_change(configuration_instance, :whitelisted_domains) + .and not_change(configuration_instance, :whitelist_validation) .and not_change(configuration_instance, :blacklisted_domains) .and not_change(configuration_instance, :smtp_safe_check) @@ -78,6 +80,7 @@ .and not_change(configuration_instance, :default_validation_type) .and not_change(configuration_instance, :validation_type_by_domain) .and not_change(configuration_instance, :whitelisted_domains) + .and not_change(configuration_instance, :whitelist_validation) .and not_change(configuration_instance, :blacklisted_domains) .and not_change(configuration_instance, :smtp_safe_check) @@ -99,6 +102,7 @@ .and not_change(configuration_instance, :default_validation_type) .and not_change(configuration_instance, :validation_type_by_domain) .and not_change(configuration_instance, :whitelisted_domains) + .and not_change(configuration_instance, :whitelist_validation) .and not_change(configuration_instance, :blacklisted_domains) .and not_change(configuration_instance, :smtp_safe_check) diff --git a/spec/truemail/validate/domain_list_match_spec.rb b/spec/truemail/validate/domain_list_match_spec.rb index 6595c49..b029112 100644 --- a/spec/truemail/validate/domain_list_match_spec.rb +++ b/spec/truemail/validate/domain_list_match_spec.rb @@ -8,29 +8,97 @@ let(:domain) { email[Truemail::RegexConstant::REGEX_DOMAIN_FROM_EMAIL, 1] } let(:result_instance) { Truemail::Validator::Result.new(email: email) } - context 'when email domain in white list' do - specify do - allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([domain]) - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) - expect { list_match_validator }.to change(result_instance, :success).from(nil).to(true) - end + before do + allow(Truemail) + .to receive_message_chain(:configuration, :whitelist_validation) + .and_return(whitelist_validation_condition) end - context 'when email domain in black list' do - specify do - allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([]) - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([domain]) - expect { list_match_validator } - .to change(result_instance, :success).from(nil).to(false) - .and change(result_instance, :errors).from({}).to({ domain_list_match: Truemail::Validate::DomainListMatch::ERROR }) + context 'when whitelist validation not configured' do + let(:whitelist_validation_condition) { false } + + context 'when email domain in white list' do + specify do + allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([domain]) + allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + expect { list_match_validator }.to change(result_instance, :success).from(nil).to(true) + end + end + + context 'when email domain in black list' do + specify do + allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([]) + allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([domain]) + expect { list_match_validator } + .to change(result_instance, :success).from(nil).to(false) + .and change(result_instance, :errors).from({}).to({ domain_list_match: Truemail::Validate::DomainListMatch::ERROR }) + end + end + + context 'when email domain exists on both lists' do + specify do + allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([domain]) + allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([domain]) + expect { list_match_validator }.to change(result_instance, :success).from(nil).to(true) + end + end + + context 'when email domain exists not on both lists' do + specify do + allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([]) + allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + expect { list_match_validator }.not_to change(result_instance, :success) + end end end - context 'when email domain not on both lists' do - specify do - allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([]) - allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) - expect { list_match_validator }.not_to change(result_instance, :success) + context 'when whitelist validation configured' do + let(:whitelist_validation_condition) { true } + + context 'when email domain whitelisted in configuration' do + before do + allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([domain]) + end + + context 'when email domain in white list' do + specify do + allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + expect { list_match_validator }.not_to change(result_instance, :success) + end + end + + context 'when email domain exists on both lists' do + specify do + allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([domain]) + expect { list_match_validator } + .to change(result_instance, :success).from(nil).to(false) + .and change(result_instance, :errors).from({}).to({ domain_list_match: Truemail::Validate::DomainListMatch::ERROR }) + end + end + end + + context 'when email domain not whitelisted in configuration' do + before do + allow(Truemail).to receive_message_chain(:configuration, :whitelisted_domains).and_return([]) + end + + context 'when email domain in black list' do + specify do + allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + expect { list_match_validator } + .to change(result_instance, :success).from(nil).to(false) + .and change(result_instance, :errors).from({}).to({ domain_list_match: Truemail::Validate::DomainListMatch::ERROR }) + end + end + + context 'when email domain not exists on both lists' do + specify do + allow(Truemail).to receive_message_chain(:configuration, :blacklisted_domains).and_return([]) + expect { list_match_validator } + .to change(result_instance, :success).from(nil).to(false) + .and change(result_instance, :errors).from({}).to({ domain_list_match: Truemail::Validate::DomainListMatch::ERROR }) + end + end end end end