diff --git a/lib/net/imap.rb b/lib/net/imap.rb
index b8cb6fd91..dcef290dc 100644
--- a/lib/net/imap.rb
+++ b/lib/net/imap.rb
@@ -109,7 +109,7 @@ module Net
# sending them. Special care should be taken to follow the #capabilities
# requirements for #starttls, #login, and #authenticate.
#
- # See #capable?, #auth_capable, #capabilities, #auth_mechanisms to discover
+ # See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover
# server capabilities. For relevant capability requirements, see the
# documentation on each \IMAP command.
#
@@ -1139,13 +1139,13 @@ def starttls(options = {}, verify = true)
# the documentation for the specific mechanisms you are using:
#
# +ANONYMOUS+::
- # See AnonymousAuthenticator[Net::IMAP::SASL::AnonymousAuthenticator].
+ # See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
#
# Allows the user to gain access to public services or resources without
# authenticating or disclosing an identity.
#
# +EXTERNAL+::
- # See ExternalAuthenticator[Net::IMAP::SASL::ExternalAuthenticator].
+ # See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
#
# Authenticates using already established credentials, such as a TLS
# certificate or IPsec.
diff --git a/lib/net/imap/sasl.rb b/lib/net/imap/sasl.rb
index 7ea0edc71..3bcffb6b0 100644
--- a/lib/net/imap/sasl.rb
+++ b/lib/net/imap/sasl.rb
@@ -28,13 +28,13 @@ class IMAP
# the documentation for the specific mechanisms you are using:
#
# +ANONYMOUS+::
- # See AnonymousAuthenticator[Net::IMAP::SASL::AnonymousAuthenticator].
+ # See AnonymousAuthenticator.
#
# Allows the user to gain access to public services or resources without
# authenticating or disclosing an identity.
#
# +EXTERNAL+::
- # See ExternalAuthenticator[Net::IMAP::SASL::ExternalAuthenticator].
+ # See ExternalAuthenticator.
#
# Authenticates using already established credentials, such as a TLS
# certificate or IPsec.
diff --git a/lib/net/imap/sasl/anonymous_authenticator.rb b/lib/net/imap/sasl/anonymous_authenticator.rb
index 7debff6a8..fbcfeeac8 100644
--- a/lib/net/imap/sasl/anonymous_authenticator.rb
+++ b/lib/net/imap/sasl/anonymous_authenticator.rb
@@ -9,6 +9,17 @@ module SASL
# Net::IMAP#authenticate.
class AnonymousAuthenticator
+ # An optional token sent for the +ANONYMOUS+ mechanism., up to 255 UTF-8
+ # characters in length.
+ #
+ # If it contains an "@" sign, the message must be a valid email address
+ # (+addr-spec+ from RFC-2822[https://tools.ietf.org/html/rfc2822]).
+ # Email syntax is _not_ validated by AnonymousAuthenticator.
+ #
+ # Otherwise, it can be any UTF8 string which is permitted by the
+ # StringPrep::Trace profile.
+ attr_reader :anonymous_message
+
# :call-seq:
# new(anonymous_message = "", **) -> authenticator
# new(anonymous_message: "", **) -> authenticator
@@ -21,7 +32,7 @@ class AnonymousAuthenticator
# #anonymous_message is an optional message which is sent to the server.
# It may be sent as a positional argument or as a keyword argument.
#
- # Any other keyword parameters are quietly ignored.
+ # Any other keyword arguments are silently ignored.
def initialize(anon_msg = nil, anonymous_message: nil, **)
message = (anonymous_message || anon_msg || "").to_str
@anonymous_message = StringPrep::Trace.stringprep_trace message
@@ -31,16 +42,6 @@ def initialize(anon_msg = nil, anonymous_message: nil, **)
end
end
- # A token sent for the +ANONYMOUS+ mechanism.
- #
- # If it contains an "@" sign, the message must be a valid email address
- # (+addr-spec+ from RFC-2822[https://tools.ietf.org/html/rfc2822]).
- # Email syntax is _not_ validated by AnonymousAuthenticator.
- #
- # Otherwise, it can be any UTF8 string which is permitted by the
- # StringPrep::Trace profile, up to 255 UTF-8 characters in length.
- attr_reader :anonymous_message
-
# :call-seq:
# initial_response? -> true
#
@@ -48,7 +49,9 @@ def initialize(anon_msg = nil, anonymous_message: nil, **)
def initial_response?; true end
# Returns #anonymous_message.
- def process(_server_challenge_string) anonymous_message end
+ def process(_server_challenge_string)
+ anonymous_message
+ end
end
end
diff --git a/lib/net/imap/sasl/cram_md5_authenticator.rb b/lib/net/imap/sasl/cram_md5_authenticator.rb
index 3359f5a10..42935d3a0 100644
--- a/lib/net/imap/sasl/cram_md5_authenticator.rb
+++ b/lib/net/imap/sasl/cram_md5_authenticator.rb
@@ -14,13 +14,6 @@
# of cleartext and recommends TLS version 1.2 or greater be used for all
# traffic. With TLS +CRAM-MD5+ is okay, but so is +PLAIN+
class Net::IMAP::SASL::CramMD5Authenticator
- def process(challenge)
- digest = hmac_md5(challenge, @password)
- return @user + " " + digest
- end
-
- private
-
def initialize(user, password, warn_deprecation: true, **_ignored)
if warn_deprecation
warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM
@@ -30,6 +23,13 @@ def initialize(user, password, warn_deprecation: true, **_ignored)
@password = password
end
+ def process(challenge)
+ digest = hmac_md5(challenge, @password)
+ return @user + " " + digest
+ end
+
+ private
+
def hmac_md5(text, key)
if key.length > 64
key = Digest::MD5.digest(key)
diff --git a/lib/net/imap/sasl/digest_md5_authenticator.rb b/lib/net/imap/sasl/digest_md5_authenticator.rb
index e8f29a0aa..8833a43a7 100644
--- a/lib/net/imap/sasl/digest_md5_authenticator.rb
+++ b/lib/net/imap/sasl/digest_md5_authenticator.rb
@@ -1,14 +1,72 @@
# frozen_string_literal: true
# Net::IMAP authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified
-# in RFC2831(https://tools.ietf.org/html/rfc2831). See Net::IMAP#authenticate.
+# in RFC-2831[https://tools.ietf.org/html/rfc2831]. See Net::IMAP#authenticate.
#
# == Deprecated
#
# "+DIGEST-MD5+" has been deprecated by
-# {RFC6331}[https://tools.ietf.org/html/rfc6331] and should not be relied on for
+# RFC-6331[https://tools.ietf.org/html/rfc6331] and should not be relied on for
# security. It is included for compatibility with existing servers.
class Net::IMAP::SASL::DigestMD5Authenticator
+ STAGE_ONE = :stage_one
+ STAGE_TWO = :stage_two
+ private_constant :STAGE_ONE, :STAGE_TWO
+
+ # Authentication identity: the identity that matches the #password.
+ #
+ # RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+.
+ # "Authentication identity" is the generic term used by
+ # RFC-4422[https://tools.ietf.org/html/rfc4422].
+ # RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
+ # that to +authcid+. So +authcid+ is available as an alias for #username.
+ attr_reader :username
+
+ # A password or passphrase that matches the #username.
+ #
+ # The +password+ will be used to create the response digest.
+ attr_reader :password
+
+ # Authorization identity: an identity to act as or on behalf of. The identity
+ # form is application protocol specific. If not provided or left blank, the
+ # server derives an authorization identity from the authentication identity.
+ # The server is responsible for verifying the client's credentials and
+ # verifying that the identity it associates with the client's authentication
+ # identity is allowed to act as (or on behalf of) the authorization identity.
+ #
+ # For example, an administrator or superuser might take on another role:
+ #
+ # imap.authenticate "DIGEST-MD5", "root", ->{passwd}, authzid: "user"
+ #
+ attr_reader :authzid
+
+ # :call-seq:
+ # new(username, password, authzid = nil) -> authenticator
+ #
+ # Creates an Authenticator for the "+DIGEST-MD5+" SASL mechanism.
+ #
+ # Called by Net::IMAP#authenticate and similar methods on other clients.
+ #
+ # ==== Parameters
+ #
+ # * #username — Identity whose #password is used.
+ # * #password — A password or passphrase associated with this #username.
+ # * #authzid ― Alternate identity to act as or on behalf of. Optional.
+ # * +warn_deprecation+ — Set to +false+ to silence the warning.
+ #
+ # See the documentation for each attribute for more details.
+ def initialize(username, password, authzid = nil, warn_deprecation: true)
+ if warn_deprecation
+ warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
+ # TODO: recommend SCRAM instead.
+ end
+ require "digest/md5"
+ require "strscan"
+ @username, @password, @authzid = username, password, authzid
+ @nc, @stage = {}, STAGE_ONE
+ end
+
+ # Responds to server challenge in two stages.
def process(challenge)
case @stage
when STAGE_ONE
@@ -31,7 +89,7 @@ def process(challenge)
response = {
:nonce => sparams['nonce'],
- :username => @user,
+ :username => @username,
:realm => sparams['realm'],
:cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]),
:'digest-uri' => 'imap/' + sparams['realm'],
@@ -41,7 +99,7 @@ def process(challenge)
:charset => sparams['charset'],
}
- response[:authzid] = @authname unless @authname.nil?
+ response[:authzid] = @authzid unless @authzid.nil?
# now, the real thing
a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') )
@@ -74,23 +132,8 @@ def process(challenge)
end
end
- def initialize(user, password, authname = nil, warn_deprecation: true)
- if warn_deprecation
- warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
- # TODO: recommend SCRAM instead.
- end
- require "digest/md5"
- require "strscan"
- @user, @password, @authname = user, password, authname
- @nc, @stage = {}, STAGE_ONE
- end
-
-
private
- STAGE_ONE = :stage_one
- STAGE_TWO = :stage_two
-
def nc(nonce)
if @nc.has_key? nonce
@nc[nonce] = @nc[nonce] + 1
diff --git a/lib/net/imap/sasl/external_authenticator.rb b/lib/net/imap/sasl/external_authenticator.rb
index e49af4731..12a0113a4 100644
--- a/lib/net/imap/sasl/external_authenticator.rb
+++ b/lib/net/imap/sasl/external_authenticator.rb
@@ -12,6 +12,12 @@ module SASL
# established external to SASL, for example by TLS certificate or IPsec.
class ExternalAuthenticator
+ # Authorization identity: an identity to act as or on behalf of.
+ #
+ # If not explicitly provided, the server defaults to using the identity
+ # that was authenticated by the external credentials.
+ attr_reader :authzid
+
# :call-seq:
# new(authzid: nil, **) -> authenticator
#
@@ -30,12 +36,6 @@ def initialize(authzid: nil)
end
end
- # Authorization identity: an identity to act as or on behalf of.
- #
- # If not explicitly provided, the server defaults to using the identity
- # that was authenticated by the external credentials.
- attr_reader :authzid
-
# :call-seq:
# initial_response? -> true
#
diff --git a/lib/net/imap/sasl/login_authenticator.rb b/lib/net/imap/sasl/login_authenticator.rb
index 9ee838b0b..11d508df5 100644
--- a/lib/net/imap/sasl/login_authenticator.rb
+++ b/lib/net/imap/sasl/login_authenticator.rb
@@ -18,20 +18,9 @@
# {draft-murchison-sasl-login}[https://www.iana.org/go/draft-murchison-sasl-login]
# for both specification and deprecation.
class Net::IMAP::SASL::LoginAuthenticator
- def process(data)
- case @state
- when STATE_USER
- @state = STATE_PASSWORD
- return @user
- when STATE_PASSWORD
- return @password
- end
- end
-
- private
-
STATE_USER = :USER
STATE_PASSWORD = :PASSWORD
+ private_constant :STATE_USER, :STATE_PASSWORD
def initialize(user, password, warn_deprecation: true, **_ignored)
if warn_deprecation
@@ -42,4 +31,13 @@ def initialize(user, password, warn_deprecation: true, **_ignored)
@state = STATE_USER
end
+ def process(data)
+ case @state
+ when STATE_USER
+ @state = STATE_PASSWORD
+ return @user
+ when STATE_PASSWORD
+ return @password
+ end
+ end
end
diff --git a/lib/net/imap/sasl/oauthbearer_authenticator.rb b/lib/net/imap/sasl/oauthbearer_authenticator.rb
index d99fb2278..c23c35f96 100644
--- a/lib/net/imap/sasl/oauthbearer_authenticator.rb
+++ b/lib/net/imap/sasl/oauthbearer_authenticator.rb
@@ -14,35 +14,6 @@ module SASL
class OAuthAuthenticator
include GS2Header
- # Creates an RFC7628[https://tools.ietf.org/html/rfc7628] OAuth
- # authenticator.
- #
- # === Options
- #
- # See child classes for required configuration parameter(s). The
- # following parameters are all optional, but protocols or servers may
- # add requirements for #authzid, #host, #port, or any other parameter.
- #
- # * #authzid ― Identity to act as or on behalf of.
- # * #host — Hostname to which the client connected.
- # * #port — Service port to which the client connected.
- # * #mthd — HTTP method
- # * #path — HTTP path data
- # * #post — HTTP post data
- # * #qs — HTTP query string
- #
- def initialize(authzid: nil, host: nil, port: nil,
- mthd: nil, path: nil, post: nil, qs: nil, **)
- @authzid = authzid
- @host = host
- @port = port
- @mthd = mthd
- @path = path
- @post = post
- @qs = qs
- @done = false
- end
-
# Authorization identity: an identity to act as or on behalf of.
#
# If no explicit authorization identity is provided, it is usually
@@ -73,6 +44,45 @@ def initialize(authzid: nil, host: nil, port: nil,
# this may hold information about the failure reason, as JSON.
attr_reader :last_server_response
+ # Creates an RFC7628[https://tools.ietf.org/html/rfc7628] OAuth
+ # authenticator.
+ #
+ # === Options
+ #
+ # See child classes for required configuration parameter(s). The
+ # following parameters are all optional, but protocols or servers may
+ # add requirements for #authzid, #host, #port, or any other parameter.
+ #
+ # * #authzid ― Identity to act as or on behalf of.
+ # * #host — Hostname to which the client connected.
+ # * #port — Service port to which the client connected.
+ # * #mthd — HTTP method
+ # * #path — HTTP path data
+ # * #post — HTTP post data
+ # * #qs — HTTP query string
+ #
+ def initialize(authzid: nil, host: nil, port: nil,
+ mthd: nil, path: nil, post: nil, qs: nil, **)
+ @authzid = authzid
+ @host = host
+ @port = port
+ @mthd = mthd
+ @path = path
+ @post = post
+ @qs = qs
+ @done = false
+ end
+
+ # The {RFC7628 §3.1}[https://www.rfc-editor.org/rfc/rfc7628#section-3.1]
+ # formatted response.
+ def initial_client_response
+ kv_pairs = {
+ host: host, port: port, mthd: mthd, path: path, post: post, qs: qs,
+ auth: authorization, # authorization is implemented by subclasses
+ }.compact
+ [gs2_header, *kv_pairs.map {|kv| kv.join("=") }, "\1"].join("\1")
+ end
+
# Returns initial_client_response the first time, then "^A".
def process(data)
@last_server_response = data
@@ -87,16 +97,6 @@ def process(data)
# does *not* indicate success.
def done?; @done end
- # The {RFC7628 §3.1}[https://www.rfc-editor.org/rfc/rfc7628#section-3.1]
- # formatted response.
- def initial_client_response
- kv_pairs = {
- host: host, port: port, mthd: mthd, path: path, post: post, qs: qs,
- auth: authorization, # authorization is implemented by subclasses
- }.compact
- [gs2_header, *kv_pairs.map {|kv| kv.join("=") }, "\1"].join("\1")
- end
-
# Value of the HTTP Authorization header
#
# Implemented by subclasses.
@@ -116,6 +116,9 @@ def authorization; raise "must be implemented by subclass" end
# the bearer token.
class OAuthBearerAuthenticator < OAuthAuthenticator
+ # An OAuth2 bearer token, generally the access token.
+ attr_reader :oauth2_token
+
# :call-seq:
# new(oauth2_token, **options) -> authenticator
# new(oauth2_token:, **options) -> authenticator
@@ -145,9 +148,6 @@ def initialize(oauth2_token_arg = nil, oauth2_token: nil, **args, &blk)
raise ArgumentError, "missing oauth2_token"
end
- # An OAuth2 bearer token, generally the access token.
- attr_reader :oauth2_token
-
# :call-seq:
# initial_response? -> true
#
diff --git a/lib/net/imap/sasl/plain_authenticator.rb b/lib/net/imap/sasl/plain_authenticator.rb
index 18d763bb2..7b2de294d 100644
--- a/lib/net/imap/sasl/plain_authenticator.rb
+++ b/lib/net/imap/sasl/plain_authenticator.rb
@@ -1,35 +1,55 @@
# frozen_string_literal: true
# Authenticator for the "+PLAIN+" SASL mechanism, specified in
-# RFC4616[https://tools.ietf.org/html/rfc4616]. See Net::IMAP#authenticate.
+# RFC-4616[https://tools.ietf.org/html/rfc4616]. See Net::IMAP#authenticate.
#
# +PLAIN+ authentication sends the password in cleartext.
-# RFC3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable
+# RFC-3501[https://tools.ietf.org/html/rfc3501] encourages servers to disable
# cleartext authentication until after TLS has been negotiated.
-# RFC8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or
+# RFC-8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2 or
# greater be used for all traffic, and deprecate cleartext access ASAP. +PLAIN+
# can be secured by TLS encryption.
class Net::IMAP::SASL::PlainAuthenticator
- def initial_response?; true end
+ NULL = -"\0".b
+ private_constant :NULL
- def process(data)
- return "#@authzid\0#@username\0#@password"
- end
+ # Authentication identity: the identity that matches the #password.
+ #
+ # RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
+ # this to +authcid+.
+ attr_reader :username
- # :nodoc:
- NULL = -"\0".b
+ # A password or passphrase that matches the #username.
+ attr_reader :password
- private
+ # Authorization identity: an identity to act as or on behalf of. The identity
+ # form is application protocol specific. If not provided or left blank, the
+ # server derives an authorization identity from the authentication identity.
+ # The server is responsible for verifying the client's credentials and
+ # verifying that the identity it associates with the client's authentication
+ # identity is allowed to act as (or on behalf of) the authorization identity.
+ #
+ # For example, an administrator or superuser might take on another role:
+ #
+ # imap.authenticate "PLAIN", "root", passwd, authzid: "user"
+ #
+ attr_reader :authzid
- # +username+ is the authentication identity, the identity whose +password+ is
- # used. +username+ is referred to as +authcid+ by
- # RFC4616[https://tools.ietf.org/html/rfc4616].
+ # :call-seq:
+ # new(username, password, authzid: nil) -> authenticator
+ #
+ # Creates an Authenticator for the "+PLAIN+" SASL mechanism.
#
- # +authzid+ is the authorization identity (identity to act as). It can
- # usually be left blank. When +authzid+ is left blank (nil or empty string)
- # the server will derive an identity from the credentials and use that as the
- # authorization identity.
+ # Called by Net::IMAP#authenticate and similar methods on other clients.
+ #
+ # === Parameters
+ #
+ # * #username ― Identity whose +password+ is used.
+ # * #password ― Password or passphrase associated with this username+.
+ # * #authzid ― Alternate identity to act as or on behalf of. Optional.
+ #
+ # See attribute documentation for more details.
def initialize(username, password, authzid: nil)
raise ArgumentError, "username contains NULL" if username&.include?(NULL)
raise ArgumentError, "password contains NULL" if password&.include?(NULL)
@@ -39,4 +59,15 @@ def initialize(username, password, authzid: nil)
@authzid = authzid
end
+ # :call-seq:
+ # initial_response? -> true
+ #
+ # +PLAIN+ can send an initial client response.
+ def initial_response?; true end
+
+ # Responds with the client's credentials.
+ def process(data)
+ return "#@authzid\0#@username\0#@password"
+ end
+
end
diff --git a/lib/net/imap/sasl/xoauth2_authenticator.rb b/lib/net/imap/sasl/xoauth2_authenticator.rb
index 9f5a99f82..00dc397ec 100644
--- a/lib/net/imap/sasl/xoauth2_authenticator.rb
+++ b/lib/net/imap/sasl/xoauth2_authenticator.rb
@@ -1,22 +1,73 @@
# frozen_string_literal: true
+# Authenticator for the "+XOAUTH2+" SASL mechanism. This mechanism was
+# originally created for GMail and widely adopted by hosted email providers.
+# +XOAUTH2+ has been documented by
+# Google[https://developers.google.com/gmail/imap/xoauth2-protocol] and
+# Microsoft[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth].
+#
+# This mechanism requires an OAuth2 +access_token+ which has been authorized
+# with the appropriate OAuth2 scopes to access IMAP. These scopes are not
+# standardized---consult each email service provider's documentation.
+#
+# Although this mechanism was never standardized and has been obsoleted by
+# "+OAUTHBEARER+", it is still very widely supported.
+#
+# See Net::IMAP::SASL:: OAuthBearerAuthenticator.
class Net::IMAP::SASL::XOAuth2Authenticator
+ # It is unclear from {Google's original XOAUTH2
+ # documentation}[https://developers.google.com/gmail/imap/xoauth2-protocol],
+ # whether "User" refers to the authentication identity (+authcid+) or the
+ # authorization identity (+authzid+). It appears to behave as +authzid+.
+ #
+ # {Microsoft's documentation for shared
+ # mailboxes}[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#sasl-xoauth2-authentication-for-shared-mailboxes-in-office-365]
+ # clearly indicate that the Office 365 server interprets it as the
+ # authorization identity.
+ attr_reader :username
+
+ # An OAuth2 access token which has been authorized with the appropriate OAuth2
+ # scopes to use the service for #username.
+ attr_reader :oauth2_token
+
+ # :call-seq:
+ # :call-seq:
+ # new(username, oauth2_token) -> authenticator
+ #
+ # Creates an Authenticator for the "+XOAUTH2+" SASL mechanism, as specified by
+ # Google[https://developers.google.com/gmail/imap/xoauth2-protocol],
+ # Microsoft[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth]
+ # and Yahoo[https://senders.yahooinc.com/developer/documentation].
+ #
+ # === Properties
+ #
+ # * #username --- the username for the account being accessed.
+ # * #oauth2_token --- An OAuth2.0 access token which is authorized to access
+ # the service for #username.
+ #
+ # See the documentation for each attribute for more details.
+ def initialize(username, oauth2_token)
+ @username = username
+ @oauth2_token = oauth2_token
+ end
+
+ # :call-seq:
+ # initial_response? -> true
+ #
+ # +PLAIN+ can send an initial client response.
def initial_response?; true end
+ # Returns the XOAUTH2 formatted response, which combines the +username+
+ # with the +oauth2_token+.
def process(_data)
- build_oauth2_string(@user, @oauth2_token)
+ build_oauth2_string(@username, @oauth2_token)
end
private
- def initialize(user, oauth2_token)
- @user = user
- @oauth2_token = oauth2_token
- end
-
- def build_oauth2_string(user, oauth2_token)
- format("user=%s\1auth=Bearer %s\1\1", user, oauth2_token)
+ def build_oauth2_string(username, oauth2_token)
+ format("user=%s\1auth=Bearer %s\1\1", username, oauth2_token)
end
end
diff --git a/test/net/imap/test_imap_authenticators.rb b/test/net/imap/test_imap_authenticators.rb
index 79ce20762..8e63c883f 100644
--- a/test/net/imap/test_imap_authenticators.rb
+++ b/test/net/imap/test_imap_authenticators.rb
@@ -88,7 +88,7 @@ def test_xoauth2_authenticator_matches_mechanism
assert_kind_of(Net::IMAP::SASL::XOAuth2Authenticator, xoauth2("user", "tok"))
end
- def test_xoauth2
+ def test_xoauth2_response
assert_equal(
"user=username\1auth=Bearer token\1\1",
xoauth2("username", "token").process(nil)