Skip to content

Commit 71ea9fd

Browse files
committed
🚧 username vs authzid vs authcid
1 parent c3f7e7c commit 71ea9fd

8 files changed

+227
-108
lines changed

lib/net/imap/sasl/anonymous_authenticator.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,17 @@ class AnonymousAuthenticator
2121
attr_reader :anonymous_message
2222

2323
# :call-seq:
24-
# new(anonymous_message = "", **) -> authenticator
2524
# new(anonymous_message: "", **) -> authenticator
25+
# new(anonymous_message = "", **) -> authenticator
2626
#
2727
# Creates an Authenticator for the "+ANONYMOUS+" SASL mechanism, as
2828
# specified in RFC-4505[https://tools.ietf.org/html/rfc4505]. To use
2929
# this, see Net::IMAP#authenticate or your client's authentication
3030
# method.
3131
#
32-
# #anonymous_message is an optional message which is sent to the server.
33-
# It may be sent as a positional argument or as a keyword argument.
32+
# ==== Parameters
33+
#
34+
# * _optional_ #anonymous_message — a message to send to the server.
3435
#
3536
# Any other keyword arguments are silently ignored.
3637
def initialize(anon_msg = nil, anonymous_message: nil, **)

lib/net/imap/sasl/digest_md5_authenticator.rb

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ class Net::IMAP::SASL::DigestMD5Authenticator
2020
# "Authentication identity" is the generic term used by
2121
# RFC-4422[https://tools.ietf.org/html/rfc4422].
2222
# RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
23-
# that to +authcid+. So +authcid+ is available as an alias for #username.
23+
# this to +authcid+.
2424
attr_reader :username
25+
alias authcid username
2526

2627
# A password or passphrase that matches the #username.
2728
#
@@ -42,34 +43,46 @@ class Net::IMAP::SASL::DigestMD5Authenticator
4243
attr_reader :authzid
4344

4445
# :call-seq:
45-
# new(username, password, authzid = nil, **options) -> authenticator
46+
# new(authcid:, password:, authzid: nil, **options) -> authenticator
4647
# new(username:, password:, authzid: nil, **options) -> authenticator
48+
# new(username, password, authzid = nil, **options) -> authenticator
4749
#
4850
# Creates an Authenticator for the "+DIGEST-MD5+" SASL mechanism.
4951
#
5052
# Called by Net::IMAP#authenticate and similar methods on other clients.
5153
#
5254
# ==== Parameters
5355
#
54-
# * #username — Identity whose #password is used.
55-
# * #password — A password or passphrase associated with this #username.
56-
# * #authzid ― Alternate identity to act as or on behalf of. Optional.
57-
# * +warn_deprecation+ — Set to +false+ to silence the warning.
56+
# * #username ― Authentication identity that is associated with #password.
57+
# * #authcid ― An alias for +username+.
58+
# * #password ― A password or passphrase associated with this #username.
59+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
60+
# * _optional_ +warn_deprecation+ — Set to +false+ to silence the warning.
61+
#
62+
# Any other keyword arguments are silently ignored.
63+
#
64+
# When +authzid+ is not set, the server should derive the authorization
65+
# identity from the authentication identity.
5866
#
5967
# See the documentation for each attribute for more details.
6068
def initialize(user = nil, pass = nil, authz = nil,
61-
username: nil, password: nil, authzid: nil,
62-
warn_deprecation: true, **)
63-
username ||= user or raise ArgumentError, "missing username"
64-
password ||= pass or raise ArgumentError, "missing password"
65-
authzid ||= authz
69+
authcid: nil, username: nil,
70+
authzid: nil,
71+
password: nil,
72+
warn_deprecation: true,
73+
**)
74+
@username = authcid || username || user
75+
@password = password || pass
76+
@authzid = authzid || authz
77+
@username or raise ArgumentError, "missing username (authcid)"
78+
@password or raise ArgumentError, "missing password"
79+
6680
if warn_deprecation
6781
warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
6882
# TODO: recommend SCRAM instead.
6983
end
7084
require "digest/md5"
7185
require "strscan"
72-
@username, @password, @authzid = username, password, authzid
7386
@nc, @stage = {}, STAGE_ONE
7487
end
7588

lib/net/imap/sasl/external_authenticator.rb

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,48 @@ module SASL
1212
# established external to SASL, for example by TLS certificate or IPsec.
1313
class ExternalAuthenticator
1414

