Skip to content

Commit 2576cad

Browse files
committed
✨ Aliases for username <=> authcid or authzid
Some of the RFCs are clearer than others about the distinction between authentication identity and authorization identity. And `username` is used by some specifications to mean `authcid` and by others to mean `authzid`. To clarify the different roles of `username`, I've updated every relevant mechanism to have `authcid` and/or `authzid` that use it as an alias to username (or vice versa). Additionally, the docs for many mechanisms has been improved.
1 parent 25bc8e3 commit 2576cad

8 files changed

+181
-61
lines changed

lib/net/imap/sasl/anonymous_authenticator.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ class AnonymousAuthenticator
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: 16 additions & 6 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
#
@@ -44,23 +45,32 @@ class Net::IMAP::SASL::DigestMD5Authenticator
4445
# :call-seq:
4546
# new(username, password, authzid = nil, **options) -> authenticator
4647
# new(username:, password:, authzid: nil, **options) -> authenticator
48+
# new(authcid:, 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,
6169
username: nil, password: nil, authzid: nil,
70+
authcid: nil,
6271
warn_deprecation: true, **)
63-
username ||= user or raise ArgumentError, "missing username"
72+
username = authcid || username || user or
73+
raise ArgumentError, "missing username (authcid)"
6474
password ||= pass or raise ArgumentError, "missing password"
6575
authzid ||= authz
6676
if warn_deprecation

lib/net/imap/sasl/external_authenticator.rb

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,44 @@ 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
28+
alias username authzid
2029

2130
# :call-seq:
2231
# new(authzid: nil, **) -> authenticator
32+
# new(username: nil, **) -> authenticator
33+
# new(username = nil, **) -> authenticator
2334
#
2435
# Creates an Authenticator for the "+EXTERNAL+" SASL mechanism, as
2536
# specified in RFC-4422[https://tools.ietf.org/html/rfc4422]. To use
2637
# this, see Net::IMAP#authenticate or your client's authentication
2738
# method.
2839
#
29-
# #authzid is an optional identity to act as or on behalf of.
40+
# ==== Parameters
41+
#
42+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
43+
# * _optional_ #username ― An alias for #authzid.
44+
#
45+
# Note that, unlike some other authenticators, the +username+ keyword
46+
# parameter sets the authorization identity and not the authentication
47+
# identity. The authenticated identity is already established for the
48+
# client by the external credentials.
3049
#
3150
# Any other keyword parameters are quietly ignored.
32-
def initialize(authzid: nil, **)
51+
def initialize(user = nil, authzid: nil, username: nil, **)
52+
authzid ||= username || user
3353
@authzid = authzid&.to_str&.encode "UTF-8"
3454
if @authzid&.match?(/\u0000/u) # also validates UTF8 encoding
3555
raise ArgumentError, "contains NULL"

lib/net/imap/sasl/oauthbearer_authenticator.rb

Lines changed: 64 additions & 33 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,39 @@ 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 with the OAuth
71+
# 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.
6481
def initialize(authzid: nil, host: nil, port: nil,
82+
username: nil, query: nil,
6583
mthd: nil, path: nil, post: nil, qs: nil, **)
66-
@authzid = authzid
84+
@authzid = authzid || username
6785
@host = host
6886
@port = port
6987
@mthd = mthd
7088
@path = path
7189
@post = post
72-
@qs = qs
90+
@qs = qs || query
7391
@done = false
7492
end
7593

@@ -116,32 +134,45 @@ def authorization; raise "must be implemented by subclass" end
116134
# the bearer token.
117135
class OAuthBearerAuthenticator < OAuthAuthenticator
118136

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

