truemail 1.7.1 → 2.0.0
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/.circleci/config.yml +1 -1
- data/.codeclimate.yml +1 -1
- data/.github/ISSUE_TEMPLATE.md +3 -3
- data/.github/PULL_REQUEST_TEMPLATE.md +1 -1
- data/.reek.yml +4 -0
- data/.rubocop.yml +177 -6
- data/CHANGELOG.md +149 -22
- data/Gemfile.lock +44 -38
- data/LICENSE.txt +1 -1
- data/README.md +162 -102
- data/lib/truemail.rb +10 -3
- data/lib/truemail/audit/base.rb +8 -0
- data/lib/truemail/audit/dns.rb +26 -0
- data/lib/truemail/audit/ip.rb +28 -0
- data/lib/truemail/audit/ptr.rb +8 -37
- data/lib/truemail/auditor.rb +7 -5
- data/lib/truemail/configuration.rb +8 -2
- data/lib/truemail/core.rb +28 -22
- data/lib/truemail/executor.rb +11 -0
- data/lib/truemail/log/serializer/auditor_json.rb +25 -0
- data/lib/truemail/log/serializer/base.rb +17 -42
- data/lib/truemail/log/serializer/validator_base.rb +45 -0
- data/lib/truemail/log/serializer/{json.rb → validator_json.rb} +1 -1
- data/lib/truemail/log/serializer/{text.rb → validator_text.rb} +2 -2
- data/lib/truemail/logger.rb +1 -1
- data/lib/truemail/validate/smtp/request.rb +2 -3
- data/lib/truemail/validator.rb +4 -4
- data/lib/truemail/version.rb +1 -1
- data/truemail.gemspec +17 -9
- metadata +49 -27
data/lib/truemail.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative 'truemail/core'
|
4
4
|
|
5
5
|
module Truemail
|
6
6
|
INCOMPLETE_CONFIG = 'verifier_email is required parameter'
|
7
7
|
NOT_CONFIGURED = 'use Truemail.configure before or pass custom configuration'
|
8
|
+
INVALID_TYPE = 'email should be a String'
|
8
9
|
|
9
10
|
class << self
|
10
11
|
def configuration(&block)
|
@@ -25,10 +26,12 @@ module Truemail
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def validate(email, custom_configuration: nil, **options)
|
29
|
+
check_argument_type(email)
|
28
30
|
Truemail::Validator.new(email, configuration: determine_configuration(custom_configuration), **options).run
|
29
31
|
end
|
30
32
|
|
31
33
|
def valid?(email, **options)
|
34
|
+
check_argument_type(email)
|
32
35
|
validate(email, **options).result.valid?
|
33
36
|
end
|
34
37
|
|
@@ -38,8 +41,12 @@ module Truemail
|
|
38
41
|
|
39
42
|
private
|
40
43
|
|
41
|
-
def raise_unless(condition, message)
|
42
|
-
raise
|
44
|
+
def raise_unless(condition, message, error_class = Truemail::ConfigurationError)
|
45
|
+
raise error_class, message unless condition
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_argument_type(argument)
|
49
|
+
raise_unless(argument.is_a?(String), Truemail::INVALID_TYPE, Truemail::TypeError)
|
43
50
|
end
|
44
51
|
|
45
52
|
def determine_configuration(custom_configuration)
|
data/lib/truemail/audit/base.rb
CHANGED
@@ -3,12 +3,20 @@
|
|
3
3
|
module Truemail
|
4
4
|
module Audit
|
5
5
|
class Base < Truemail::Worker
|
6
|
+
require 'net/http'
|
7
|
+
require 'ipaddr'
|
8
|
+
require 'resolv'
|
9
|
+
|
6
10
|
private
|
7
11
|
|
8
12
|
def add_warning(message)
|
9
13
|
result.warnings[self.class.name.split('::').last.downcase.to_sym] = message
|
10
14
|
end
|
11
15
|
|
16
|
+
def current_host_ip
|
17
|
+
result.current_host_ip
|
18
|
+
end
|
19
|
+
|
12
20
|
def configuration
|
13
21
|
result.configuration
|
14
22
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Truemail
|
4
|
+
module Audit
|
5
|
+
class Dns < Truemail::Audit::Base
|
6
|
+
VERIFIER_DOMAIN_NOT_REFER = 'A-record of verifier domain not refers to current host ip address'
|
7
|
+
|
8
|
+
def run
|
9
|
+
return if verifier_domain_refer_to_current_host_ip?
|
10
|
+
add_warning(Truemail::Audit::Dns::VERIFIER_DOMAIN_NOT_REFER)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def a_record
|
16
|
+
Truemail::Wrapper.call(configuration: configuration) do
|
17
|
+
Resolv::DNS.new.getaddress(verifier_domain).to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def verifier_domain_refer_to_current_host_ip?
|
22
|
+
a_record.eql?(current_host_ip)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Truemail
|
4
|
+
module Audit
|
5
|
+
class Ip < Truemail::Audit::Base
|
6
|
+
GET_MY_IP_URL = 'https://api.ipify.org'
|
7
|
+
IPIFY_ERROR = 'impossible to detect current host address via third party service'
|
8
|
+
|
9
|
+
def run
|
10
|
+
return add_warning(Truemail::Audit::Ip::IPIFY_ERROR) unless detect_current_host_ip
|
11
|
+
Truemail::Audit::Dns.check(result)
|
12
|
+
Truemail::Audit::Ptr.check(result)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def detect_ip_via_ipify
|
18
|
+
Net::HTTP.get(URI(Truemail::Audit::Ip::GET_MY_IP_URL))
|
19
|
+
end
|
20
|
+
|
21
|
+
def detect_current_host_ip
|
22
|
+
result.current_host_ip = Truemail::Wrapper.call(configuration: configuration) do
|
23
|
+
detect_ip_via_ipify
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/truemail/audit/ptr.rb
CHANGED
@@ -3,38 +3,19 @@
|
|
3
3
|
module Truemail
|
4
4
|
module Audit
|
5
5
|
class Ptr < Truemail::Audit::Base
|
6
|
-
|
7
|
-
|
8
|
-
require 'resolv'
|
9
|
-
|
10
|
-
GET_MY_IP_URL = 'https://api.ipify.org'
|
11
|
-
IPIFY_ERROR = 'impossible to detect current host address via third party service'
|
12
|
-
PTR_NOT_FOUND = 'ptr record for current host address was not found'
|
13
|
-
PTR_NOT_REFER = 'ptr record does not reference to current verifier domain'
|
14
|
-
VERIFIER_DOMAIN_NOT_REFER = 'a record of verifier domain not refers to current host address'
|
6
|
+
PTR_NOT_FOUND = 'PTR-record for current host ip address was not found'
|
7
|
+
PTR_NOT_REFER = 'PTR-record does not reference to current verifier domain'
|
15
8
|
|
16
9
|
def run
|
17
|
-
return
|
18
|
-
return if
|
19
|
-
|
20
|
-
return if verifier_domain_refer_to_current_host_address?
|
21
|
-
add_warning(Truemail::Audit::Ptr::VERIFIER_DOMAIN_NOT_REFER)
|
10
|
+
return add_warning(Truemail::Audit::Ptr::PTR_NOT_FOUND) if ptr_records.empty?
|
11
|
+
return if ptr_refer_to_verifier_domain?
|
12
|
+
add_warning(Truemail::Audit::Ptr::PTR_NOT_REFER)
|
22
13
|
end
|
23
14
|
|
24
15
|
private
|
25
16
|
|
26
|
-
def detect_ip_via_ipify
|
27
|
-
Net::HTTP.get(URI(Truemail::Audit::Ptr::GET_MY_IP_URL))
|
28
|
-
end
|
29
|
-
|
30
|
-
def current_host_address
|
31
|
-
@current_host_address ||= Truemail::Wrapper.call(configuration: configuration) do
|
32
|
-
IPAddr.new(detect_ip_via_ipify)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
17
|
def current_host_reverse_lookup
|
37
|
-
|
18
|
+
IPAddr.new(current_host_ip).reverse
|
38
19
|
end
|
39
20
|
|
40
21
|
def ptr_records
|
@@ -45,18 +26,8 @@ module Truemail
|
|
45
26
|
end || []
|
46
27
|
end
|
47
28
|
|
48
|
-
def
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
def a_record
|
53
|
-
Truemail::Wrapper.call(configuration: configuration) do
|
54
|
-
Resolv::DNS.new.getaddress(verifier_domain).to_s
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def verifier_domain_refer_to_current_host_address?
|
59
|
-
a_record.eql?(current_host_address.to_s)
|
29
|
+
def ptr_refer_to_verifier_domain?
|
30
|
+
ptr_records.include?(verifier_domain)
|
60
31
|
end
|
61
32
|
end
|
62
33
|
end
|
data/lib/truemail/auditor.rb
CHANGED
@@ -1,22 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Truemail
|
4
|
-
class Auditor
|
5
|
-
Result = Struct.new(:warnings, :configuration, keyword_init: true) do
|
4
|
+
class Auditor < Truemail::Executor
|
5
|
+
Result = Struct.new(:current_host_ip, :warnings, :configuration, keyword_init: true) do
|
6
6
|
def initialize(warnings: {}, **args)
|
7
7
|
super
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
attr_reader :result
|
12
|
-
|
13
11
|
def initialize(configuration:)
|
14
12
|
@result = Truemail::Auditor::Result.new(configuration: configuration)
|
15
13
|
end
|
16
14
|
|
17
15
|
def run
|
18
|
-
Truemail::Audit::
|
16
|
+
Truemail::Audit::Ip.check(result)
|
19
17
|
self
|
20
18
|
end
|
19
|
+
|
20
|
+
def as_json
|
21
|
+
Truemail::Log::Serializer::AuditorJson.call(self)
|
22
|
+
end
|
21
23
|
end
|
22
24
|
end
|
@@ -6,6 +6,7 @@ module Truemail
|
|
6
6
|
DEFAULT_RESPONSE_TIMEOUT = 2
|
7
7
|
DEFAULT_CONNECTION_ATTEMPTS = 2
|
8
8
|
DEFAULT_VALIDATION_TYPE = :smtp
|
9
|
+
DEFAULT_LOGGER_OPTIONS = { tracking_event: :error, stdout: false, log_absolute_path: nil }.freeze
|
9
10
|
|
10
11
|
attr_reader :email_pattern,
|
11
12
|
:smtp_error_body_pattern,
|
@@ -71,7 +72,8 @@ module Truemail
|
|
71
72
|
end
|
72
73
|
end
|
73
74
|
|
74
|
-
def logger=(
|
75
|
+
def logger=(options)
|
76
|
+
tracking_event, stdout, log_absolute_path = logger_options(options)
|
75
77
|
valid_event = Truemail::Log::Event::TRACKING_EVENTS.key?(tracking_event)
|
76
78
|
stdout_only = stdout && log_absolute_path.nil?
|
77
79
|
file_only = log_absolute_path.is_a?(String)
|
@@ -109,7 +111,7 @@ module Truemail
|
|
109
111
|
end
|
110
112
|
|
111
113
|
def validate_arguments(argument, method)
|
112
|
-
constant = Truemail::RegexConstant.const_get("regex_#{method[/\A.+_(.+)
|
114
|
+
constant = Truemail::RegexConstant.const_get("regex_#{method[/\A.+_(.+)=\z/, 1]}_pattern".upcase)
|
113
115
|
raise_unless(argument, method, constant.match?(argument.to_s))
|
114
116
|
end
|
115
117
|
|
@@ -140,5 +142,9 @@ module Truemail
|
|
140
142
|
check_validation_type(validation_type)
|
141
143
|
end
|
142
144
|
end
|
145
|
+
|
146
|
+
def logger_options(current_options)
|
147
|
+
Truemail::Configuration::DEFAULT_LOGGER_OPTIONS.merge(current_options).values
|
148
|
+
end
|
143
149
|
end
|
144
150
|
end
|
data/lib/truemail/core.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Truemail
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
require_relative '../truemail/version'
|
5
|
+
require_relative '../truemail/configuration'
|
6
|
+
require_relative '../truemail/worker'
|
7
|
+
require_relative '../truemail/executor'
|
8
|
+
require_relative '../truemail/wrapper'
|
9
|
+
require_relative '../truemail/auditor'
|
10
|
+
require_relative '../truemail/validator'
|
11
|
+
require_relative '../truemail/logger'
|
11
12
|
|
12
13
|
ConfigurationError = Class.new(StandardError)
|
14
|
+
TypeError = Class.new(StandardError)
|
13
15
|
|
14
16
|
ArgumentError = Class.new(StandardError) do
|
15
17
|
def initialize(arg_value, arg_name)
|
@@ -29,32 +31,36 @@ module Truemail
|
|
29
31
|
end
|
30
32
|
|
31
33
|
module RegexConstant
|
32
|
-
REGEX_DOMAIN = /[\p{L}0-9]+([
|
33
|
-
REGEX_EMAIL_PATTERN = /(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w
|
34
|
+
REGEX_DOMAIN = /[\p{L}0-9]+([\-.]{1}[\p{L}0-9]+)*\.\p{L}{2,63}/i.freeze
|
35
|
+
REGEX_EMAIL_PATTERN = /(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-|.|+]*)@(#{REGEX_DOMAIN})\z)/.freeze
|
34
36
|
REGEX_DOMAIN_PATTERN = /(?=\A.{4,255}\z)(\A#{REGEX_DOMAIN}\z)/.freeze
|
35
37
|
REGEX_DOMAIN_FROM_EMAIL = /\A.+@(.+)\z/.freeze
|
36
38
|
REGEX_SMTP_ERROR_BODY_PATTERN = /(?=.*550)(?=.*(user|account|customer|mailbox)).*/i.freeze
|
37
39
|
end
|
38
40
|
|
39
41
|
module Audit
|
40
|
-
|
41
|
-
|
42
|
+
require_relative '../truemail/audit/base'
|
43
|
+
require_relative '../truemail/audit/ip'
|
44
|
+
require_relative '../truemail/audit/dns'
|
45
|
+
require_relative '../truemail/audit/ptr'
|
42
46
|
end
|
43
47
|
|
44
48
|
module Validate
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
require_relative '../truemail/validate/base'
|
50
|
+
require_relative '../truemail/validate/domain_list_match'
|
51
|
+
require_relative '../truemail/validate/regex'
|
52
|
+
require_relative '../truemail/validate/mx'
|
53
|
+
require_relative '../truemail/validate/smtp'
|
54
|
+
require_relative '../truemail/validate/smtp/response'
|
55
|
+
require_relative '../truemail/validate/smtp/request'
|
52
56
|
end
|
53
57
|
|
54
58
|
module Log
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
+
require_relative '../truemail/log/event'
|
60
|
+
require_relative '../truemail/log/serializer/base'
|
61
|
+
require_relative '../truemail/log/serializer/auditor_json'
|
62
|
+
require_relative '../truemail/log/serializer/validator_base'
|
63
|
+
require_relative '../truemail/log/serializer/validator_text'
|
64
|
+
require_relative '../truemail/log/serializer/validator_json'
|
59
65
|
end
|
60
66
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Truemail
|
4
|
+
module Log
|
5
|
+
module Serializer
|
6
|
+
class AuditorJson < Truemail::Log::Serializer::Base
|
7
|
+
def serialize
|
8
|
+
result.to_json
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def result
|
14
|
+
@result ||=
|
15
|
+
{
|
16
|
+
date: Time.now,
|
17
|
+
current_host_ip: executor_result.current_host_ip,
|
18
|
+
warnings: warnings(executor_result.warnings),
|
19
|
+
configuration: configuration
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -4,47 +4,35 @@ module Truemail
|
|
4
4
|
module Log
|
5
5
|
module Serializer
|
6
6
|
class Base
|
7
|
+
require 'json'
|
8
|
+
|
7
9
|
DEFAULT_GEM_VALUE = 'default gem value'
|
8
10
|
|
9
|
-
def self.call(
|
10
|
-
new(
|
11
|
+
def self.call(executor_instance)
|
12
|
+
new(executor_instance).serialize
|
11
13
|
end
|
12
14
|
|
13
|
-
def initialize(
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@validation_configuration = validation_result.configuration
|
15
|
+
def initialize(executor_instance)
|
16
|
+
@executor_result = executor_instance.result
|
17
|
+
@executor_configuration = executor_result.configuration
|
17
18
|
end
|
18
19
|
|
19
20
|
def serialize; end
|
20
21
|
|
21
22
|
private
|
22
23
|
|
23
|
-
attr_reader :
|
24
|
+
attr_reader :executor_result, :executor_configuration
|
24
25
|
|
25
|
-
def errors
|
26
|
-
|
27
|
-
|
28
|
-
validation_errors
|
26
|
+
def errors(executor_result_target)
|
27
|
+
return if executor_result_target.empty?
|
28
|
+
executor_result_target
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
validation_smtp_debug = validation_result.smtp_debug
|
33
|
-
return unless validation_smtp_debug
|
34
|
-
validation_smtp_debug.map do |smtp_request|
|
35
|
-
smtp_response = smtp_request.response
|
36
|
-
{
|
37
|
-
mail_host: smtp_request.host,
|
38
|
-
port_opened: smtp_response.port_opened,
|
39
|
-
connection: smtp_response.connection,
|
40
|
-
errors: smtp_response.errors
|
41
|
-
}
|
42
|
-
end
|
43
|
-
end
|
31
|
+
alias warnings errors
|
44
32
|
|
45
33
|
%i[validation_type_by_domain whitelisted_domains blacklisted_domains].each do |method|
|
46
34
|
define_method(method) do
|
47
|
-
value =
|
35
|
+
value = executor_configuration.public_send(method)
|
48
36
|
return if value.empty?
|
49
37
|
value
|
50
38
|
end
|
@@ -52,7 +40,7 @@ module Truemail
|
|
52
40
|
|
53
41
|
%i[email_pattern smtp_error_body_pattern].each do |method|
|
54
42
|
define_method(method) do
|
55
|
-
value =
|
43
|
+
value = executor_configuration.public_send(method)
|
56
44
|
default_pattern = Truemail::RegexConstant.const_get(
|
57
45
|
(method.eql?(:email_pattern) ? :regex_email_pattern : :regex_smtp_error_body_pattern).upcase
|
58
46
|
)
|
@@ -64,28 +52,15 @@ module Truemail
|
|
64
52
|
def configuration
|
65
53
|
{
|
66
54
|
validation_type_by_domain: validation_type_by_domain,
|
67
|
-
whitelist_validation:
|
55
|
+
whitelist_validation: executor_configuration.whitelist_validation,
|
68
56
|
whitelisted_domains: whitelisted_domains,
|
69
57
|
blacklisted_domains: blacklisted_domains,
|
70
|
-
not_rfc_mx_lookup_flow:
|
71
|
-
smtp_safe_check:
|
58
|
+
not_rfc_mx_lookup_flow: executor_configuration.not_rfc_mx_lookup_flow,
|
59
|
+
smtp_safe_check: executor_configuration.smtp_safe_check,
|
72
60
|
email_pattern: email_pattern,
|
73
61
|
smtp_error_body_pattern: smtp_error_body_pattern
|
74
62
|
}
|
75
63
|
end
|
76
|
-
|
77
|
-
def result
|
78
|
-
@result ||=
|
79
|
-
{
|
80
|
-
date: Time.now,
|
81
|
-
email: validation_result.email,
|
82
|
-
validation_type: validation_type,
|
83
|
-
success: validation_result.success,
|
84
|
-
errors: errors,
|
85
|
-
smtp_debug: smtp_debug,
|
86
|
-
configuration: configuration
|
87
|
-
}
|
88
|
-
end
|
89
64
|
end
|
90
65
|
end
|
91
66
|
end
|