Skip to content

Commit 7e1c3fb

Browse files
committed
🚚 Move SASL.authenticator registry from Net::IMAP
And change `Net::IMAP::SASL.add_authenticator` to be case insensitive for the mechanism name. The original methods (`Net::IMAP.authenticator` and `Net::IMAP.add_authenticator`) both still work, by delegation to the new methods. But they have been deprecated.
1 parent c655632 commit 7e1c3fb

File tree

10 files changed

+122
-67
lines changed

10 files changed

+122
-67
lines changed

lib/net/imap.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1033,7 +1033,7 @@ def starttls(options = {}, verify = true)
10331033
# will be cached.
10341034
#
10351035
def authenticate(mechanism, ...)
1036-
authenticator = self.class.authenticator(mechanism, ...)
1036+
authenticator = SASL.authenticator(mechanism, ...)
10371037
send_command("AUTHENTICATE", mechanism) do |resp|
10381038
if resp.instance_of?(ContinuationRequest)
10391039
data = authenticator.process(resp.data.text.unpack("m")[0])

lib/net/imap/authenticators.rb

Lines changed: 19 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,30 @@
11
# frozen_string_literal: true
22

3-
# Registry for SASL authenticators used by Net::IMAP.
3+
# Backward compatible delegators from Net::IMAP to Net::IMAP::SASL.
44
module Net::IMAP::Authenticators
55

6-
# Adds an authenticator for Net::IMAP#authenticate to use. +mechanism+ is the
7-
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
8-
# implemented by +authenticator+ (for instance, <tt>"PLAIN"</tt>).
9-
#
10-
# The +authenticator+ must respond to +#new+ (or #call), receiving the
11-
# authenticator configuration and return a configured authentication session.
12-
# The authenticator session must respond to +#process+, receiving the server's
13-
# challenge and returning the client's response.
14-
#
15-
# See PlainAuthenticator, XOauth2Authenticator, and DigestMD5Authenticator for
16-
# examples.
17-
def add_authenticator(auth_type, authenticator)
18-
authenticators[auth_type] = authenticator
6+
# Deprecated. Use Net::IMAP::SASL.add_authenticator instead.
7+
def add_authenticator(...)
8+
warn(
9+
"%s.%s is deprecated. Use %s.%s instead." % [
10+
Net::IMAP, __method__, Net::IMAP::SASL, __method__
11+
],
12+
uplevel: 1
13+
)
14+
Net::IMAP::SASL.add_authenticator(...)
1915
end
2016

21-
# :call-seq:
22-
# authenticator(mechanism, ...) -> authenticator
23-
# authenticator(mech, *creds, **props) {|prop, auth| val } -> authenticator
24-
# authenticator(mechanism, authnid, creds, authzid=nil) -> authenticator
25-
# authenticator(mechanism, **properties) -> authenticator
26-
# authenticator(mechanism) {|propname, authctx| value } -> authenticator
27-
#
28-
# Builds a new authentication session context for +mechanism+.
29-
#
30-
# [Note]
31-
# This method is intended for internal use by connection protocol code only.
32-
# Protocol client users should see refer to their client's documentation,
33-
# e.g. Net::IMAP#authenticate for Net::IMAP.
34-
#
35-
# The call signatures documented for this method are recommendations for
36-
# authenticator implementors. All arguments (other than +mechanism+) are
37-
# forwarded to the registered authenticator's +#new+ (or +#call+) method, and
38-
# each authenticator must document its own arguments.
39-
#
40-
# The returned object represents a single authentication exchange and <em>must
41-
# not</em> be reused for multiple authentication attempts.
42-
def authenticator(mechanism, ...)
43-
auth = authenticators.fetch(mechanism.upcase) do
44-
raise ArgumentError, 'unknown auth type - "%s"' % mechanism
45-
end
46-
auth.respond_to?(:new) ? auth.new(...) : auth.call(...)
47-
end
48-
49-
private
50-
51-
def authenticators
52-
@authenticators ||= {}
17+
# Deprecated. Use Net::IMAP::SASL.authenticator instead.
18+
def authenticator(...)
19+
warn(
20+
"%s.%s is deprecated. Use %s.%s instead." % [
21+
Net::IMAP, __method__, Net::IMAP::SASL, __method__
22+
],
23+
uplevel: 1
24+
)
25+
Net::IMAP::SASL.authenticator(...)
5326
end
5427

5528
end
5629

