Skip to content

Commit d36f8b9

Browse files
committed
♻️ SASL PLAIN: SASL-IR, refactor, move, document
* ✨ Add `initial_response? => true` * ♻️ Inherit from Authenticator base class. * 🚚 Move to sasl directory and SASL namespace. * 🗑️ Deprecate original constant name.
1 parent d92d2c7 commit d36f8b9

File tree

6 files changed

+131
-45
lines changed

6 files changed

+131
-45
lines changed

lib/net/imap.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -988,7 +988,7 @@ def starttls(options = {}, verify = true)
988988
# n.b. the following table is copied from Net::IMAP::SASL::Authenticator.
989989
#++
990990
#
991-
# +PLAIN+:: See PlainAuthenticator.
991+
# +PLAIN+:: See SASL::PlainAuthenticator.
992992
# Login using clear-text username and password.
993993
#
994994
# +XOAUTH2+:: See XOauth2Authenticator.

lib/net/imap/authenticators.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def authenticators
5656

5757
Net::IMAP.extend Net::IMAP::Authenticators
5858

59-
require_relative "authenticators/plain"
59+
require_relative "sasl/plain_authenticator"
6060

6161
require_relative "authenticators/login"
6262
require_relative "authenticators/cram_md5"

lib/net/imap/authenticators/plain.rb

Lines changed: 0 additions & 41 deletions
This file was deleted.

lib/net/imap/sasl/authenticator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module SASL
1919
# n.b. the following table is copied to Net::IMAP#authenticate.
2020
#++
2121
#
22-
# +PLAIN+:: See PlainAuthenticator.
22+
# +PLAIN+:: See SASL::PlainAuthenticator.
2323
# Login using clear-text username and password.
2424
#
2525
# +XOAUTH2+:: See XOauth2Authenticator.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "authenticator"
4+
5+
module Net
6+
class IMAP < Protocol
7+
module SASL
8+
9+
# Authenticator for the "+PLAIN+" SASL mechanism, specified in
10+
# RFC-4616[https://tools.ietf.org/html/rfc4616]. Use via
11+
# Net::IMAP#authenticate.
12+
#
13+
# +PLAIN+ authentication sends the password in cleartext.
14+
# RFC-3501[https://tools.ietf.org/html/rfc3501] encourages servers to
15+
# disable cleartext authentication until after TLS has been negotiated.
16+
# RFC-8314[https://tools.ietf.org/html/rfc8314] recommends TLS version 1.2
17+
# or greater be used for all traffic, and deprecate cleartext access ASAP.
18+
# +PLAIN+ can be secured by TLS encryption.
19+
class PlainAuthenticator < Authenticator
20+
register Net::IMAP
21+
22+
##
23+
# :call-seq:
24+
# new(username, password, authzid = nil, **) -> auth_ctx
25+
# new(authcid:, password:, authzid: nil, **) -> auth_ctx
26+
# new(**) {|propname, auth_ctx| propval } -> auth_ctx
27+
#
28+
# Creates an Authenticator for the "+PLAIN+" SASL mechanism.
29+
#
30+
# Called by Net::IMAP#authenticate and similar methods on other clients.
31+
#
32+
# === Properties
33+
#
34+
# * #authcid ― Identity whose #password is used. Aliased as #username.
35+
# * #password ― Password or passphrase associated with this #authcid.
36+
# * #authzid ― Alternate identity to act as or on behalf of. Optional.
37+
#
38+
# See the documentation on each property method for more details.
39+
#
40+
# All three properties may be sent as either positional or keyword
41+
# arguments. See Net::IMAP::SASL::Authenticator@Properties for a
42+
# detailed description of property assignment, lazy loading, and
43+
# callbacks.
44+
#
45+
def initialize(username_arg = nil, password_arg = nil, authzid_arg = nil,
46+
authcid: nil, password: nil, authzid: nil,
47+
username: nil, # alias for authcid
48+
**, &callback)
49+
super
50+
propinit(:authcid, authcid, username, username_arg)
51+
propinit(:authzid, authzid, authzid_arg)
52+
propinit(:password, password, password_arg)
53+
end
54+
55+
# :call-seq:
56+
# initial_response? -> true
57+
#
58+
# +PLAIN+ can send an initial client response.
59+
def initial_response?; true end
60+
61+
##
62+
# method: authcid
63+
# :call-seq: authcid -> string or nil
64+
#
65+
# Authentication identity: the identity that matches the #password.
66+
#
67+
# RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+.
68+
# "Authentication identity" is the generic term used by
69+
# RFC-4422[https://tools.ietf.org/html/rfc4422].
70+
# RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs
71+
# abbreviate to +authcid+. #username is available as an alias for
72+
# #authcid, but only <tt>:authcid</tt> will be sent to callbacks.
73+
property :authcid
74+
alias username authcid
75+
76+
##
77+
# method: password
78+
# :call-seq: password -> string or nil
79+
#
80+
# A password or passphrase that matches the #authcid.
81+
property :password
82+
83+
##
84+
# method: authzid
85+
# :call-seq: authzid -> string or nil
86+
#
87+
# Authorization identity: an identity to act as or on behalf of. The
88+
# identity form is application protocol specific. If not provided or
89+
# left blank, the server derives an authorization identity from the
90+
# authentication identity. The server is responsible for verifying the
91+
# client's credentials and verifying that the identity it associates with
92+
# the client's authentication identity is allowed to act as (or on behalf
93+
# of) the authorization identity.
94+
#
95+
# For example, an administrator or superuser might take on another role:
96+
#
97+
# imap.authenticate "PLAIN", "root", ->{passwd}, authzid: "user"
98+
#
99+
property :authzid
100+
101+
##
102+
# Responds with the client's credentials.
103+
def process(data) [authzid, authcid, password].join("\0") end
104+
105+
private
106+
107+
NULL = -"\0".b
108+
private_constant :NULL
109+
110+
def propset(name, value)
111+
case name
112+
when :authcid, :password, :authzid
113+
raise ArgumentError, "#{name} contains NULL" if value&.include?(NULL)
114+
end
115+
super
116+
end
117+
118+
end
119+
end
120+
121+
PlainAuthenticator = SASL::PlainAuthenticator # :nodoc:
122+
deprecate_constant :PlainAuthenticator
123+
124+
end
125+
end

test/net/imap/test_imap_authenticators.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ def plain(*args, **kwargs, &block)
1414
end
1515

1616
def test_plain_authenticator_matches_mechanism
17-
assert_kind_of(Net::IMAP::PlainAuthenticator, plain("user", "pass"))
17+
assert_kind_of(Net::IMAP::SASL::PlainAuthenticator, plain("user", "pass"))
1818
end
1919

2020
def test_plain_response
2121
assert_equal("\0authc\0passwd", plain("authc", "passwd").process(nil))
22+
assert_equal("authz\0user\0pass",
23+
plain("user", "pass", "authz").process(nil))
2224
assert_equal("authz\0user\0pass",
2325
plain("user", "pass", authzid: "authz").process(nil))
2426
end

0 commit comments

Comments
 (0)