15-
# Authorization identity: an identity to act as or on behalf of.
15+
# Authorization identity: an identity to act as or on behalf of. The
16+
# identity form is application protocol specific. If not provided or
17+
# left blank, the server derives an authorization identity from the
18+
# authentication identity. The server is responsible for verifying the
19+
# client's credentials and verifying that the identity it associates
20+
# with the client's authentication identity is allowed to act as (or on
21+
# behalf of) the authorization identity.
22+
#
23+
# For example, an administrator or superuser might take on another role:
24+
#
25+
# imap.authenticate "PLAIN", "root", passwd, authzid: "user"
1626
#
17-
# If not explicitly provided, the server defaults to using the identity
18-
# that was authenticated by the external credentials.
1927
attr_reader :authzid
2028

29+
# An alias for #authzid.
30+
alias username authzid
31+
2132
# :call-seq:
22-
# new(authzid: nil, **) -> authenticator
33+
# new(authzid: nil, **) -> authenticator
34+
# new(username: nil, **) -> authenticator
35+
# new(username = nil, **) -> authenticator
2336
#
2437
# Creates an Authenticator for the "+EXTERNAL+" SASL mechanism, as
2538
# specified in RFC-4422[https://tools.ietf.org/html/rfc4422]. To use
2639
# this, see Net::IMAP#authenticate or your client's authentication
2740
# method.
2841
#
29-
# #authzid is an optional identity to act as or on behalf of.
42+
# ==== Parameters
43+
#
44+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
45+
# * _optional_ #username ― An alias for #authzid.
46+
#
47+
# Note that, unlike some other authenticators, the +username+ keyword
48+
# parameter sets the authorization identity and not the authentication
49+
# identity. The authenticated identity is established for the client
50+
# with the OAuth credential.
3051
#
3152
# Any other keyword parameters are quietly ignored.
32-
def initialize(authzid: nil, **)
33-
@authzid = authzid&.to_str&.encode "UTF-8"
34-
if @authzid&.match?(/\u0000/u) # also validates UTF8 encoding
35-
raise ArgumentError, "contains NULL"
36-
end
53+
def initialize(user = nil, authzid: nil, username: nil, **)
54+
@authzid = authzid || username || user
55+
@authzid &&= @authzid.to_str.encode "UTF-8"
56+
raise ArgumentError, "contains NULL" if @authzid&.match?(/\u0000/u)
3757
@done = false
3858
end
3959

lib/net/imap/sasl/oauthbearer_authenticator.rb

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,20 @@ module SASL
1414
class OAuthAuthenticator
1515
include GS2Header
1616

17-
# Authorization identity: an identity to act as or on behalf of.
17+
# Authorization identity: an identity to act as or on behalf of. The
18+
# identity form is application protocol specific. If not provided or
19+
# left blank, the server derives an authorization identity from the
20+
# authentication identity. The server is responsible for verifying the
21+
# client's credentials and verifying that the identity it associates
22+
# with the client's authentication identity is allowed to act as (or on
23+
# behalf of) the authorization identity.
24+
#
25+
# For example, an administrator or superuser might take on another role:
26+
#
27+
# imap.authenticate "PLAIN", "root", passwd, authzid: "user"
1828
#
19-
# If no explicit authorization identity is provided, it is usually
20-
# derived from the authentication identity. For the OAuth-based
21-
# mechanisms, the authentication identity is the identity established by
22-
# the OAuth credential.
2329
attr_reader :authzid
30+
alias username authzid
2431

2532
# Hostname to which the client connected.
2633
attr_reader :host
@@ -39,6 +46,7 @@ class OAuthAuthenticator
3946

4047
# The query string. (optional)
4148
attr_reader :qs
49+
alias query qs
4250

