net-imap 0.4.0 → 0.4.2
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/lib/net/imap/sasl/anonymous_authenticator.rb +3 -2
- data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
- data/lib/net/imap/sasl/authenticators.rb +10 -4
- data/lib/net/imap/sasl/client_adapter.rb +72 -0
- 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/protocol_adapters.rb +45 -0
- data/lib/net/imap/sasl/scram_authenticator.rb +19 -10
- data/lib/net/imap/sasl/xoauth2_authenticator.rb +33 -15
- data/lib/net/imap/sasl.rb +8 -2
- data/lib/net/imap/sasl_adapter.rb +21 -0
- data/lib/net/imap.rb +14 -7
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90ea9e3ff227103752eda7658da755be8f4aabda8fdf60f33379f95c27a1a69c
|
4
|
+
data.tar.gz: b7878cec3bdd9216ee1eda01fe8276c1753f01de6bd875988415665150b5fb00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df018d07e090469ff814af057d94ee4c8b9327071bc173f6f10aa106385071bd39076c334e195f163a670c756b3379a38ffcb00b46248a06b80b3d4aa3ed04c1
|
7
|
+
data.tar.gz: b6215f30153f034d04bfe6d0006da68cb0beee20ec15c2664283d2c766b5a37b9fddfaec82a0fa94a36233e175e81a492829a7094fda4581dc847573e572f0e9
|
@@ -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, **)
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# This API is *experimental*, and may change.
|
8
|
+
#
|
9
|
+
# TODO: catch exceptions in #process and send #cancel_response.
|
10
|
+
# TODO: raise an error if the command succeeds after being canceled.
|
11
|
+
# TODO: use with more clients, to verify the API can accommodate them.
|
12
|
+
#
|
13
|
+
# Create an AuthenticationExchange from a client adapter and a mechanism
|
14
|
+
# authenticator:
|
15
|
+
# def authenticate(mechanism, ...)
|
16
|
+
# authenticator = SASL.authenticator(mechanism, ...)
|
17
|
+
# SASL::AuthenticationExchange.new(
|
18
|
+
# sasl_adapter, mechanism, authenticator
|
19
|
+
# ).authenticate
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# private
|
23
|
+
#
|
24
|
+
# def sasl_adapter = MyClientAdapter.new(self, &method(:send_command))
|
25
|
+
#
|
26
|
+
# Or delegate creation of the authenticator to ::build:
|
27
|
+
# def authenticate(...)
|
28
|
+
# SASL::AuthenticationExchange.build(sasl_adapter, ...)
|
29
|
+
# .authenticate
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# As a convenience, ::authenticate combines ::build and #authenticate:
|
33
|
+
# def authenticate(...)
|
34
|
+
# SASL::AuthenticationExchange.authenticate(sasl_adapter, ...)
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# Likewise, ClientAdapter#authenticate delegates to #authenticate:
|
38
|
+
# def authenticate(...) = sasl_adapter.authenticate(...)
|
39
|
+
#
|
40
|
+
class AuthenticationExchange
|
41
|
+
# Convenience method for <tt>build(...).authenticate</tt>
|
42
|
+
def self.authenticate(...) build(...).authenticate end
|
43
|
+
|
44
|
+
# Use +registry+ to override the global Authenticators registry.
|
45
|
+
def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)
|
46
|
+
authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block)
|
47
|
+
new(client, mechanism, authenticator, sasl_ir: sasl_ir)
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :mechanism, :authenticator
|
51
|
+
|
52
|
+
def initialize(client, mechanism, authenticator, sasl_ir: true)
|
53
|
+
@client = client
|
54
|
+
@mechanism = -mechanism.to_s.upcase.tr(?_, ?-)
|
55
|
+
@authenticator = authenticator
|
56
|
+
@sasl_ir = sasl_ir
|
57
|
+
@processed = false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Call #authenticate to execute an authentication exchange for #client
|
61
|
+
# using #authenticator. Authentication failures will raise an
|
62
|
+
# exception. Any exceptions other than those in RESPONSE_ERRORS will
|
63
|
+
# drop the connection.
|
64
|
+
def authenticate
|
65
|
+
client.run_command(mechanism, initial_response) { process _1 }
|
66
|
+
.tap { raise AuthenticationIncomplete, _1 unless done? }
|
67
|
+
rescue *client.response_errors
|
68
|
+
raise # but don't drop the connection
|
69
|
+
rescue
|
70
|
+
client.drop_connection
|
71
|
+
raise
|
72
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
73
|
+
client.drop_connection!
|
74
|
+
raise
|
75
|
+
end
|
76
|
+
|
77
|
+
def send_initial_response?
|
78
|
+
@sasl_ir &&
|
79
|
+
authenticator.respond_to?(:initial_response?) &&
|
80
|
+
authenticator.initial_response? &&
|
81
|
+
client.sasl_ir_capable? &&
|
82
|
+
client.auth_capable?(mechanism)
|
83
|
+
end
|
84
|
+
|
85
|
+
def done?
|
86
|
+
authenticator.respond_to?(:done?) ? authenticator.done? : @processed
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
attr_reader :client
|
92
|
+
|
93
|
+
def initial_response
|
94
|
+
return unless send_initial_response?
|
95
|
+
client.encode_ir authenticator.process nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def process(challenge)
|
99
|
+
client.encode authenticator.process client.decode challenge
|
100
|
+
ensure
|
101
|
+
@processed = true
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -65,7 +65,7 @@ module Net::IMAP::SASL
|
|
65
65
|
# lazily loaded from <tt>Net::IMAP::SASL::#{name}Authenticator</tt> (case is
|
66
66
|
# preserved and non-alphanumeric characters are removed..
|
67
67
|
def add_authenticator(name, authenticator = nil)
|
68
|
-
key = name.upcase.
|
68
|
+
key = -name.to_s.upcase.tr(?_, ?-)
|
69
69
|
authenticator ||= begin
|
70
70
|
class_name = "#{name.gsub(/[^a-zA-Z0-9]/, "")}Authenticator".to_sym
|
71
71
|
auth_class = nil
|
@@ -79,10 +79,15 @@ module Net::IMAP::SASL
|
|
79
79
|
|
80
80
|
# Removes the authenticator registered for +name+
|
81
81
|
def remove_authenticator(name)
|
82
|
-
key = name.upcase.
|
82
|
+
key = -name.to_s.upcase.tr(?_, ?-)
|
83
83
|
@authenticators.delete(key)
|
84
84
|
end
|
85
85
|
|
86
|
+
def mechanism?(name)
|
87
|
+
key = -name.to_s.upcase.tr(?_, ?-)
|
88
|
+
@authenticators.key?(key)
|
89
|
+
end
|
90
|
+
|
86
91
|
# :call-seq:
|
87
92
|
# authenticator(mechanism, ...) -> auth_session
|
88
93
|
#
|
@@ -100,8 +105,9 @@ module Net::IMAP::SASL
|
|
100
105
|
# only. Protocol client users should see refer to their client's
|
101
106
|
# documentation, e.g. Net::IMAP#authenticate.
|
102
107
|
def authenticator(mechanism, ...)
|
103
|
-
|
104
|
-
|
108
|
+
key = -mechanism.to_s.upcase.tr(?_, ?-)
|
109
|
+
auth = @authenticators.fetch(key) do
|
110
|
+
raise ArgumentError, 'unknown auth type - "%s"' % key
|
105
111
|
end
|
106
112
|
auth.respond_to?(:new) ? auth.new(...) : auth.call(...)
|
107
113
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
# This API is *experimental*, and may change.
|
8
|
+
#
|
9
|
+
# TODO: use with more clients, to verify the API can accommodate them.
|
10
|
+
#
|
11
|
+
# An abstract base class for implementing a SASL authentication exchange.
|
12
|
+
# Different clients will each have their own adapter subclass, overridden
|
13
|
+
# to match their needs.
|
14
|
+
#
|
15
|
+
# Although the default implementations _may_ be sufficient, subclasses
|
16
|
+
# will probably need to override some methods. Additionally, subclasses
|
17
|
+
# may need to include a protocol adapter mixin, if the default
|
18
|
+
# ProtocolAdapters::Generic isn't sufficient.
|
19
|
+
class ClientAdapter
|
20
|
+
include ProtocolAdapters::Generic
|
21
|
+
|
22
|
+
attr_reader :client, :command_proc
|
23
|
+
|
24
|
+
# +command_proc+ can used to avoid exposing private methods on #client.
|
25
|
+
# It should run a command with the arguments sent to it, yield each
|
26
|
+
# continuation payload, respond to the server with the result of each
|
27
|
+
# yield, and return the result. Non-successful results *MUST* raise an
|
28
|
+
# exception. Exceptions in the block *MUST* cause the command to fail.
|
29
|
+
#
|
30
|
+
# Subclasses that override #run_command may use #command_proc for
|
31
|
+
# other purposes.
|
32
|
+
def initialize(client, &command_proc)
|
33
|
+
@client, @command_proc = client, command_proc
|
34
|
+
end
|
35
|
+
|
36
|
+
# Delegates to AuthenticationExchange.authenticate.
|
37
|
+
def authenticate(...) AuthenticationExchange.authenticate(self, ...) end
|
38
|
+
|
39
|
+
# Do the protocol and server both support an initial response?
|
40
|
+
def sasl_ir_capable?; client.sasl_ir_capable? end
|
41
|
+
|
42
|
+
# Does the server advertise support for the mechanism?
|
43
|
+
def auth_capable?(mechanism); client.auth_capable?(mechanism) end
|
44
|
+
|
45
|
+
# Runs the authenticate command with +mechanism+ and +initial_response+.
|
46
|
+
# When +initial_response+ is nil, an initial response must NOT be sent.
|
47
|
+
#
|
48
|
+
# Yields each continuation payload, responds to the server with the
|
49
|
+
# result of each yield, and returns the result. Non-successful results
|
50
|
+
# *MUST* raise an exception. Exceptions in the block *MUST* cause the
|
51
|
+
# command to fail.
|
52
|
+
#
|
53
|
+
# Subclasses that override this may use #command_proc differently.
|
54
|
+
def run_command(mechanism, initial_response = nil, &block)
|
55
|
+
command_proc or raise Error, "initialize with block or override"
|
56
|
+
args = [command_name, mechanism, initial_response].compact
|
57
|
+
command_proc.call(*args, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns an array of server responses errors raised by run_command.
|
61
|
+
# Exceptions in this array won't drop the connection.
|
62
|
+
def response_errors; [] end
|
63
|
+
|
64
|
+
# Drop the connection gracefully.
|
65
|
+
def drop_connection; client.drop_connection end
|
66
|
+
|
67
|
+
# Drop the connection abruptly.
|
68
|
+
def drop_connection!; client.drop_connection! end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -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)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
module SASL
|
6
|
+
|
7
|
+
module ProtocolAdapters
|
8
|
+
# This API is experimental, and may change.
|
9
|
+
module Generic
|
10
|
+
def command_name; "AUTHENTICATE" end
|
11
|
+
def service; raise "Implement in subclass or module" end
|
12
|
+
def host; client.host end
|
13
|
+
def port; client.port end
|
14
|
+
def encode_ir(string) string.empty? ? "=" : encode(string) end
|
15
|
+
def encode(string) [string].pack("m0") end
|
16
|
+
def decode(string) string.unpack1("m0") end
|
17
|
+
def cancel_response; "*" end
|
18
|
+
end
|
19
|
+
|
20
|
+
# See RFC-3501 (IMAP4rev1), RFC-4959 (SASL-IR capability),
|
21
|
+
# and RFC-9051 (IMAP4rev2).
|
22
|
+
module IMAP
|
23
|
+
include Generic
|
24
|
+
def service; "imap" end
|
25
|
+
end
|
26
|
+
|
27
|
+
# See RFC-4954 (AUTH capability).
|
28
|
+
module SMTP
|
29
|
+
include Generic
|
30
|
+
def command_name; "AUTH" end
|
31
|
+
def service; "smtp" end
|
32
|
+
end
|
33
|
+
|
34
|
+
# See RFC-5034 (SASL capability).
|
35
|
+
module POP
|
36
|
+
include Generic
|
37
|
+
def command_name; "AUTH" end
|
38
|
+
def service; "pop" end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -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,9 +6,10 @@
|
|
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.
|
@@ -19,21 +20,34 @@ class Net::IMAP::SASL::XOAuth2Authenticator
|
|
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+
|
data/lib/net/imap/sasl.rb
CHANGED
@@ -135,6 +135,10 @@ module Net
|
|
135
135
|
autoload :BidiStringError, sasl_stringprep_rb
|
136
136
|
|
137
137
|
sasl_dir = File.expand_path("sasl", __dir__)
|
138
|
+
autoload :AuthenticationExchange, "#{sasl_dir}/authentication_exchange"
|
139
|
+
autoload :ClientAdapter, "#{sasl_dir}/client_adapter"
|
140
|
+
autoload :ProtocolAdapters, "#{sasl_dir}/protocol_adapters"
|
141
|
+
|
138
142
|
autoload :Authenticators, "#{sasl_dir}/authenticators"
|
139
143
|
autoload :GS2Header, "#{sasl_dir}/gs2_header"
|
140
144
|
autoload :ScramAlgorithm, "#{sasl_dir}/scram_algorithm"
|
@@ -155,8 +159,10 @@ module Net
|
|
155
159
|
# Returns the default global SASL::Authenticators instance.
|
156
160
|
def self.authenticators; @authenticators ||= Authenticators.new end
|
157
161
|
|
158
|
-
# Delegates to
|
159
|
-
def self.authenticator(
|
162
|
+
# Delegates to <tt>registry.new</tt> See Authenticators#new.
|
163
|
+
def self.authenticator(*args, registry: authenticators, **kwargs, &block)
|
164
|
+
registry.new(*args, **kwargs, &block)
|
165
|
+
end
|
160
166
|
|
161
167
|
# Delegates to ::authenticators. See Authenticators#add_authenticator.
|
162
168
|
def self.add_authenticator(...) authenticators.add_authenticator(...) end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP
|
5
|
+
|
6
|
+
# Experimental
|
7
|
+
class SASLAdapter < SASL::ClientAdapter
|
8
|
+
include SASL::ProtocolAdapters::IMAP
|
9
|
+
|
10
|
+
RESPONSE_ERRORS = [NoResponseError, BadResponseError, ByeResponseError]
|
11
|
+
.freeze
|
12
|
+
|
13
|
+
def response_errors; RESPONSE_ERRORS end
|
14
|
+
def sasl_ir_capable?; client.capable?("SASL-IR") end
|
15
|
+
def auth_capable?(mechanism); client.auth_capable?(mechanism) end
|
16
|
+
def drop_connection; client.logout! end
|
17
|
+
def drop_connection!; client.disconnect end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/net/imap.rb
CHANGED
@@ -127,7 +127,7 @@ module Net
|
|
127
127
|
# end
|
128
128
|
#
|
129
129
|
# # Support for "UTF8=ACCEPT" implies support for "ENABLE"
|
130
|
-
# imap.enable :utf8 if imap.
|
130
|
+
# imap.enable :utf8 if imap.capable?("UTF8=ACCEPT")
|
131
131
|
#
|
132
132
|
# namespaces = imap.namespace if imap.capable?(:namespace)
|
133
133
|
# mbox_prefix = namespaces&.personal&.first&.prefix || ""
|
@@ -662,7 +662,7 @@ module Net
|
|
662
662
|
# * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
|
663
663
|
#
|
664
664
|
class IMAP < Protocol
|
665
|
-
VERSION = "0.4.
|
665
|
+
VERSION = "0.4.2"
|
666
666
|
|
667
667
|
# Aliases for supported capabilities, to be used with the #enable command.
|
668
668
|
ENABLE_ALIASES = {
|
@@ -670,8 +670,9 @@ module Net
|
|
670
670
|
"UTF8=ONLY" => "UTF8=ACCEPT",
|
671
671
|
}.freeze
|
672
672
|
|
673
|
-
autoload :SASL,
|
674
|
-
autoload :
|
673
|
+
autoload :SASL, File.expand_path("imap/sasl", __dir__)
|
674
|
+
autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
|
675
|
+
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
|
675
676
|
|
676
677
|
include MonitorMixin
|
677
678
|
if defined?(OpenSSL::SSL)
|
@@ -1142,7 +1143,10 @@ module Net
|
|
1142
1143
|
end
|
1143
1144
|
|
1144
1145
|
# :call-seq:
|
1145
|
-
# authenticate(mechanism, *,
|
1146
|
+
# authenticate(mechanism, *,
|
1147
|
+
# sasl_ir: true,
|
1148
|
+
# registry: Net::IMAP::SASL.authenticators,
|
1149
|
+
# **, &) -> ok_resp
|
1146
1150
|
#
|
1147
1151
|
# Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
|
1148
1152
|
# to authenticate the client. If successful, the connection enters the
|
@@ -1886,8 +1890,7 @@ module Net
|
|
1886
1890
|
# +attr+ is a list of attributes to fetch; see the documentation
|
1887
1891
|
# for FetchData for a list of valid attributes.
|
1888
1892
|
#
|
1889
|
-
# The return value is an array of FetchData
|
1890
|
-
# (instead of an empty array) if there is no matching message.
|
1893
|
+
# The return value is an array of FetchData.
|
1891
1894
|
#
|
1892
1895
|
# Related: #uid_search, FetchData
|
1893
1896
|
#
|
@@ -2747,6 +2750,10 @@ module Net
|
|
2747
2750
|
end
|
2748
2751
|
end
|
2749
2752
|
|
2753
|
+
def sasl_adapter
|
2754
|
+
SASLAdapter.new(self, &method(:send_command_with_continuations))
|
2755
|
+
end
|
2756
|
+
|
2750
2757
|
#--
|
2751
2758
|
# We could get the saslprep method by extending the SASLprep module
|
2752
2759
|
# directly. It's done indirectly, so SASLprep can be lazily autoloaded,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: net-imap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shugo Maeda
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-10-
|
12
|
+
date: 2023-10-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-protocol
|
@@ -100,7 +100,9 @@ files:
|
|
100
100
|
- lib/net/imap/response_parser/parser_utils.rb
|
101
101
|
- lib/net/imap/sasl.rb
|
102
102
|
- lib/net/imap/sasl/anonymous_authenticator.rb
|
103
|
+
- lib/net/imap/sasl/authentication_exchange.rb
|
103
104
|
- lib/net/imap/sasl/authenticators.rb
|
105
|
+
- lib/net/imap/sasl/client_adapter.rb
|
104
106
|
- lib/net/imap/sasl/cram_md5_authenticator.rb
|
105
107
|
- lib/net/imap/sasl/digest_md5_authenticator.rb
|
106
108
|
- lib/net/imap/sasl/external_authenticator.rb
|
@@ -108,10 +110,12 @@ files:
|
|
108
110
|
- lib/net/imap/sasl/login_authenticator.rb
|
109
111
|
- lib/net/imap/sasl/oauthbearer_authenticator.rb
|
110
112
|
- lib/net/imap/sasl/plain_authenticator.rb
|
113
|
+
- lib/net/imap/sasl/protocol_adapters.rb
|
111
114
|
- lib/net/imap/sasl/scram_algorithm.rb
|
112
115
|
- lib/net/imap/sasl/scram_authenticator.rb
|
113
116
|
- lib/net/imap/sasl/stringprep.rb
|
114
117
|
- lib/net/imap/sasl/xoauth2_authenticator.rb
|
118
|
+
- lib/net/imap/sasl_adapter.rb
|
115
119
|
- lib/net/imap/stringprep.rb
|
116
120
|
- lib/net/imap/stringprep/nameprep.rb
|
117
121
|
- lib/net/imap/stringprep/saslprep.rb
|