net-imap 0.4.1 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of net-imap might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/lib/net/imap/errors.rb +20 -0
- data/lib/net/imap/response_data.rb +46 -6
- data/lib/net/imap/response_parser/parser_utils.rb +14 -4
- data/lib/net/imap/response_parser.rb +609 -368
- data/lib/net/imap/sasl/anonymous_authenticator.rb +3 -2
- data/lib/net/imap/sasl/authenticators.rb +2 -2
- data/lib/net/imap/sasl/cram_md5_authenticator.rb +7 -3
- data/lib/net/imap/sasl/digest_md5_authenticator.rb +20 -8
- data/lib/net/imap/sasl/external_authenticator.rb +26 -5
- data/lib/net/imap/sasl/login_authenticator.rb +7 -3
- data/lib/net/imap/sasl/oauthbearer_authenticator.rb +73 -38
- data/lib/net/imap/sasl/plain_authenticator.rb +19 -11
- data/lib/net/imap/sasl/scram_authenticator.rb +19 -10
- data/lib/net/imap/sasl/xoauth2_authenticator.rb +34 -16
- data/lib/net/imap.rb +26 -28
- data/net-imap.gemspec +3 -2
- data/rakelib/benchmarks.rake +98 -0
- metadata +3 -6
- data/benchmarks/generate_parser_benchmarks +0 -52
- data/benchmarks/parser.yml +0 -578
- data/benchmarks/stringprep.yml +0 -65
- data/benchmarks/table-regexps.yml +0 -39
@@ -29,8 +29,9 @@ module Net
|
|
29
29
|
# this, see Net::IMAP#authenticate or your client's authentication
|
30
30
|
# method.
|
31
31
|
#
|
32
|
-
#
|
33
|
-
#
|
32
|
+
# ==== Parameters
|
33
|
+
#
|
34
|
+
# * _optional_ #anonymous_message — a message to send to the server.
|
34
35
|
#
|
35
36
|
# Any other keyword arguments are silently ignored.
|
36
37
|
def initialize(anon_msg = nil, anonymous_message: nil, **)
|
@@ -58,8 +58,8 @@ module Net::IMAP::SASL
|
|
58
58
|
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
|
59
59
|
# implemented by +authenticator_class+ (for instance, <tt>"PLAIN"</tt>).
|
60
60
|
#
|
61
|
-
# If +mechanism+ refers to an existing authenticator,
|
62
|
-
#
|
61
|
+
# If +mechanism+ refers to an existing authenticator,
|
62
|
+
# the old authenticator will be replaced.
|
63
63
|
#
|
64
64
|
# When only a single argument is given, the authenticator class will be
|
65
65
|
# lazily loaded from <tt>Net::IMAP::SASL::#{name}Authenticator</tt> (case is
|
@@ -14,13 +14,17 @@
|
|
14
14
|
# of cleartext and recommends TLS version 1.2 or greater be used for all
|
15
15
|
# traffic. With TLS +CRAM-MD5+ is okay, but so is +PLAIN+
|
16
16
|
class Net::IMAP::SASL::CramMD5Authenticator
|
17
|
-
def initialize(user
|
17
|
+
def initialize(user = nil, pass = nil,
|
18
|
+
authcid: nil, username: nil,
|
19
|
+
password: nil, secret: nil,
|
20
|
+
warn_deprecation: true,
|
21
|
+
**)
|
18
22
|
if warn_deprecation
|
19
23
|
warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM
|
20
24
|
end
|
21
25
|
require "digest/md5"
|
22
|
-
@user = user
|
23
|
-
@password = password
|
26
|
+
@user = authcid || username || user
|
27
|
+
@password = password || secret || pass
|
24
28
|
@done = false
|
25
29
|
end
|
26
30
|
|
@@ -20,8 +20,9 @@ class Net::IMAP::SASL::DigestMD5Authenticator
|
|
20
20
|
# "Authentication identity" is the generic term used by
|
21
21
|
# RFC-4422[https://tools.ietf.org/html/rfc4422].
|
22
22
|
# RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
|
23
|
-
#
|
23
|
+
# this to +authcid+.
|
24
24
|
attr_reader :username
|
25
|
+
alias authcid username
|
25
26
|
|
26
27
|
# A password or passphrase that matches the #username.
|
27
28
|
#
|
@@ -44,6 +45,7 @@ class Net::IMAP::SASL::DigestMD5Authenticator
|
|
44
45
|
# :call-seq:
|
45
46
|
# new(username, password, authzid = nil, **options) -> authenticator
|
46
47
|
# new(username:, password:, authzid: nil, **options) -> authenticator
|
48
|
+
# new(authcid:, password:, authzid: nil, **options) -> authenticator
|
47
49
|
#
|
48
50
|
# Creates an Authenticator for the "+DIGEST-MD5+" SASL mechanism.
|
49
51
|
#
|
@@ -51,17 +53,27 @@ class Net::IMAP::SASL::DigestMD5Authenticator
|
|
51
53
|
#
|
52
54
|
# ==== Parameters
|
53
55
|
#
|
54
|
-
# * #
|
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
|
+
# * #authcid ― Authentication identity that is associated with #password.
|
58
57
|
#
|
59
|
-
#
|
58
|
+
# #username ― An alias for +authcid+.
|
59
|
+
#
|
60
|
+
# * #password ― A password or passphrase associated with this #authcid.
|
61
|
+
#
|
62
|
+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
|
63
|
+
#
|
64
|
+
# When +authzid+ is not set, the server should derive the authorization
|
65
|
+
# identity from the authentication identity.
|
66
|
+
#
|
67
|
+
# * _optional_ +warn_deprecation+ — Set to +false+ to silence the warning.
|
68
|
+
#
|
69
|
+
# Any other keyword arguments are silently ignored.
|
60
70
|
def initialize(user = nil, pass = nil, authz = nil,
|
61
71
|
username: nil, password: nil, authzid: nil,
|
72
|
+
authcid: nil, secret: nil,
|
62
73
|
warn_deprecation: true, **)
|
63
|
-
username
|
64
|
-
|
74
|
+
username = authcid || username || user or
|
75
|
+
raise ArgumentError, "missing username (authcid)"
|
76
|
+
password ||= secret || pass or raise ArgumentError, "missing password"
|
65
77
|
authzid ||= authz
|
66
78
|
if warn_deprecation
|
67
79
|
warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
|
@@ -12,24 +12,45 @@ module Net
|
|
12
12
|
# established external to SASL, for example by TLS certificate or IPsec.
|
13
13
|
class ExternalAuthenticator
|
14
14
|
|
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"
|
16
26
|
#
|
17
|
-
# If not explicitly provided, the server defaults to using the identity
|
18
|
-
# that was authenticated by the external credentials.
|
19
27
|
attr_reader :authzid
|
28
|
+
alias username authzid
|
20
29
|
|
21
30
|
# :call-seq:
|
22
31
|
# new(authzid: nil, **) -> authenticator
|
32
|
+
# new(username: nil, **) -> authenticator
|
33
|
+
# new(username = nil, **) -> authenticator
|
23
34
|
#
|
24
35
|
# Creates an Authenticator for the "+EXTERNAL+" SASL mechanism, as
|
25
36
|
# specified in RFC-4422[https://tools.ietf.org/html/rfc4422]. To use
|
26
37
|
# this, see Net::IMAP#authenticate or your client's authentication
|
27
38
|
# method.
|
28
39
|
#
|
29
|
-
#
|
40
|
+
# ==== Parameters
|
41
|
+
#
|
42
|
+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
|
43
|
+
#
|
44
|
+
# _optional_ #username ― An alias for #authzid.
|
45
|
+
#
|
46
|
+
# Note that, unlike some other authenticators, +username+ sets the
|
47
|
+
# _authorization_ identity and not the _authentication_ identity. The
|
48
|
+
# authentication identity is established for the client by the
|
49
|
+
# external credentials.
|
30
50
|
#
|
31
51
|
# Any other keyword parameters are quietly ignored.
|
32
|
-
def initialize(authzid: nil, **)
|
52
|
+
def initialize(user = nil, authzid: nil, username: nil, **)
|
53
|
+
authzid ||= username || user
|
33
54
|
@authzid = authzid&.to_str&.encode "UTF-8"
|
34
55
|
if @authzid&.match?(/\u0000/u) # also validates UTF8 encoding
|
35
56
|
raise ArgumentError, "contains NULL"
|
@@ -23,12 +23,16 @@ class Net::IMAP::SASL::LoginAuthenticator
|
|
23
23
|
STATE_DONE = :DONE
|
24
24
|
private_constant :STATE_USER, :STATE_PASSWORD, :STATE_DONE
|
25
25
|
|
26
|
-
def initialize(user
|
26
|
+
def initialize(user = nil, pass = nil,
|
27
|
+
authcid: nil, username: nil,
|
28
|
+
password: nil, secret: nil,
|
29
|
+
warn_deprecation: true,
|
30
|
+
**)
|
27
31
|
if warn_deprecation
|
28
32
|
warn "WARNING: LOGIN SASL mechanism is deprecated. Use PLAIN instead."
|
29
33
|
end
|
30
|
-
@user = user
|
31
|
-
@password = password
|
34
|
+
@user = authcid || username || user
|
35
|
+
@password = password || secret || pass
|
32
36
|
@state = STATE_USER
|
33
37
|
end
|
34
38
|
|
@@ -14,18 +14,25 @@ module Net
|
|
14
14
|
class OAuthAuthenticator
|
15
15
|
include GS2Header
|
16
16
|
|
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"
|
18
28
|
#
|
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.
|
23
29
|
attr_reader :authzid
|
30
|
+
alias username authzid
|
24
31
|
|
25
|
-
# Hostname to which the client connected.
|
32
|
+
# Hostname to which the client connected. (optional)
|
26
33
|
attr_reader :host
|
27
34
|
|
28
|
-
# Service port to which the client connected.
|
35
|
+
# Service port to which the client connected. (optional)
|
29
36
|
attr_reader :port
|
30
37
|
|
31
38
|
# HTTP method. (optional)
|
@@ -39,6 +46,7 @@ module Net
|
|
39
46
|
|
40
47
|
# The query string. (optional)
|
41
48
|
attr_reader :qs
|
49
|
+
alias query qs
|
42
50
|
|
43
51
|
# Stores the most recent server "challenge". When authentication fails,
|
44
52
|
# this may hold information about the failure reason, as JSON.
|
@@ -47,29 +55,42 @@ module Net
|
|
47
55
|
# Creates an RFC7628[https://tools.ietf.org/html/rfc7628] OAuth
|
48
56
|
# authenticator.
|
49
57
|
#
|
50
|
-
#
|
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>.
|
64
|
+
#
|
65
|
+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
|
66
|
+
#
|
67
|
+
# _optional_ #username — An alias for #authzid.
|
51
68
|
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
69
|
+
# Note that, unlike some other authenticators, +username+ sets the
|
70
|
+
# _authorization_ identity and not the _authentication_ identity. The
|
71
|
+
# authentication identity is established for the client by the OAuth
|
72
|
+
# token.
|
55
73
|
#
|
56
|
-
# * #
|
57
|
-
# * #
|
58
|
-
# * #
|
59
|
-
# * #
|
60
|
-
# * #
|
61
|
-
# * #
|
62
|
-
# * #qs — HTTP query string
|
74
|
+
# * _optional_ #host — Hostname to which the client connected.
|
75
|
+
# * _optional_ #port — Service port to which the client connected.
|
76
|
+
# * _optional_ #mthd — HTTP method
|
77
|
+
# * _optional_ #path — HTTP path data
|
78
|
+
# * _optional_ #post — HTTP post data
|
79
|
+
# * _optional_ #qs — HTTP query string
|
63
80
|
#
|
81
|
+
# _optional_ #query — An alias for #qs
|
82
|
+
#
|
83
|
+
# Any other keyword parameters are quietly ignored.
|
64
84
|
def initialize(authzid: nil, host: nil, port: nil,
|
85
|
+
username: nil, query: nil,
|
65
86
|
mthd: nil, path: nil, post: nil, qs: nil, **)
|
66
|
-
@authzid = authzid
|
87
|
+
@authzid = authzid || username
|
67
88
|
@host = host
|
68
89
|
@port = port
|
69
90
|
@mthd = mthd
|
70
91
|
@path = path
|
71
92
|
@post = post
|
72
|
-
@qs = qs
|
93
|
+
@qs = qs || query
|
73
94
|
@done = false
|
74
95
|
end
|
75
96
|
|
@@ -116,35 +137,49 @@ module Net
|
|
116
137
|
# the bearer token.
|
117
138
|
class OAuthBearerAuthenticator < OAuthAuthenticator
|
118
139
|
|
119
|
-
# An
|
140
|
+
# An OAuth 2.0 bearer token. See {RFC-6750}[https://www.rfc-editor.org/rfc/rfc6750]
|
120
141
|
attr_reader :oauth2_token
|
142
|
+
alias secret oauth2_token
|
121
143
|
|
122
144
|
# :call-seq:
|
123
|
-
# new(oauth2_token,
|
124
|
-
# new(oauth2_token
|
145
|
+
# new(oauth2_token, **options) -> authenticator
|
146
|
+
# new(authzid, oauth2_token, **options) -> authenticator
|
147
|
+
# new(oauth2_token:, **options) -> authenticator
|
125
148
|
#
|
126
149
|
# Creates an Authenticator for the "+OAUTHBEARER+" SASL mechanism.
|
127
150
|
#
|
128
151
|
# Called by Net::IMAP#authenticate and similar methods on other clients.
|
129
152
|
#
|
130
|
-
#
|
153
|
+
# ==== Parameters
|
154
|
+
#
|
155
|
+
# * #oauth2_token — An OAuth2 bearer token
|
156
|
+
#
|
157
|
+
# All other keyword parameters are passed to
|
158
|
+
# {super}[rdoc-ref:OAuthAuthenticator::new] (see OAuthAuthenticator).
|
159
|
+
# The most common ones are:
|
160
|
+
#
|
161
|
+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
|
162
|
+
#
|
163
|
+
# _optional_ #username — An alias for #authzid.
|
131
164
|
#
|
132
|
-
#
|
133
|
-
# and
|
134
|
-
#
|
165
|
+
# Note that, unlike some other authenticators, +username+ sets the
|
166
|
+
# _authorization_ identity and not the _authentication_ identity. The
|
167
|
+
# authentication identity is established for the client by
|
168
|
+
# #oauth2_token.
|
135
169
|
#
|
136
|
-
# * #
|
137
|
-
#
|
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.
|
170
|
+
# * _optional_ #host — Hostname to which the client connected.
|
171
|
+
# * _optional_ #port — Service port to which the client connected.
|
142
172
|
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
173
|
+
# Although only oauth2_token is required by this mechanism, it is worth
|
174
|
+
# noting that <b><em>application protocols are allowed to
|
175
|
+
# require</em></b> #authzid (<em>or other parameters, such as</em> #host
|
176
|
+
# _or_ #port) <b><em>as are specific server implementations</em></b>.
|
177
|
+
def initialize(arg1 = nil, arg2 = nil,
|
178
|
+
oauth2_token: nil, secret: nil,
|
179
|
+
**args, &blk)
|
180
|
+
username, oauth2_token_arg = arg2.nil? ? [nil, arg1] : [arg1, arg2]
|
181
|
+
super(username: username, **args, &blk)
|
182
|
+
@oauth2_token = oauth2_token || secret || oauth2_token_arg or
|
148
183
|
raise ArgumentError, "missing oauth2_token"
|
149
184
|
end
|
150
185
|
|
@@ -22,9 +22,11 @@ class Net::IMAP::SASL::PlainAuthenticator
|
|
22
22
|
# RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
|
23
23
|
# this to +authcid+.
|
24
24
|
attr_reader :username
|
25
|
+
alias authcid username
|
25
26
|
|
26
27
|
# A password or passphrase that matches the #username.
|
27
28
|
attr_reader :password
|
29
|
+
alias secret password
|
28
30
|
|
29
31
|
# Authorization identity: an identity to act as or on behalf of. The identity
|
30
32
|
# form is application protocol specific. If not provided or left blank, the
|
@@ -42,26 +44,32 @@ class Net::IMAP::SASL::PlainAuthenticator
|
|
42
44
|
# :call-seq:
|
43
45
|
# new(username, password, authzid: nil, **) -> authenticator
|
44
46
|
# new(username:, password:, authzid: nil, **) -> authenticator
|
47
|
+
# new(authcid:, password:, authzid: nil, **) -> authenticator
|
45
48
|
#
|
46
49
|
# Creates an Authenticator for the "+PLAIN+" SASL mechanism.
|
47
50
|
#
|
48
51
|
# Called by Net::IMAP#authenticate and similar methods on other clients.
|
49
52
|
#
|
50
|
-
#
|
53
|
+
# ==== Parameters
|
51
54
|
#
|
52
|
-
# * #
|
53
|
-
# * #password ― Password or passphrase associated with this username+.
|
54
|
-
# * #authzid ― Alternate identity to act as or on behalf of. Optional.
|
55
|
+
# * #authcid ― Authentication identity that is associated with #password.
|
55
56
|
#
|
56
|
-
#
|
57
|
+
# #username ― An alias for #authcid.
|
58
|
+
#
|
59
|
+
# * #password ― A password or passphrase associated with the #authcid.
|
60
|
+
#
|
61
|
+
# * _optional_ #authzid ― Authorization identity to act as or on behalf of.
|
62
|
+
#
|
63
|
+
# When +authzid+ is not set, the server should derive the authorization
|
64
|
+
# identity from the authentication identity.
|
65
|
+
#
|
66
|
+
# Any other keyword parameters are quietly ignored.
|
57
67
|
def initialize(user = nil, pass = nil,
|
68
|
+
authcid: nil, secret: nil,
|
58
69
|
username: nil, password: nil, authzid: nil, **)
|
59
|
-
|
60
|
-
raise ArgumentError, "
|
61
|
-
|
62
|
-
raise ArgumentError, "conflicting values for password"
|
63
|
-
username ||= user or raise ArgumentError, "missing username"
|
64
|
-
password ||= pass or raise ArgumentError, "missing password"
|
70
|
+
username ||= authcid || user or
|
71
|
+
raise ArgumentError, "missing username (authcid)"
|
72
|
+
password ||= secret || pass or raise ArgumentError, "missing password"
|
65
73
|
raise ArgumentError, "username contains NULL" if username.include?(NULL)
|
66
74
|
raise ArgumentError, "password contains NULL" if password.include?(NULL)
|
67
75
|
raise ArgumentError, "authzid contains NULL" if authzid&.include?(NULL)
|
@@ -60,6 +60,7 @@ module Net
|
|
60
60
|
# :call-seq:
|
61
61
|
# new(username, password, **options) -> auth_ctx
|
62
62
|
# new(username:, password:, **options) -> auth_ctx
|
63
|
+
# new(authcid:, password:, **options) -> auth_ctx
|
63
64
|
#
|
64
65
|
# Creates an authenticator for one of the "+SCRAM-*+" SASL mechanisms.
|
65
66
|
# Each subclass defines #digest to match a specific mechanism.
|
@@ -68,39 +69,47 @@ module Net
|
|
68
69
|
#
|
69
70
|
# === Parameters
|
70
71
|
#
|
71
|
-
# * #
|
72
|
+
# * #authcid ― Identity whose #password is used.
|
73
|
+
#
|
74
|
+
# #username - An alias for #authcid.
|
72
75
|
# * #password ― Password or passphrase associated with this #username.
|
73
|
-
# * #authzid ― Alternate identity to act as or on behalf of.
|
74
|
-
# * #min_iterations - Overrides the default value (4096).
|
76
|
+
# * _optional_ #authzid ― Alternate identity to act as or on behalf of.
|
77
|
+
# * _optional_ #min_iterations - Overrides the default value (4096).
|
75
78
|
#
|
76
|
-
#
|
79
|
+
# Any other keyword parameters are quietly ignored.
|
77
80
|
def initialize(username_arg = nil, password_arg = nil,
|
78
|
-
|
81
|
+
authcid: nil, username: nil,
|
82
|
+
authzid: nil,
|
83
|
+
password: nil, secret: nil,
|
79
84
|
min_iterations: 4096, # see both RFC5802 and RFC7677
|
80
85
|
cnonce: nil, # must only be set in tests
|
81
86
|
**options)
|
82
87
|
@username = username || username_arg || authcid or
|
83
88
|
raise ArgumentError, "missing username (authcid)"
|
84
|
-
|
85
|
-
raise ArgumentError, "conflicting values for username (authcid)"
|
86
|
-
@password = password || password_arg or
|
89
|
+
@password = password || secret || password_arg or
|
87
90
|
raise ArgumentError, "missing password"
|
88
|
-
[password, password_arg].compact.count == 1 or
|
89
|
-
raise ArgumentError, "conflicting values for password"
|
90
91
|
@authzid = authzid
|
91
92
|
|
92
93
|
@min_iterations = Integer min_iterations
|
93
94
|
@min_iterations.positive? or
|
94
95
|
raise ArgumentError, "min_iterations must be positive"
|
96
|
+
|
95
97
|
@cnonce = cnonce || SecureRandom.base64(32)
|
96
98
|
end
|
97
99
|
|
98
100
|
# Authentication identity: the identity that matches the #password.
|
101
|
+
#
|
102
|
+
# RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+.
|
103
|
+
# "Authentication identity" is the generic term used by
|
104
|
+
# RFC-4422[https://tools.ietf.org/html/rfc4422].
|
105
|
+
# RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
|
106
|
+
# this to +authcid+.
|
99
107
|
attr_reader :username
|
100
108
|
alias authcid username
|
101
109
|
|
102
110
|
# A password or passphrase that matches the #username.
|
103
111
|
attr_reader :password
|
112
|
+
alias secret password
|
104
113
|
|
105
114
|
# Authorization identity: an identity to act as or on behalf of. The
|
106
115
|
# identity form is application protocol specific. If not provided or
|
@@ -6,34 +6,48 @@
|
|
6
6
|
# Google[https://developers.google.com/gmail/imap/xoauth2-protocol] and
|
7
7
|
# Microsoft[https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth].
|
8
8
|
#
|
9
|
-
# This mechanism requires an OAuth2
|
10
|
-
# with the appropriate OAuth2 scopes to access
|
11
|
-
# standardized---consult each
|
9
|
+
# This mechanism requires an OAuth2 access token which has been authorized
|
10
|
+
# with the appropriate OAuth2 scopes to access the user's services. Most of
|
11
|
+
# these scopes are not standardized---consult each service provider's
|
12
|
+
# documentation for their scopes.
|
12
13
|
#
|
13
14
|
# Although this mechanism was never standardized and has been obsoleted by
|
14
15
|
# "+OAUTHBEARER+", it is still very widely supported.
|
15
16
|
#
|
16
|
-
# See Net::IMAP::SASL::
|
17
|
+
# See Net::IMAP::SASL::OAuthBearerAuthenticator.
|
17
18
|
class Net::IMAP::SASL::XOAuth2Authenticator
|
18
19
|
|
19
20
|
# It is unclear from {Google's original XOAUTH2
|
20
21
|
# documentation}[https://developers.google.com/gmail/imap/xoauth2-protocol],
|
21
22
|
# whether "User" refers to the authentication identity (+authcid+) or the
|
22
|
-
# authorization identity (+authzid+).
|
23
|
+
# authorization identity (+authzid+). The authentication identity is
|
24
|
+
# established for the client by the OAuth token, so it seems that +username+
|
25
|
+
# must be the authorization identity.
|
23
26
|
#
|
24
27
|
# {Microsoft's documentation for shared
|
25
28
|
# 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]
|
26
|
-
#
|
29
|
+
# _clearly_ indicates that the Office 365 server interprets it as the
|
27
30
|
# authorization identity.
|
31
|
+
#
|
32
|
+
# Although they _should_ validate that the token has been authorized to access
|
33
|
+
# the service for +username+, _some_ servers appear to ignore this field,
|
34
|
+
# relying only the identity and scope authorized by the token.
|
28
35
|
attr_reader :username
|
29
36
|
|
37
|
+
# Note that, unlike most other authenticators, #username is an alias for the
|
38
|
+
# authorization identity and not the authentication identity. The
|
39
|
+
# authenticated identity is established for the client by the #oauth2_token.
|
40
|
+
alias authzid username
|
41
|
+
|
30
42
|
# An OAuth2 access token which has been authorized with the appropriate OAuth2
|
31
43
|
# scopes to use the service for #username.
|
32
44
|
attr_reader :oauth2_token
|
45
|
+
alias secret oauth2_token
|
33
46
|
|
34
47
|
# :call-seq:
|
35
48
|
# new(username, oauth2_token, **) -> authenticator
|
36
49
|
# new(username:, oauth2_token:, **) -> authenticator
|
50
|
+
# new(authzid:, oauth2_token:, **) -> authenticator
|
37
51
|
#
|
38
52
|
# Creates an Authenticator for the "+XOAUTH2+" SASL mechanism, as specified by
|
39
53
|
# Google[https://developers.google.com/gmail/imap/xoauth2-protocol],
|
@@ -43,26 +57,30 @@ class Net::IMAP::SASL::XOAuth2Authenticator
|
|
43
57
|
# === Properties
|
44
58
|
#
|
45
59
|
# * #username --- the username for the account being accessed.
|
60
|
+
#
|
61
|
+
# #authzid --- an alias for #username.
|
62
|
+
#
|
63
|
+
# Note that, unlike some other authenticators, +username+ sets the
|
64
|
+
# _authorization_ identity and not the _authentication_ identity. The
|
65
|
+
# authenticated identity is established for the client with the OAuth token.
|
66
|
+
#
|
46
67
|
# * #oauth2_token --- An OAuth2.0 access token which is authorized to access
|
47
68
|
# the service for #username.
|
48
69
|
#
|
49
|
-
#
|
50
|
-
def initialize(user = nil, token = nil, username: nil, oauth2_token: nil,
|
51
|
-
|
52
|
-
|
53
|
-
|
70
|
+
# Any other keyword parameters are quietly ignored.
|
71
|
+
def initialize(user = nil, token = nil, username: nil, oauth2_token: nil,
|
72
|
+
authzid: nil, secret: nil, **)
|
73
|
+
@username = authzid || username || user or
|
74
|
+
raise ArgumentError, "missing username (authzid)"
|
75
|
+
@oauth2_token = oauth2_token || secret || token or
|
54
76
|
raise ArgumentError, "missing oauth2_token"
|
55
|
-
[username, user].compact.count == 1 or
|
56
|
-
raise ArgumentError, "conflicting values for username"
|
57
|
-
[oauth2_token, token].compact.count == 1 or
|
58
|
-
raise ArgumentError, "conflicting values for oauth2_token"
|
59
77
|
@done = false
|
60
78
|
end
|
61
79
|
|
62
80
|
# :call-seq:
|
63
81
|
# initial_response? -> true
|
64
82
|
#
|
65
|
-
# +
|
83
|
+
# +XOAUTH2+ can send an initial client response.
|
66
84
|
def initial_response?; true end
|
67
85
|
|
68
86
|
# Returns the XOAUTH2 formatted response, which combines the +username+
|