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



169
170
171
172
173
174
175
176
# File 'app/services/email_settings_validator.rb', line 169

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

.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.



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

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: nil, 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.

Parameters:

  • domain (String) (defaults to: nil)
    • Used for HELO, should be the FQDN of the server sending the mail

    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.



70
71
72
73
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
136
137
138
# File 'app/services/email_settings_validator.rb', line 70

def self.validate_smtp(
  host:,
  port:,
  username:,
  password:,
  domain: nil,
  authentication: nil,
  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
    if enable_tls && enable_starttls_auto
      raise ArgumentError, "TLS and STARTTLS are mutually exclusive"
    end

    if username || password
      authentication = SmtpProviderOverrides.authentication_override(host) if authentication.nil?
      authentication = authentication.to_sym
      if !%i[plain login cram_md5].include?(authentication)
        raise ArgumentError, "Invalid authentication method. Must be plain, login, or cram_md5."
      end
    else
      authentication = nil
    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

    # Some SMTP servers have a higher delay to respond with errors
    # as a tarpit measure that slows down clients who are sending "bad" commands.
    # 10s is the minimum, we might need to increase this in the future.
    smtp.read_timeout = 10

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