From 745c320df4daef62ce669babb7a11faf2cea5e1a Mon Sep 17 00:00:00 2001 From: nicholas evans Date: Wed, 11 Oct 2023 08:56:08 -0400 Subject: [PATCH] Add `#auth` method for SASL authentication Although `#authenticate` can be updated to make username and secret _both_ optional, by placing the mechanism last and making it optional, it's not possible to use an authenticator with a _single_ positional parameter or with more than two positional parameters. By placing `type` first among positional parameters or as a keyword argument, we avoid this problem. --- lib/net/smtp.rb | 16 ++++++++++++++++ test/net/smtp/test_smtp.rb | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index 7b8aefd..7e967a7 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -897,6 +897,22 @@ def authenticate(*args, **kwargs, &block) raise ArgumentError, "wrong number of arguments " \ "(given %d, expected 0..3)" % [args.length] end + auth(authtype, *args, **kwargs, &block) + end + + # call-seq: + # auth(type = DEFAULT_AUTH_TYPE, ...) + # auth(type: DEFAULT_AUTH_TYPE, **kwargs, &block) + # + # All arguments besides +mechanism+ are forwarded directly to the + # authenticator. Alternatively, +mechanism+ can be provided by the +type+ + # keyword parameter. Positional parameters cannot be used with +type+. + # + # Different authenticators take different options, but common options + # include +authcid+ for authentication identity, +authzid+ for authorization + # identity, +username+ for either "authentication identity" or + # "authorization identity" depending on the +mechanism+, and +password+. + def auth(authtype = DEFAULT_AUTH_TYPE, *args, **kwargs, &block) authtype, args, kwargs = check_auth_args authtype, *args, **kwargs authenticator = Authenticator.auth_class(authtype).new(self) critical { authenticator.auth(*args, **kwargs, &block) } diff --git a/test/net/smtp/test_smtp.rb b/test/net/smtp/test_smtp.rb index a4450b3..f7a38b8 100644 --- a/test/net/smtp/test_smtp.rb +++ b/test/net/smtp/test_smtp.rb @@ -110,6 +110,21 @@ def test_auth_plain smtp = Net::SMTP.start 'localhost', server.port assert smtp.authenticate("account", "password", :plain).success? assert_equal "AUTH PLAIN AGFjY291bnQAcGFzc3dvcmQ=\r\n", server.commands.last + + server = FakeServer.start(auth: 'plain') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth("PLAIN", "account", "password").success? + assert_equal "AUTH PLAIN AGFjY291bnQAcGFzc3dvcmQ=\r\n", server.commands.last + + server = FakeServer.start(auth: 'plain') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth(type: "PLAIN", username: "account", secret: "password").success? + assert_equal "AUTH PLAIN AGFjY291bnQAcGFzc3dvcmQ=\r\n", server.commands.last + + server = FakeServer.start(auth: 'plain') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth("PLAIN", username: "account", password: "password").success? + assert_equal "AUTH PLAIN AGFjY291bnQAcGFzc3dvcmQ=\r\n", server.commands.last end def test_unsucessful_auth_plain @@ -120,10 +135,20 @@ def test_unsucessful_auth_plain assert_equal "535", err.response.status end + def test_auth_cram_md5 + server = FakeServer.start(auth: 'CRAM-MD5') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth(:cram_md5, "account", password: "password").success? + end + def test_auth_login server = FakeServer.start(auth: 'login') smtp = Net::SMTP.start 'localhost', server.port assert smtp.authenticate("account", "password", :login).success? + + server = FakeServer.start(auth: 'login') + smtp = Net::SMTP.start 'localhost', server.port + assert smtp.auth("LOGIN", username: "account", secret: "password").success? end def test_unsucessful_auth_login