5730
Net::IMAP.extend Net::IMAP::Authenticators
58-
59-
require_relative "authenticators/plain"
60-
61-
require_relative "authenticators/login"
62-
require_relative "authenticators/cram_md5"
63-
require_relative "authenticators/digest_md5"
64-
require_relative "authenticators/xoauth2"

lib/net/imap/authenticators/cram_md5.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,4 @@ def hmac_md5(text, key)
4747
return Digest::MD5.hexdigest(k_opad + digest)
4848
end
4949

50-
Net::IMAP.add_authenticator "CRAM-MD5", self
5150
end

lib/net/imap/authenticators/digest_md5.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,4 @@ def qdval(k, v)
111111
end
112112
end
113113

114-
Net::IMAP.add_authenticator "DIGEST-MD5", self
115114
end

lib/net/imap/authenticators/login.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,4 @@ def initialize(user, password, warn_deprecation: true, **_ignored)
4242
@state = STATE_USER
4343
end
4444

45-
Net::IMAP.add_authenticator "LOGIN", self
4645
end

lib/net/imap/authenticators/plain.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,4 @@ def initialize(username, password, authzid: nil)
3737
@authzid = authzid
3838
end
3939

40-
Net::IMAP.add_authenticator "PLAIN", self
4140
end

lib/net/imap/authenticators/xoauth2.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,4 @@ def build_oauth2_string(user, oauth2_token)
1616
format("user=%s\1auth=Bearer %s\1\1", user, oauth2_token)
1717
end
1818

19-
Net::IMAP.add_authenticator 'XOAUTH2', self
2019
end

lib/net/imap/sasl.rb

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ module SASL
5959
autoload :ProhibitedCodepoint, stringprep_path
6060
autoload :BidiStringError, stringprep_path
6161

62+
sasl_dir = File.expand_path("sasl", __dir__)
63+
autoload :Authenticators, "#{sasl_dir}/authenticators"
64+
65+
# Authenticators are all lazy loaded
66+
def self.authenticators
67+
@authenticators ||= SASL::Authenticators.new.tap do |registry|
68+
registry.add_authenticator "Plain", PlainAuthenticator
69+
registry.add_authenticator "XOauth2", XOauth2Authenticator
70+
registry.add_authenticator "Login", LoginAuthenticator # deprecated
71+
registry.add_authenticator "Cram-MD5", CramMD5Authenticator # deprecated
72+
registry.add_authenticator "Digest-MD5", DigestMD5Authenticator # deprecated
73+
end
74+
end
75+
76+
# Delegates to authenticators. See Authenticators#authenticator.
77+
def self.authenticator(...) authenticators.authenticator(...) end
78+
79+
# See SASL::add_authenticator
80+
def self.add_authenticator(...) authenticators.add_authenticator(...) end
6281

6382
module_function
6483

@@ -69,5 +88,10 @@ def saslprep(string, **opts)
6988

7089
end
7190
end
72-
7391
end
92+
93+
require_relative "authenticators/plain"
94+
require_relative "authenticators/login"
95+
require_relative "authenticators/cram_md5"
96+
require_relative "authenticators/digest_md5"
97+
require_relative "authenticators/xoauth2"

lib/net/imap/sasl/authenticators.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
module Net::IMAP::SASL
4+
5+
# Registry for SASL mechanism authenticators
6+
class Authenticators
7+
8+
def initialize
9+
@authenticators = {}
10+
end
11+
12+
# Returns the hash of SASL mechanism names to registered authenticators.
13+
def authenticators; @authenticators.dup end
14+
15+
# :call-seq:
16+
# add_authenticator(sasl_mechanism_name, authenticator_class)
17+
# add_authenticator(sasl_mechanism_name, authenticator_proc)
18+
#
19+
# Adds an authenticator for #authenticator to use. +mechanism+ is the
20+
# {SASL mechanism}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml]
21+
# implemented by +authenticator+ (for instance, <tt>"PLAIN"</tt>).
22+
#
23+
# The +authenticator+ must respond to +#new+ (or #call), receiving the
24+
# authenticator configuration and return a configured authentication session.
25+
# The authenticator session must respond to +#process+, receiving the server's
26+
# challenge and returning the client's response.
27+
#
28+
# See PlainAuthenticator, XOauth2Authenticator, and DigestMD5Authenticator for
29+
# examples.
30+
def add_authenticator(mechanism, authenticator)
31+
@authenticators[mechanism.upcase] = authenticator
32+
end
33+
34+
# :call-seq:
35+
# authenticator(mechanism, ...) -> authenticator
36+
# authenticator(mech, *creds, **props) {|prop, auth| val } -> authenticator
37+
# authenticator(mechanism, authnid, creds, authzid=nil) -> authenticator
38+
# authenticator(mechanism, **properties) -> authenticator
39+
# authenticator(mechanism) {|propname, authctx| value } -> authenticator
40+
#
41+
# Builds a new authentication session context for +mechanism+.
42+
#
43+
# [Note]
44+
# This method is intended for internal use by connection protocol code only.
45+
# Protocol client users should see refer to their client's documentation,
46+
# e.g. Net::IMAP#authenticate.
47+
#
48+
# The call signatures documented for this method are generic recommendations
49+
# for authenticators. All arguments (other than +mechanism+) are forwarded to
50+
# the registered authenticator's +#new+ (or +#call+) method, and each
51+
# authenticator must document its own arguments.
52+
#
53+
# The returned object represents a single authentication exchange and <em>must
54+
# not</em> be reused for multiple authentication attempts.
55+
def authenticator(mechanism, ...)
56+
auth = @authenticators.fetch(mechanism.upcase) do
57+
raise ArgumentError, 'unknown auth type - "%s"' % mechanism
58+
end
59+
auth.respond_to?(:new) ? auth.new(...) : auth.call(...)
60+
end
61+
62+
end
63+
64+
end

