Skip to content

Commit 628f113

Browse files
committed
Add forward-compatible auth kwarg to start
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.
1 parent 6cf07a4 commit 628f113

2 files changed

Lines changed: 29 additions & 10 deletions

File tree

lib/net/smtp.rb

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ def debug_output=(arg)
452452

453453
#
454454
# :call-seq:
455+
# start(address, port = nil, helo: 'localhost', auth: nil, tls: false, starttls: :auto, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... }
455456
# 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| ... }
456457
# start(address, port = nil, helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... }
457458
#
@@ -502,7 +503,8 @@ def debug_output=(arg)
502503
# identity (depending on +authtype+); +secret+ or +password+ is your
503504
# password or other authentication token; and +authtype+ is the SASL
504505
# authentication mechanism. These will be used as regular positional
505-
# arguments to #authenticate. See the discussion of SMTP Authentication
506+
# arguments to #authenticate, and +auth+ is a hash of arbitrary keyword
507+
# arguments for #authenticate. See the discussion of SMTP Authentication
506508
# in the overview notes.
507509
#
508510
# === Errors
@@ -520,7 +522,7 @@ def debug_output=(arg)
520522
#
521523
def SMTP.start(address, port = nil, *args, helo: nil,
522524
user: nil, secret: nil, password: nil, authtype: nil,
523-
username: nil,
525+
username: nil, auth: nil,
524526
tls: false, starttls: :auto,
525527
tls_verify: true, tls_hostname: nil, ssl_context_params: nil,
526528
&block)
@@ -529,7 +531,7 @@ def SMTP.start(address, port = nil, *args, helo: nil,
529531
user ||= username || args[1]
530532
secret ||= password || args[2]
531533
authtype ||= args[3]
532-
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)
534+
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)
533535
end
534536

535537
# +true+ if the SMTP session has been started.
@@ -541,6 +543,7 @@ def started?
541543
# :call-seq:
542544
# start(helo: 'localhost', user: nil, secret: nil, authtype: nil) { |smtp| ... }
543545
# start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... }
546+
# start(helo = 'localhost', auth: {type: nil, **auth_kwargs}) { |smtp| ... }
544547
#
545548
# Opens a TCP connection and starts the SMTP session.
546549
#
@@ -554,7 +557,8 @@ def started?
554557
# identity (depending on +authtype+); +secret+ or +password+ is your
555558
# password or other authentication token; and +authtype+ is the SASL
556559
# authentication mechanism. These will be used as regular positional
557-
# arguments to #authenticate. See the discussion of SMTP Authentication
560+
# arguments to #authenticate, and +auth+ is a hash of arbitrary keyword
561+
# arguments for #authenticate. See the discussion of SMTP Authentication
558562
# in the overview notes.
559563
#
560564
# === Block Usage
@@ -594,13 +598,15 @@ def started?
594598
# * Net::ReadTimeout
595599
# * IOError
596600
#
597-
def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil,
598-
username: nil)
601+
def start(*args, helo: nil,
602+
user: nil, username: nil, secret: nil, password: nil,
603+
authtype: nil, auth: nil)
599604
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..4)" if args.size > 4
600605
helo ||= args[0] || 'localhost'
601606
user ||= username || args[1]
602607
secret ||= password || args[2]
603608
authtype ||= args[3]
609+
auth ||= {}
604610
if defined?(OpenSSL::VERSION)
605611
ssl_context_params = @ssl_context_params || {}
606612
unless ssl_context_params.has_key?(:verify_mode)
@@ -615,13 +621,13 @@ def start(*args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil
615621
end
616622
if block_given?
617623
begin
618-
do_start helo, user, secret, authtype
624+
do_start helo, user, secret, authtype, **auth
619625
return yield(self)
620626
ensure
621627
do_finish
622628
end
623629
else
624-
do_start helo, user, secret, authtype
630+
do_start helo, user, secret, authtype, **auth
625631
return self
626632
end
627633
end
@@ -639,7 +645,7 @@ def tcp_socket(address, port)
639645
TCPSocket.open address, port
640646
end
641647

642-
def do_start(helo_domain, user, secret, authtype)
648+
def do_start(helo_domain, user, secret, authtype, **auth)
643649
raise IOError, 'SMTP session already started' if @started
644650
if user or secret
645651
check_auth_method(authtype || DEFAULT_AUTH_TYPE)
@@ -661,7 +667,11 @@ def do_start(helo_domain, user, secret, authtype)
661667
# helo response may be different after STARTTLS
662668
do_helo helo_domain
663669
end
664-
authenticate user, secret, (authtype || DEFAULT_AUTH_TYPE) if user
670+
if user or secret
671+
authenticate(user, secret, authtype, **auth)
672+
elsif authtype or auth.any?
673+
authenticate(authtype, **auth)
674+
end
665675
@started = true
666676
ensure
667677
unless @started

test/net/smtp/test_smtp.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,15 @@ def test_start_auth_plain
480480
port = fake_server_start(auth: 'plain')
481481
Net::SMTP.start('localhost', port, user: 'account', password: 'password', authtype: :plain){}
482482

483+
port = fake_server_start(auth: 'plain')
484+
Net::SMTP.start('localhost', port, authtype: "PLAIN",
485+
auth: {username: 'account', password: 'password'}){}
486+
487+
port = fake_server_start(auth: 'plain')
488+
Net::SMTP.start('localhost', port, auth: {username: 'account',
489+
password: 'password',
490+
type: :plain}){}
491+
483492
port = fake_server_start(auth: 'plain')
484493
assert_raise Net::SMTPAuthenticationError do
485494
Net::SMTP.start('localhost', port, user: 'account', password: 'invalid', authtype: :plain){}

0 commit comments

Comments
 (0)