122140
# :call-seq:
123-
# new(oauth2_token, **options) -> authenticator
124-
# new(oauth2_token:, **options) -> authenticator
141+
# new(oauth2_token:, authzid: nil, host: nil, port: nil, **options) -> authenticator
142+
# new(oauth2_token:, username: nil, host: nil, port: nil, **options) -> authenticator
143+
# new(oauth2_token, authzid: nil, host: nil, port: nil, **options) -> authenticator
144+
# new(authzid, oauth2_token, host: nil, port: nil, **options) -> authenticator
125145
#
126146
# Creates an Authenticator for the "+OAUTHBEARER+" SASL mechanism.
127147
#
128148
# Called by Net::IMAP#authenticate and similar methods on other clients.
129149
#
130-
# === Options
150+
# ==== Parameters
151+
#
152+
# * #oauth2_token — An OAuth2 bearer token
153+
#
154+
# All other keyword parameters are passed to
155+
# {super}[rdoc-ref:OAuthAuthenticator::new]. The most common ones are:
156+
#
157+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
158+
# * _optional_ #username — An alias for #authzid.
131159
#
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.
160+
# Note that, unlike some other authenticators, the +username+ keyword
161+
# parameter sets the authorization identity and not the authentication
162+
# identity. The authenticated identity is established for the client
163+
# with the #oauth2_token.
164+
# * _optional_ #host — Hostname to which the client connected.
165+
# * _optional_ #port — Service port to which the client connected.
135166
#
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.
167+
# Only +oauth2_token+ is required by this mechanism, but it is worth
168+
# noting that <b>application protocols are allowed to require</b>
169+
# #authzid (or other parameters, such as #host or #port) <b>as are
170+
# specific server implementations</b>.
142171
#
143-
def initialize(oauth2_token_arg = nil, oauth2_token: nil, **args, &blk)
144-
super(**args, &blk) # handles authzid, host, port, etc
172+
# See OAuthAuthenticator documentation for other parameters.
173+
def initialize(arg1 = nil, arg2 = nil, oauth2_token: nil, **args, &blk)
174+
username, oauth2_token_arg = arg2.nil? ? [nil, arg1] : [arg1, arg2]
175+
super(username: username, **args, &blk)
145176
oauth2_token && oauth2_token_arg and
146177
raise ArgumentError, "conflicting values for oauth2_token"
147178
@oauth2_token = oauth2_token || oauth2_token_arg or

lib/net/imap/sasl/plain_authenticator.rb

Lines changed: 18 additions & 8 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
@@ -42,25 +43,34 @@ class Net::IMAP::SASL::PlainAuthenticator
4243
# :call-seq:
4344
# new(username, password, authzid: nil, **) -> authenticator
4445
# new(username:, password:, authzid: nil, **) -> authenticator
46+
# new(authcid:, 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
5153
#
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.
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.
58+
#
59+
# When +authzid+ is not set, the server should derive the authorization
60+
# identity from the authentication identity.
61+
#
62+
# Any other keyword parameters are quietly ignored.
5563
#
5664
# See attribute documentation for more details.
5765
def initialize(user = nil, pass = nil,
66+
authcid: nil,
5867
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
68+
[authcid, username, user].compact.count <= 1 or
69+
raise ArgumentError, "conflicting values for username (authcid)"
70+
[password, pass].compact.count <= 1 or
6271
raise ArgumentError, "conflicting values for password"
63-
username ||= user or raise ArgumentError, "missing username"
72+
username ||= authcid || user or
73+
raise ArgumentError, "missing username (authcid)"
6474
password ||= pass or raise ArgumentError, "missing password"
6575
raise ArgumentError, "username contains NULL" if username.include?(NULL)
6676
raise ArgumentError, "password contains NULL" if password.include?(NULL)

lib/net/imap/sasl/scram_authenticator.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class ScramAuthenticator
6060
# :call-seq:
6161
# new(username, password, **options) -> auth_ctx
6262
# new(username:, password:, **options) -> auth_ctx
63+
# new(authcid:, password:, **options) -> auth_ctx
6364
#
6465
# Creates an authenticator for one of the "+SCRAM-*+" SASL mechanisms.
6566
# Each subclass defines #digest to match a specific mechanism.
@@ -68,14 +69,17 @@ class ScramAuthenticator
6869
#
6970
# === Parameters
7071
#
71-
# * #username ― Identity whose #password is used. Aliased as #authcid.
72+
# * #username ― Identity whose #password is used.
73+
# * #authcid - An alias for #username.
7274
# * #password ― Password or passphrase associated with this #username.
7375
# * #authzid ― Alternate identity to act as or on behalf of. Optional.
7476
# * #min_iterations - Overrides the default value (4096). Optional.
7577
#
7678
# See the documentation on the corresponding attributes for more.
7779
def initialize(username_arg = nil, password_arg = nil,
78-
username: nil, password: nil, authcid: nil, authzid: nil,
80+
authcid: nil, username: nil,
81+
authzid: nil,
82+
password: nil,
7983
min_iterations: 4096, # see both RFC5802 and RFC7677
8084
cnonce: nil, # must only be set in tests
8185
**options)
@@ -92,10 +96,17 @@ def initialize(username_arg = nil, password_arg = nil,
9296
@min_iterations = Integer min_iterations
9397
@min_iterations.positive? or
9498
raise ArgumentError, "min_iterations must be positive"
99+
95100
@cnonce = cnonce || SecureRandom.base64(32)
96101
end
97102

98103
# Authentication identity: the identity that matches the #password.
104+
#
105+
# RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+.
106+
# "Authentication identity" is the generic term used by
107+
# RFC-4422[https://tools.ietf.org/html/rfc4422].
108+
# RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
109+
# this to +authcid+.
99110
attr_reader :username
100111
alias authcid username
101112

0 commit comments

Comments
 (0)