test/net/imap/test_imap_authenticators.rb

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55

66
class IMAPAuthenticatorsTest < Test::Unit::TestCase
77

8+
def test_net_imap_authenticator_deprecated
9+
assert_warn(/Net::IMAP\.authenticator .+deprecated./) do
10+
Net::IMAP.authenticator("PLAIN", "user", "pass")
11+
end
12+
end
13+
814
# ----------------------
915
# PLAIN
1016
# ----------------------
1117

1218
def plain(*args, **kwargs, &block)
13-
Net::IMAP.authenticator("PLAIN", *args, **kwargs, &block)
19+
Net::IMAP::SASL.authenticator("PLAIN", *args, **kwargs, &block)
1420
end
1521

1622
def test_plain_authenticator_matches_mechanism
@@ -45,7 +51,7 @@ def test_xoauth2
4551
# ----------------------
4652

4753
def login(*args, warn_deprecation: false, **kwargs, &block)
48-
Net::IMAP.authenticator(
54+
Net::IMAP::SASL.authenticator(
4955
"LOGIN", *args, warn_deprecation: warn_deprecation, **kwargs, &block
5056
)
5157
end
@@ -56,7 +62,7 @@ def test_login_authenticator_matches_mechanism
5662

5763
def test_login_authenticator_deprecated
5864
assert_warn(/LOGIN.+deprecated.+PLAIN/) do
59-
Net::IMAP.authenticator("LOGIN", "user", "pass")
65+
Net::IMAP::SASL.authenticator("LOGIN", "user", "pass")
6066
end
6167
end
6268

@@ -71,7 +77,7 @@ def test_login_responses
7177
# ----------------------
7278

7379
def cram_md5(*args, warn_deprecation: false, **kwargs, &block)
74-
Net::IMAP.authenticator(
80+
Net::IMAP::SASL.authenticator(
7581
"CRAM-MD5", *args, warn_deprecation: warn_deprecation, **kwargs, &block
7682
)
7783
end
@@ -82,7 +88,7 @@ def test_cram_md5_authenticator_matches_mechanism
8288

8389
def test_cram_md5_authenticator_deprecated
8490
assert_warn(/CRAM-MD5.+deprecated./) do
85-
Net::IMAP.authenticator("CRAM-MD5", "user", "pass")
91+
Net::IMAP::SASL.authenticator("CRAM-MD5", "user", "pass")
8692
end
8793
end
8894

@@ -97,7 +103,7 @@ def test_cram_md5_authenticator
97103
# ----------------------
98104

99105
def digest_md5(*args, warn_deprecation: false, **kwargs, &block)
100-
Net::IMAP.authenticator(
106+
Net::IMAP::SASL.authenticator(
101107
"DIGEST-MD5", *args, warn_deprecation: warn_deprecation, **kwargs, &block
102108
)
103109
end
@@ -108,7 +114,7 @@ def test_digest_md5_authenticator_matches_mechanism
108114

109115
def test_digest_md5_authenticator_deprecated
110116
assert_warn(/DIGEST-MD5.+deprecated.+RFC6331/) do
111-
Net::IMAP.authenticator("DIGEST-MD5", "user", "pass")
117+
Net::IMAP::SASL.authenticator("DIGEST-MD5", "user", "pass")
112118
end
113119
end
114120

0 commit comments

Comments
 (0)