Skip to content

Commit

Permalink
Add forward-compatible auth kwarg to start
Browse files Browse the repository at this point in the history
The pre-existing `start` keyword params will retain their existing
behavior as positional `user` and `secret` args to `#authenticate`.
This adds a new `auth` keyword` param that is used to pass any
arbitratry keyword params to `#authenticate`.

The current #start API can't fully support every SASL mechanism.  Some
SASL mechanisms do not require a +username+ (OAUTHBEARER, EXTERNAL,
ANONYMOUS) or a +secret+ (EXTERNAL, ANONYMOUS).  Many SASL mechanisms
should oe able to take extra arguments (e.g: `authzid` for many
mechanisms, `warn_deprecations` for deprecated mechanisms,
`min_iterations` for SCRAM-*, `anonymous_message` for ANONYMOUS), and so
on.  And, although it is convenient to use +username+ as an alias for
+authcid+ or +authzid+ and +secret+ as an alias for +password+ or
+oauth2_token+, it can also be useful to have keyword parameters that
keep stable semantics across many different mechanisms.
  • Loading branch information
nevans committed Oct 11, 2023
1 parent 6cf07a4 commit 628f113
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 10 deletions.
30 changes: 20 additions & 10 deletions lib/net/smtp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ def debug_output=(arg)

#
# :call-seq:
# start(address, port = nil, helo: 'localhost', auth: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... }
# start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... }
# start(address, port = nil, helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... }
#
Expand Down Expand Up @@ -502,7 +503,8 @@ def debug_output=(arg)
# identity (depending on +authtype+); +secret+ or +password+ is your
# password or other authentication token; and +authtype+ is the SASL
# authentication mechanism. These will be used as regular positional
# arguments to #authenticate. See the discussion of SMTP Authentication
# arguments to #authenticate, and +auth+ is a hash of arbitrary keyword
# arguments for #authenticate. See the discussion of SMTP Authentication
# in the overview notes.
#
# === Errors
Expand All @@ -520,7 +522,7 @@ def debug_output=(arg)
#
def SMTP.start(address, port = nil, *args, helo: nil,
user: nil, secret: nil, password: nil, authtype: nil,
username: nil,
username: nil, auth: nil,
tls: false, starttls: :auto,
tls_verify: true, tls_hostname: nil, ssl_context_params: nil,
&block)
Expand All @@ -529,7 +531,7 @@ def SMTP.start(address, port = nil, *args, helo: nil,
user ||= username || args[1]
secret ||= password || args[2]
authtype ||= args[3]
new(address, port, tls: tls, starttls: starttls, tls_verify: tls_verify, tls_hostname: tls_hostname, ssl_context_params: ssl_context_params).start(helo: helo, user: user, secret: secret, authtype: authtype, &block)
new(address, port, tls: tls, starttls: starttls, tls_verify: tls_verify, tls_hostname: tls_hostname, ssl_context_params: ssl_context_params).start(helo: helo, user: user, secret: secret, authtype: authtype, auth: auth, &block)
end

# +true+ if the SMTP session has been started.
Expand All @@ -541,6 +543,7 @@ def started?
# :call-seq:
# start(helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... }
# start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... }
# start(helo = 'localhost', auth: {type: nil, **auth_kwargs}) { |smtp| ... }
#
# Opens a TCP connection and starts the SMTP session.
#
Expand All @@ -554,7 +557,8 @@ def started?
# identity (depending on +authtype+); +secret+ or +password+ is your
# password or other authentication token; and +authtype+ is the SASL
# authentication mechanism. These will be used as regular positional
# arguments to #authenticate. See the discussion of SMTP Authentication
# arguments to #authenticate, and +auth+ is a hash of arbitrary keyword
# arguments for #authenticate. See the discussion of SMTP Authentication
# in the overview notes.
#
# === Block Usage
Expand Down Expand Up @@ -594,13 +598,15 @@ def started?
# * Net::ReadTimeout
# * IOError
#
def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil,
username: nil)
def start(*args, helo: nil,
user: nil, username: nil, secret: nil, password: nil,
authtype: nil, auth: nil)
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..4)" if args.size > 4
helo ||= args[0] || 'localhost'
user ||= username || args[1]
secret ||= password || args[2]
authtype ||= args[3]
auth ||= {}
if defined?(OpenSSL::VERSION)
ssl_context_params = @ssl_context_params || {}
unless ssl_context_params.has_key?(:verify_mode)
Expand All @@ -615,13 +621,13 @@ def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil
end
if block_given?
begin
do_start helo, user, secret, authtype
do_start helo, user, secret, authtype, **auth
return yield(self)
ensure
do_finish
end
else
do_start helo, user, secret, authtype
do_start helo, user, secret, authtype, **auth
return self
end
end
Expand All @@ -639,7 +645,7 @@ def tcp_socket(address, port)
TCPSocket.open address, port
end

def do_start(helo_domain, user, secret, authtype)
def do_start(helo_domain, user, secret, authtype, **auth)
raise IOError, 'SMTP session already started' if @started
if user or secret
check_auth_method(authtype || DEFAULT_AUTH_TYPE)
Expand All @@ -661,7 +667,11 @@ def do_start(helo_domain, user, secret, authtype)
# helo response may be different after STARTTLS
do_helo helo_domain
end
authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user
if user or secret
authenticate(user, secret, authtype, **auth)
elsif authtype or auth.any?
authenticate(authtype, **auth)
end
@started = true
ensure
unless @started
Expand Down
9 changes: 9 additions & 0 deletions test/net/smtp/test_smtp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,15 @@ def test_start_auth_plain
port = fake_server_start(auth: 'plain')
Net::SMTP.start('localhost', port, user: 'account', password: 'password', authtype: :plain){}

port = fake_server_start(auth: 'plain')
Net::SMTP.start('localhost', port, authtype: "PLAIN",
auth: {username: 'account', password: 'password'}){}

port = fake_server_start(auth: 'plain')
Net::SMTP.start('localhost', port, auth: {username: 'account',
password: 'password',
type: :plain}){}

port = fake_server_start(auth: 'plain')
assert_raise Net::SMTPAuthenticationError do
Net::SMTP.start('localhost', port, user: 'account', password: 'invalid', authtype: :plain){}
Expand Down

0 comments on commit 628f113

Please sign in to comment.