Class: EmailSettingsValidator

Inherits:
Object
  • Object
show all
Defined in:
app/services/email_settings_validator.rb

Overview

Usage:

begin

EmailSettingsValidator.validate_imap(host: "imap.test.com", port: 999, username: "[email protected]", password: "password")

# or for specific host preset
EmailSettingsValidator.validate_imap(**{ username: "[email protected]", password: "test" }.merge(Email.gmail_imap_settings))

rescue *EmailSettingsExceptionHandler::EXPECTED_EXCEPTIONS => err

EmailSettingsExceptionHandler.friendly_exception_message(err, host)

end

Class Method Summary collapse

Class Method Details

.log_and_raise(err, debug) ⇒ Object



166
167
168
169
170
171
172
173
# File 'app/services/email_settings_validator.rb', line 166

def self.log_and_raise(err, debug)
  if debug
    Rails.logger.warn(
      "[EmailSettingsValidator] Error encountered when validating email settings: #{err.message} #{err.backtrace.join("\n")}",
    )
  end
  raise err
end

.provider_specific_ssl_overrides(host, port, enable_tls, enable_starttls_auto) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'app/services/email_settings_validator.rb', line 175

def self.provider_specific_ssl_overrides(host, port, enable_tls, enable_starttls_auto)
  # Gmail acts weirdly if you do not use the correct combinations of
  # TLS settings based on the port, we clean these up here for the user.
  if host == "smtp.gmail.com"
    if port.to_i == 587
      enable_starttls_auto = true
      enable_tls = false
    elsif port.to_i == 465
      enable_starttls_auto = false
      enable_tls = true
    end
  end

  [port, enable_tls, enable_starttls_auto]
end

.validate_as_user(user, protocol, **kwargs) ⇒ Object



19
20
21
22
23
# File 'app/services/email_settings_validator.rb', line 19

def self.validate_as_user(user, protocol, **kwargs)
  DistributedMutex.synchronize("validate_#{protocol}_#{user.id}", validity: 10) do
    self.public_send("validate_#{protocol}", **kwargs)
  end
end

.validate_imap(host:, port:, username:, password:, open_timeout: 5, ssl: true, debug: false) ⇒ Object

Attempts to login, logout, and disconnect an IMAP session and if that raises an error then it is assumed the credentials or some other settings are wrong.

Parameters:

  • debug (Boolean) (defaults to: false)
    • When set to true, any errors will be logged at a warning

    level before being re-raised.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'app/services/email_settings_validator.rb', line 143

def self.validate_imap(
  host:,
  port:,
  username:,
  password:,
  open_timeout: 5,
  ssl: true,
  debug: false
)
  begin
    imap = Net::IMAP.new(host, port: port, ssl: ssl, open_timeout: open_timeout)
    imap.(username, password)
    begin
      imap.logout
    rescue StandardError
      nil
    end
    imap.disconnect
  rescue => err
    log_and_raise(err, debug)
  end
end

.validate_pop3(host:, port:, username:, password:, ssl: SiteSetting.pop3_polling_ssl, openssl_verify: SiteSetting.pop3_polling_openssl_verify, debug: Rails.env.development?) ⇒ Object

Attempts to authenticate and disconnect a POP3 session and if that raises an error then it is assumed the credentials or some other settings are wrong.

Parameters:

  • debug (Boolean) (defaults to: Rails.env.development?)
    • When set to true, any errors will be logged at a warning

    level before being re-raised.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'app/services/email_settings_validator.rb', line 31

