diff --git a/lib/net/imap/sasl/anonymous_authenticator.rb b/lib/net/imap/sasl/anonymous_authenticator.rb
index ed7a46ef..aa4ecae4 100644
--- a/lib/net/imap/sasl/anonymous_authenticator.rb
+++ b/lib/net/imap/sasl/anonymous_authenticator.rb
@@ -29,8 +29,9 @@ class AnonymousAuthenticator
# this, see Net::IMAP#authenticate or your client's authentication
# method.
#
- # #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.
+ # ==== Parameters
+ #
+ # * _optional_ #anonymous_message — a message to send to the server.
#
# Any other keyword arguments are silently ignored.
def initialize(anon_msg = nil, anonymous_message: nil, **)
diff --git a/lib/net/imap/sasl/cram_md5_authenticator.rb b/lib/net/imap/sasl/cram_md5_authenticator.rb
index 3aac7b35..d5648515 100644
--- a/lib/net/imap/sasl/cram_md5_authenticator.rb
+++ b/lib/net/imap/sasl/cram_md5_authenticator.rb
@@ -14,13 +14,17 @@
# 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 initialize(user, password, warn_deprecation: true, **_ignored)
+ def initialize(user = nil, pass = nil,
+ authcid: nil, username: nil,
+ password: nil,
+ warn_deprecation: true,
+ **)
if warn_deprecation
warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM
end
require "digest/md5"
- @user = user
- @password = password
+ @user = authcid || username || user
+ @password = password || pass
@done = false
end
diff --git a/lib/net/imap/sasl/digest_md5_authenticator.rb b/lib/net/imap/sasl/digest_md5_authenticator.rb
index dcc6fc59..3945f155 100644
--- a/lib/net/imap/sasl/digest_md5_authenticator.rb
+++ b/lib/net/imap/sasl/digest_md5_authenticator.rb
@@ -20,8 +20,9 @@ class Net::IMAP::SASL::DigestMD5Authenticator
# "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.
+ # this to +authcid+.
attr_reader :username
+ alias authcid username
# A password or passphrase that matches the #username.
#
@@ -44,6 +45,7 @@ class Net::IMAP::SASL::DigestMD5Authenticator
# :call-seq:
# new(username, password, authzid = nil, **options) -> authenticator
# new(username:, password:, authzid: nil, **options) -> authenticator
+ # new(authcid:, password:, authzid: nil, **options) -> authenticator
#
# Creates an Authenticator for the "+DIGEST-MD5+" SASL mechanism.
#
@@ -51,16 +53,26 @@ class Net::IMAP::SASL::DigestMD5Authenticator
#
# ==== 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.
+ # * #authcid ― Authentication identity that is associated with #password.
#
- # See the documentation for each attribute for more details.
+ # #username ― An alias for +authcid+.
+ #
+ # * #password ― A password or passphrase associated with this #authcid.
+ #
+ # * _optional_ #authzid ― Authorization identity to act as or on behalf of.
+ #
+ # When +authzid+ is not set, the server should derive the authorization
+ # identity from the authentication identity.
+ #
+ # * _optional_ +warn_deprecation+ — Set to +false+ to silence the warning.
+ #
+ # Any other keyword arguments are silently ignored.
def initialize(user = nil, pass = nil, authz = nil,
username: nil, password: nil, authzid: nil,
+ authcid: nil,
warn_deprecation: true, **)
- username ||= user or raise ArgumentError, "missing username"
+ username = authcid || username || user or
+ raise ArgumentError, "missing username (authcid)"
password ||= pass or raise ArgumentError, "missing password"
authzid ||= authz
if warn_deprecation
diff --git a/lib/net/imap/sasl/external_authenticator.rb b/lib/net/imap/sasl/external_authenticator.rb
index f229c63d..ab4e3502 100644
--- a/lib/net/imap/sasl/external_authenticator.rb
+++ b/lib/net/imap/sasl/external_authenticator.rb
@@ -12,24 +12,45 @@ 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.
+ # 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"
#
- # If not explicitly provided, the server defaults to using the identity
- # that was authenticated by the external credentials.
attr_reader :authzid
+ alias username authzid
# :call-seq:
# new(authzid: nil, **) -> authenticator
+ # new(username: nil, **) -> authenticator
+ # new(username = nil, **) -> authenticator
#
# Creates an Authenticator for the "+EXTERNAL+" SASL mechanism, as
# specified in RFC-4422[https://tools.ietf.org/html/rfc4422]. To use
# this, see Net::IMAP#authenticate or your client's authentication
# method.
#
- # #authzid is an optional identity to act as or on behalf of.
+ # ==== Parameters
+ #
+ # * _optional_ #authzid ― Authorization identity to act as or on behalf of.
+ #
+ # _optional_ #username ― An alias for #authzid.
+ #
+ # Note that, unlike some other authenticators, +username+ sets the
+ # _authorization_ identity and not the _authentication_ identity. The
+ # authentication identity is established for the client by the
+ # external credentials.
#
# Any other keyword parameters are quietly ignored.
- def initialize(authzid: nil, **)
+ def initialize(user = nil, authzid: nil, username: nil, **)
+ authzid ||= username || user
@authzid = authzid&.to_str&.encode "UTF-8"
if @authzid&.match?(/\u0000/u) # also validates UTF8 encoding
raise ArgumentError, "contains NULL"
diff --git a/lib/net/imap/sasl/login_authenticator.rb b/lib/net/imap/sasl/login_authenticator.rb
index 5132a09e..81201f66 100644
--- a/lib/net/imap/sasl/login_authenticator.rb
+++ b/lib/net/imap/sasl/login_authenticator.rb
@@ -23,12 +23,16 @@ class Net::IMAP::SASL::LoginAuthenticator
STATE_DONE = :DONE
private_constant :STATE_USER, :STATE_PASSWORD, :STATE_DONE
- def initialize(user, password, warn_deprecation: true, **_ignored)
+ def initialize(user = nil, pass = nil,
+ authcid: nil, username: nil,
+ password: nil,
+ warn_deprecation: true,
+ **)
if warn_deprecation
warn "WARNING: LOGIN SASL mechanism is deprecated. Use PLAIN instead."
end
- @user = user
- @password = password
+ @user = authcid || username || user
+ @password = password || pass
@state = STATE_USER
end
diff --git a/lib/net/imap/sasl/oauthbearer_authenticator.rb b/lib/net/imap/sasl/oauthbearer_authenticator.rb
index c23c35f9..f7191c71 100644
--- a/lib/net/imap/sasl/oauthbearer_authenticator.rb
+++ b/lib/net/imap/sasl/oauthbearer_authenticator.rb
@@ -14,18 +14,25 @@ module SASL
class OAuthAuthenticator
include GS2Header
- # Authorization identity: an identity to act as or on behalf of.
+ # 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"
#
- # If no explicit authorization identity is provided, it is usually
- # derived from the authentication identity. For the OAuth-based
- # mechanisms, the authentication identity is the identity established by
- # the OAuth credential.
attr_reader :authzid
+ alias username authzid
- # Hostname to which the client connected.
+ # Hostname to which the client connected. (optional)
attr_reader :host
- # Service port to which the client connected.
+ # Service port to which the client connected. (optional)
attr_reader :port
# HTTP method. (optional)
@@ -39,6 +46,7 @@ class OAuthAuthenticator
# The query string. (optional)
attr_reader :qs
+ alias query qs
# Stores the most recent server "challenge". When authentication fails,
# this may hold information about the failure reason, as JSON.
@@ -47,29 +55,42 @@ class OAuthAuthenticator
# Creates an RFC7628[https://tools.ietf.org/html/rfc7628] OAuth
# authenticator.
#
- # === Options
+ # ==== Parameters
+ #
+ # See child classes for required parameter(s). The following parameters
+ # are all optional, but it is worth noting that application protocols
+ # are allowed to require #authzid (or other parameters, such as
+ # #host or #port) as are specific server implementations.
+ #
+ # * _optional_ #authzid ― Authorization identity to act as or on behalf of.
+ #
+ # _optional_ #username — An alias for #authzid.
#
- # 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.
+ # Note that, unlike some other authenticators, +username+ sets the
+ # _authorization_ identity and not the _authentication_ identity. The
+ # authentication identity is established for the client by the OAuth
+ # token.
#
- # * #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
+ # * _optional_ #host — Hostname to which the client connected.
+ # * _optional_ #port — Service port to which the client connected.
+ # * _optional_ #mthd — HTTP method
+ # * _optional_ #path — HTTP path data
+ # * _optional_ #post — HTTP post data
+ # * _optional_ #qs — HTTP query string
#
+ # _optional_ #query — An alias for #qs
+ #
+ # Any other keyword parameters are quietly ignored.
def initialize(authzid: nil, host: nil, port: nil,
+ username: nil, query: nil,
mthd: nil, path: nil, post: nil, qs: nil, **)
- @authzid = authzid
+ @authzid = authzid || username
@host = host
@port = port
@mthd = mthd
@path = path
@post = post
- @qs = qs
+ @qs = qs || query
@done = false
end
@@ -116,34 +137,45 @@ def authorization; raise "must be implemented by subclass" end
# the bearer token.
class OAuthBearerAuthenticator < OAuthAuthenticator
- # An OAuth2 bearer token, generally the access token.
+ # An OAuth 2.0 bearer token. See {RFC-6750}[https://www.rfc-editor.org/rfc/rfc6750]
attr_reader :oauth2_token
# :call-seq:
- # new(oauth2_token, **options) -> authenticator
- # new(oauth2_token:, **options) -> authenticator
+ # new(oauth2_token, **options) -> authenticator
+ # new(authzid, oauth2_token, **options) -> authenticator
+ # new(oauth2_token:, **options) -> authenticator
#
# Creates an Authenticator for the "+OAUTHBEARER+" SASL mechanism.
#
# Called by Net::IMAP#authenticate and similar methods on other clients.
#
- # === Options
+ # ==== Parameters
+ #
+ # * #oauth2_token — An OAuth2 bearer token
+ #
+ # All other keyword parameters are passed to
+ # {super}[rdoc-ref:OAuthAuthenticator::new] (see OAuthAuthenticator).
+ # The most common ones are:
+ #
+ # * _optional_ #authzid ― Authorization identity to act as or on behalf of.
+ #
+ # _optional_ #username — An alias for #authzid.
#
- # Only +oauth2_token+ is required by the mechanism, however protocols
- # and servers may add requirements for #authzid, #host, #port, or any
- # other parameter.
+ # Note that, unlike some other authenticators, +username+ sets the
+ # _authorization_ identity and not the _authentication_ identity. The
+ # authentication identity is established for the client by
+ # #oauth2_token.
#
- # * #oauth2_token — An OAuth2 bearer token or access token. *Required.*
- # May be provided as either regular or keyword argument.
- # * #authzid ― Identity to act as or on behalf of.
- # * #host — Hostname to which the client connected.
- # * #port — Service port to which the client connected.
- # * See OAuthAuthenticator documentation for less common parameters.
+ # * _optional_ #host — Hostname to which the client connected.
+ # * _optional_ #port — Service port to which the client connected.
#
- def initialize(oauth2_token_arg = nil, oauth2_token: nil, **args, &blk)
- super(**args, &blk) # handles authzid, host, port, etc
- oauth2_token && oauth2_token_arg and
- raise ArgumentError, "conflicting values for oauth2_token"
+ # Although only oauth2_token is required by this mechanism, it is worth
+ # noting that application protocols are allowed to
+ # require #authzid (or other parameters, such as #host
+ # _or_ #port) as are specific server implementations.
+ def initialize(arg1 = nil, arg2 = nil, oauth2_token: nil, **args, &blk)
+ username, oauth2_token_arg = arg2.nil? ? [nil, arg1] : [arg1, arg2]
+ super(username: username, **args, &blk)
@oauth2_token = oauth2_token || oauth2_token_arg or
raise ArgumentError, "missing oauth2_token"
end
diff --git a/lib/net/imap/sasl/plain_authenticator.rb b/lib/net/imap/sasl/plain_authenticator.rb
index c8539282..cb1acf24 100644
--- a/lib/net/imap/sasl/plain_authenticator.rb
+++ b/lib/net/imap/sasl/plain_authenticator.rb
@@ -22,6 +22,7 @@ class Net::IMAP::SASL::PlainAuthenticator
# RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
# this to +authcid+.
attr_reader :username
+ alias authcid username
# A password or passphrase that matches the #username.
attr_reader :password
@@ -42,25 +43,31 @@ class Net::IMAP::SASL::PlainAuthenticator
# :call-seq:
# new(username, password, authzid: nil, **) -> authenticator
# new(username:, password:, authzid: nil, **) -> authenticator
+ # new(authcid:, password:, authzid: nil, **) -> authenticator
#
# Creates an Authenticator for the "+PLAIN+" SASL mechanism.
#
# Called by Net::IMAP#authenticate and similar methods on other clients.
#
- # === Parameters
+ # ==== 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.
+ # * #authcid ― Authentication identity that is associated with #password.
#
- # See attribute documentation for more details.
+ # #username ― An alias for #authcid.
+ #
+ # * #password ― A password or passphrase associated with the #authcid.
+ #
+ # * _optional_ #authzid ― Authorization identity to act as or on behalf of.
+ #
+ # When +authzid+ is not set, the server should derive the authorization
+ # identity from the authentication identity.
+ #
+ # Any other keyword parameters are quietly ignored.
def initialize(user = nil, pass = nil,
+ authcid: nil,
username: nil, password: nil, authzid: nil, **)
- [username, user].compact.count == 1 or
- raise ArgumentError, "conflicting values for username"
- [password, pass].compact.count == 1 or
- raise ArgumentError, "conflicting values for password"
- username ||= user or raise ArgumentError, "missing username"
+ username ||= authcid || user or
+ raise ArgumentError, "missing username (authcid)"
password ||= pass or raise ArgumentError, "missing password"
raise ArgumentError, "username contains NULL" if username.include?(NULL)
raise ArgumentError, "password contains NULL" if password.include?(NULL)
diff --git a/lib/net/imap/sasl/scram_authenticator.rb b/lib/net/imap/sasl/scram_authenticator.rb
index d8d36947..01c63480 100644
--- a/lib/net/imap/sasl/scram_authenticator.rb
+++ b/lib/net/imap/sasl/scram_authenticator.rb
@@ -60,6 +60,7 @@ class ScramAuthenticator
# :call-seq:
# new(username, password, **options) -> auth_ctx
# new(username:, password:, **options) -> auth_ctx
+ # new(authcid:, password:, **options) -> auth_ctx
#
# Creates an authenticator for one of the "+SCRAM-*+" SASL mechanisms.
# Each subclass defines #digest to match a specific mechanism.
@@ -68,34 +69,41 @@ class ScramAuthenticator
#
# === Parameters
#
- # * #username ― Identity whose #password is used. Aliased as #authcid.
+ # * #authcid ― Identity whose #password is used.
+ #
+ # #username - An alias for #authcid.
# * #password ― Password or passphrase associated with this #username.
- # * #authzid ― Alternate identity to act as or on behalf of. Optional.
- # * #min_iterations - Overrides the default value (4096). Optional.
+ # * _optional_ #authzid ― Alternate identity to act as or on behalf of.
+ # * _optional_ #min_iterations - Overrides the default value (4096).
#
- # See the documentation on the corresponding attributes for more.
+ # Any other keyword parameters are quietly ignored.
def initialize(username_arg = nil, password_arg = nil,
- username: nil, password: nil, authcid: nil, authzid: nil,
+ authcid: nil, username: nil,
+ authzid: nil,
+ password: nil,
min_iterations: 4096, # see both RFC5802 and RFC7677
cnonce: nil, # must only be set in tests
**options)
@username = username || username_arg || authcid or
raise ArgumentError, "missing username (authcid)"
- [username, username_arg, authcid].compact.count == 1 or
- raise ArgumentError, "conflicting values for username (authcid)"
@password = password || password_arg or
raise ArgumentError, "missing password"
- [password, password_arg].compact.count == 1 or
- raise ArgumentError, "conflicting values for password"
@authzid = authzid
@min_iterations = Integer min_iterations
@min_iterations.positive? or
raise ArgumentError, "min_iterations must be positive"
+
@cnonce = cnonce || SecureRandom.base64(32)
end
# 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
+ # this to +authcid+.
attr_reader :username
alias authcid username
diff --git a/lib/net/imap/sasl/xoauth2_authenticator.rb b/lib/net/imap/sasl/xoauth2_authenticator.rb
index 164afa0d..819b42ff 100644
--- a/lib/net/imap/sasl/xoauth2_authenticator.rb
+++ b/lib/net/imap/sasl/xoauth2_authenticator.rb
@@ -6,9 +6,10 @@
# 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.
+# This mechanism requires an OAuth2 access token which has been authorized
+# with the appropriate OAuth2 scopes to access the user's services. Most of
+# these scopes are not standardized---consult each service provider's
+# documentation for their scopes.
#
# Although this mechanism was never standardized and has been obsoleted by
# "+OAUTHBEARER+", it is still very widely supported.
@@ -19,14 +20,25 @@ 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+.
+ # authorization identity (+authzid+). The authentication identity is
+ # established for the client by the OAuth token, so it seems that +username+
+ # must be the authorization identity.
#
# {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
+ # _clearly_ indicates that the Office 365 server interprets it as the
# authorization identity.
+ #
+ # Although they _should_ validate that the token has been authorized to access
+ # the service for +username+, _some_ servers appear to ignore this field,
+ # relying only the identity and scope authorized by the token.
attr_reader :username
+ # Note that, unlike most other authenticators, #username is an alias for the
+ # authorization identity and not the authentication identity. The
+ # authenticated identity is established for the client by the #oauth2_token.
+ alias authzid username
+
# An OAuth2 access token which has been authorized with the appropriate OAuth2
# scopes to use the service for #username.
attr_reader :oauth2_token
@@ -34,6 +46,7 @@ class Net::IMAP::SASL::XOAuth2Authenticator
# :call-seq:
# new(username, oauth2_token, **) -> authenticator
# new(username:, oauth2_token:, **) -> authenticator
+ # new(authzid:, oauth2_token:, **) -> authenticator
#
# Creates an Authenticator for the "+XOAUTH2+" SASL mechanism, as specified by
# Google[https://developers.google.com/gmail/imap/xoauth2-protocol],
@@ -43,26 +56,30 @@ class Net::IMAP::SASL::XOAuth2Authenticator
# === Properties
#
# * #username --- the username for the account being accessed.
+ #
+ # #authzid --- an alias for #username.
+ #
+ # Note that, unlike some other authenticators, +username+ sets the
+ # _authorization_ identity and not the _authentication_ identity. The
+ # authenticated identity is established for the client with the OAuth token.
+ #
# * #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(user = nil, token = nil, username: nil, oauth2_token: nil, **)
- @username = username || user or
- raise ArgumentError, "missing username"
+ # Any other keyword parameters are quietly ignored.
+ def initialize(user = nil, token = nil, username: nil, oauth2_token: nil,
+ authzid: nil, **)
+ @username = authzid || username || user or
+ raise ArgumentError, "missing username (authzid)"
@oauth2_token = oauth2_token || token or
raise ArgumentError, "missing oauth2_token"
- [username, user].compact.count == 1 or
- raise ArgumentError, "conflicting values for username"
- [oauth2_token, token].compact.count == 1 or
- raise ArgumentError, "conflicting values for oauth2_token"
@done = false
end
# :call-seq:
# initial_response? -> true
#
- # +PLAIN+ can send an initial client response.
+ # +XOAUTH2+ can send an initial client response.
def initial_response?; true end
# Returns the XOAUTH2 formatted response, which combines the +username+
diff --git a/test/net/imap/test_imap_authenticators.rb b/test/net/imap/test_imap_authenticators.rb
index 38773a29..38afb1fc 100644
--- a/test/net/imap/test_imap_authenticators.rb
+++ b/test/net/imap/test_imap_authenticators.rb
@@ -45,13 +45,34 @@ def test_plain_supports_initial_response
def test_plain_response
assert_equal("\0authc\0passwd", plain("authc", "passwd").process(nil))
+ end
+
+ def test_plain_authzid
assert_equal("authz\0user\0pass",
plain("user", "pass", authzid: "authz").process(nil))
end
+ def test_plain_kw_params
+ assert_equal(
+ "zid\0cid\0p",
+ plain(authcid: "cid", password: "p", authzid: "zid").process(nil)
+ )
+ end
+
+ def test_plain_username_kw_sets_both_authcid_and_authzid
+ assert_equal(
+ "\0uname\0passwd",
+ plain(username: "uname", password: "passwd").process(nil)
+ )
+ end
+
def test_plain_no_null_chars
assert_raise(ArgumentError) { plain("bad\0user", "pass") }
assert_raise(ArgumentError) { plain("user", "bad\0pass") }
+ assert_raise(ArgumentError) { plain(authcid: "bad\0user", password: "p") }
+ assert_raise(ArgumentError) { plain(username: "bad\0user", password: "p") }
+ assert_raise(ArgumentError) { plain(username: "u", password: "bad\0pass") }
+ assert_raise(ArgumentError) { plain("u", "p", authzid: "bad\0authz") }
assert_raise(ArgumentError) { plain("u", "p", authzid: "bad\0authz") }
end
@@ -244,7 +265,11 @@ def test_external_matches_mechanism
def test_external_response
assert_equal("", external.process(nil))
+ assert_equal("", external.process(""))
assert_equal("kwarg", external(authzid: "kwarg").process(nil))
+ assert_equal("username", external(username: "username").process(nil))
+ assert_equal("z", external("p", authzid: "z", username: "u").process(nil))
+ assert_equal("positional", external("positional").process(nil))
end
def test_external_utf8
@@ -256,7 +281,6 @@ def test_external_utf8
def test_external_invalid
assert_raise(ArgumentError) { external(authzid: "bad\0contains NULL") }
assert_raise(ArgumentError) { external(authzid: "invalid utf8\x80") }
- assert_raise(ArgumentError) { external("invalid positional argument") }
end
# ----------------------