4351
# Stores the most recent server "challenge". When authentication fails,
4452
# this may hold information about the failure reason, as JSON.
@@ -47,29 +55,44 @@ class OAuthAuthenticator
4755
# Creates an RFC7628[https://tools.ietf.org/html/rfc7628] OAuth
4856
# authenticator.
4957
#
50-
# === Options
58+
# ==== Parameters
59+
#
60+
# See child classes for required parameter(s). The following parameters
61+
# are all optional, but it is worth noting that <b>application protocols
62+
# are allowed to require</b> #authzid (or other parameters, such as
63+
# #host or #port) <b>as are specific server implementations</b>.
5164
#
52-
# See child classes for required configuration parameter(s). The
53-
# following parameters are all optional, but protocols or servers may
54-
# add requirements for #authzid, #host, #port, or any other parameter.
65+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
66+
# * _optional_ #username — An alias for #authzid.
5567
#
56-
# * #authzid ― Identity to act as or on behalf of.
57-
# * #host — Hostname to which the client connected.
58-
# * #port — Service port to which the client connected.
59-
# * #mthd — HTTP method
60-
# * #path — HTTP path data
61-
# * #post — HTTP post data
62-
# * #qs — HTTP query string
68+
# Note that, unlike some other authenticators, the +username+ keyword
69+
# parameter sets the authorization identity and not the authentication
70+
# identity. The authenticated identity is established for the client
71+
# with the #oauth2_token.
72+
# * _optional_ #host — Hostname to which the client connected.
73+
# * _optional_ #port — Service port to which the client connected.
74+
# * _optional_ #mthd — HTTP method
75+
# * _optional_ #path — HTTP path data
76+
# * _optional_ #post — HTTP post data
77+
# * _optional_ #qs — HTTP query string
78+
# * _optional_ #query — An alias for #qs
6379
#
80+
# Any other keyword parameters are quietly ignored.
81+
#
82+
# Note that, unlike some other authenticators, the +username+ keyword
83+
# parameter sets the authorization identity and not the authentication
84+
# identity. The authenticated identity is established for the client
85+
# with the OAuth credential.
6486
def initialize(authzid: nil, host: nil, port: nil,
87+
username: nil, query: nil,
6588
mthd: nil, path: nil, post: nil, qs: nil, **)
66-
@authzid = authzid
89+
@authzid = authzid || username
6790
@host = host
6891
@port = port
6992
@mthd = mthd
7093
@path = path
7194
@post = post
72-
@qs = qs
95+
@qs = qs || query
7396
@done = false
7497
end
7598

@@ -116,36 +139,48 @@ def authorization; raise "must be implemented by subclass" end
116139
# the bearer token.
117140
class OAuthBearerAuthenticator < OAuthAuthenticator
118141

119-
# An OAuth2 bearer token, generally the access token.
142+
# An OAuth 2.0 bearer token. See {RFC-6750}[https://www.rfc-editor.org/rfc/rfc6750]
120143
attr_reader :oauth2_token
121144

122145
# :call-seq:
123-
# new(oauth2_token, **options) -> authenticator
124-
# new(oauth2_token:, **options) -> authenticator
146+
# new(oauth2_token:, authzid: nil, host: nil, port: nil, **options) -> authenticator
147+
# new(oauth2_token:, username: nil, host: nil, port: nil, **options) -> authenticator
148+
# new(oauth2_token, authzid: nil, host: nil, port: nil, **options) -> authenticator
149+
# new(authzid, oauth2_token, host: nil, port: nil, **options) -> authenticator
125150
#
126151
# Creates an Authenticator for the "+OAUTHBEARER+" SASL mechanism.
127152
#
128153
# Called by Net::IMAP#authenticate and similar methods on other clients.
129154
#
130-
# === Options
131-
#
132-
# Only +oauth2_token+ is required by the mechanism, however protocols
133-
# and servers may add requirements for #authzid, #host, #port, or any
134-
# other parameter.
135-
#
136-
# * #oauth2_token — An OAuth2 bearer token or access token. *Required.*
137-
# May be provided as either regular or keyword argument.
138-
# * #authzid ― Identity to act as or on behalf of.
139-
# * #host — Hostname to which the client connected.
140-
# * #port — Service port to which the client connected.
141-
# * See OAuthAuthenticator documentation for less common parameters.
142-
#
143-
def initialize(oauth2_token_arg = nil, oauth2_token: nil, **args, &blk)
144-
super(**args, &blk) # handles authzid, host, port, etc
145-
oauth2_token && oauth2_token_arg and
146-
raise ArgumentError, "conflicting values for oauth2_token"
147-
@oauth2_token = oauth2_token || oauth2_token_arg or
148-
raise ArgumentError, "missing oauth2_token"
155+
# ==== Parameters
156+
#
157+
# * #oauth2_token — An OAuth2 bearer token
158+
#
159+
# All other keyword parameters are passed to
160+
# {super}[rdoc-ref:OAuthAuthenticator::new]. The most common ones are:
161+
#
162+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
163+
# * _optional_ #username — An alias for #authzid.
164+
#
165+
# Note that, unlike some other authenticators, the +username+ keyword
166+
# parameter sets the authorization identity and not the authentication
167+
# identity. The authenticated identity is established for the client
168+
# with the #oauth2_token.
169+
# * _optional_ #host — Hostname to which the client connected.
170+
# * _optional_ #port — Service port to which the client connected.
171+
#
172+
# Only +oauth2_token+ is required by this mechanism, but it is worth
173+
# noting that <b>application protocols are allowed to require</b>
174+
# #authzid (or other parameters, such as #host or #port) <b>as are
175+
# specific server implementations</b>. At the time this was written,
176+
# <em>Gmail and Yahoo Mail both required</em> +authzid+.
177+
#
178+
# See OAuthAuthenticator documentation for other parameters.
179+
def initialize(arg1 = nil, arg2 = nil, oauth2_token: nil, **args, &blk)
180+
username, oauth2_token_arg = arg2.nil? ? [nil, arg1] : [arg1, arg2]
181+
super(username: username, **args, &blk)
182+
@oauth2_token = oauth2_token || oauth2_token_arg
183+
@oauth2_token or raise ArgumentError, "missing oauth2_token"
149184
end
150185