def self.validate_pop3(
  host:,
  port:,
  username:,
  password:,
  ssl: SiteSetting.pop3_polling_ssl,
  openssl_verify: SiteSetting.pop3_polling_openssl_verify,
  debug: Rails.env.development?
)
  begin
    pop3 = Net::POP3.new(host, port)

    # Note that we do not allow which verification mode to be specified
    # like we do for SMTP, we just pick TLS1_2 if the SSL and openSSL verify
    # options have been enabled.
    if ssl
      if openssl_verify
        pop3.enable_ssl(max_version: OpenSSL::SSL::TLS1_2_VERSION)
      else
        pop3.enable_ssl(OpenSSL::SSL::VERIFY_NONE)
      end
    end

    # This disconnects itself, unlike SMTP and IMAP.
    pop3.auth_only(username, password)
  rescue => err
    log_and_raise(err, debug)
  end
end

.validate_smtp(host:, port:, username:, password:, domain: nil, authentication: GlobalSetting.smtp_authentication, enable_starttls_auto: GlobalSetting.smtp_enable_start_tls, enable_tls: GlobalSetting.smtp_force_tls, openssl_verify_mode: GlobalSetting.smtp_openssl_verify_mode, debug: Rails.env.development?) ⇒ Object

Attempts to start an SMTP session and if that raises an error then it is assumed the credentials or other settings are wrong.

For Gmail, the port should be 587, enable_starttls_auto should be true, and enable_tls should be false.

Parameters:

  • domain (String) (defaults to: nil)
    • Used for HELO, will be the email sender’s domain, so often

    will just be the host e.g. the domain for [email protected] is gmail.com. localhost can be used in development mode. See datatracker.ietf.org/doc/html/rfc788#section-4

  • debug (Boolean) (defaults to: Rails.env.development?)
    • When set to true, any errors will be logged at a warning

    level before being re-raised.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'app/services/email_settings_validator.rb', line 74

def self.validate_smtp(
  host:,
  port:,
  username:,
  password:,
  domain: nil,
  authentication: GlobalSetting.smtp_authentication,
  enable_starttls_auto: GlobalSetting.smtp_enable_start_tls,
  enable_tls: GlobalSetting.smtp_force_tls,
  openssl_verify_mode: GlobalSetting.smtp_openssl_verify_mode,
  debug: Rails.env.development?
)
  begin
    port, enable_tls, enable_starttls_auto =
      provider_specific_ssl_overrides(host, port, enable_tls, enable_starttls_auto)

    if enable_tls && enable_starttls_auto
      raise ArgumentError, "TLS and STARTTLS are mutually exclusive"
    end

    if !%i[plain login cram_md5].include?(authentication.to_sym)
      raise ArgumentError, "Invalid authentication method. Must be plain, login, or cram_md5."
    end

    if domain.blank?
      if Rails.env.development?
        domain = "localhost"
      else
        # Because we are using the SMTP settings here to send emails,
        # the domain should just be the TLD of the host.
        domain = MiniSuffix.domain(host)
      end
    end

    smtp = Net::SMTP.new(host, port)

    # These SSL options are cribbed from the Mail gem, which is used internally
    # by ActionMailer. Unfortunately the mail gem hides this setup in private
    # methods, e.g. https://github.com/mikel/mail/blob/master/lib/mail/network/delivery_methods/smtp.rb#L112-L147
    #
    # Relying on the GlobalSetting options is a good idea here.
    #
    # For specific use cases, options should be passed in from higher up. For example
    # Gmail needs either port 465 and tls enabled, or port 587 and starttls_auto.
    if openssl_verify_mode.kind_of?(String)
      openssl_verify_mode = OpenSSL::SSL.const_get("VERIFY_#{openssl_verify_mode.upcase}")
    end
    ssl_context = Net::SMTP.default_ssl_context
    ssl_context.verify_mode = openssl_verify_mode if openssl_verify_mode

    smtp.enable_starttls_auto(ssl_context) if enable_starttls_auto
    smtp.enable_tls(ssl_context) if enable_tls

    smtp.open_timeout = 5
    smtp.read_timeout = 5

    smtp.start(domain, username, password, authentication.to_sym)
    smtp.finish
  rescue => err
    log_and_raise(err, debug)
  end
end