151186
# :call-seq:

lib/net/imap/sasl/plain_authenticator.rb

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Net::IMAP::SASL::PlainAuthenticator
2222
# RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
2323
# this to +authcid+.
2424
attr_reader :username
25+
alias authcid username
2526

2627
# A password or passphrase that matches the #username.
2728
attr_reader :password
@@ -40,34 +41,41 @@ class Net::IMAP::SASL::PlainAuthenticator
4041
attr_reader :authzid
4142

4243
# :call-seq:
43-
# new(username, password, authzid: nil, **) -> authenticator
44+
# new(authcid:, password:, authzid: nil, **) -> authenticator
4445
# new(username:, password:, authzid: nil, **) -> authenticator
46+
# new(username, password, authzid: nil, **) -> authenticator
4547
#
4648
# Creates an Authenticator for the "+PLAIN+" SASL mechanism.
4749
#
4850
# Called by Net::IMAP#authenticate and similar methods on other clients.
4951
#
50-
# === Parameters
52+
# ==== Parameters
53+
#
54+
# * #username ― Authentication identity that is associated with #password.
55+
# * #authcid ― An alias for #username.
56+
# * #password ― A password or passphrase associated with #username.
57+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
5158
#
52-
# * #username ― Identity whose +password+ is used.
53-
# * #password ― Password or passphrase associated with this username+.
54-
# * #authzid ― Alternate identity to act as or on behalf of. Optional.
59+
# Any other keyword parameters are quietly ignored.
60+
#
61+
# When +authzid+ is not set, the server should derive the authorization
62+
# identity from the authentication identity.
5563
#
5664
# See attribute documentation for more details.
5765
def initialize(user = nil, pass = nil,
58-
username: nil, password: nil, authzid: nil, **)
59-
[username, user].compact.count == 1 or
60-
raise ArgumentError, "conflicting values for username"
61-
[password, pass].compact.count == 1 or
62-
raise ArgumentError, "conflicting values for password"
63-
username ||= user or raise ArgumentError, "missing username"
64-
password ||= pass or raise ArgumentError, "missing password"
65-
raise ArgumentError, "username contains NULL" if username.include?(NULL)
66-
raise ArgumentError, "password contains NULL" if password.include?(NULL)
67-
raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL)
68-
@username = username
69-
@password = password
66+
authcid: nil, username: nil,
67+
authzid: nil,
68+
password: nil,
69+
**)
70+
@username = authcid || username || user
71+
@password = password || pass
7072
@authzid = authzid
73+
@username or raise ArgumentError, "missing username (authcid)"
74+
@password or raise ArgumentError, "missing password"
75+
raise ArgumentError, "username contains NULL" if @username.include?(NULL)
76+
raise ArgumentError, "password contains NULL" if @password.include?(NULL)
77+
raise ArgumentError, "authzid contains NULL" if @authzid&.include?(NULL)
78+
7179
@done = false
7280
end
7381

0 commit comments

Comments
 (0)