Skip to content

Commit e64dbc8

Browse files
authored
🔀 Merge pull request #176 from ruby/fix-sasl-rdoc
📚 Update SASL docs and add attr_readers
2 parents 9d8460c + 3fdc9f6 commit e64dbc8

11 files changed

+255
-129
lines changed

lib/net/imap.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ module Net
109109
# sending them. Special care should be taken to follow the #capabilities
110110
# requirements for #starttls, #login, and #authenticate.
111111
#
112-
# See #capable?, #auth_capable, #capabilities, #auth_mechanisms to discover
112+
# See #capable?, #auth_capable?, #capabilities, #auth_mechanisms to discover
113113
# server capabilities. For relevant capability requirements, see the
114114
# documentation on each \IMAP command.
115115
#
@@ -1139,13 +1139,13 @@ def starttls(options = {}, verify = true)
11391139
# the documentation for the specific mechanisms you are using:
11401140
#
11411141
# +ANONYMOUS+::
1142-
# See AnonymousAuthenticator[Net::IMAP::SASL::AnonymousAuthenticator].
1142+
# See AnonymousAuthenticator[rdoc-ref:Net::IMAP::SASL::AnonymousAuthenticator].
11431143
#
11441144
# Allows the user to gain access to public services or resources without
11451145
# authenticating or disclosing an identity.
11461146
#
11471147
# +EXTERNAL+::
1148-
# See ExternalAuthenticator[Net::IMAP::SASL::ExternalAuthenticator].
1148+
# See ExternalAuthenticator[rdoc-ref:Net::IMAP::SASL::ExternalAuthenticator].
11491149
#
11501150
# Authenticates using already established credentials, such as a TLS
11511151
# certificate or IPsec.

lib/net/imap/sasl.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ class IMAP
2828
# the documentation for the specific mechanisms you are using:
2929
#
3030
# +ANONYMOUS+::
31-
# See AnonymousAuthenticator[Net::IMAP::SASL::AnonymousAuthenticator].
31+
# See AnonymousAuthenticator.
3232
#
3333
# Allows the user to gain access to public services or resources without
3434
# authenticating or disclosing an identity.
3535
#
3636
# +EXTERNAL+::
37-
# See ExternalAuthenticator[Net::IMAP::SASL::ExternalAuthenticator].
37+
# See ExternalAuthenticator.
3838
#
3939
# Authenticates using already established credentials, such as a TLS
4040
# certificate or IPsec.

lib/net/imap/sasl/anonymous_authenticator.rb

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ module SASL
99
# Net::IMAP#authenticate.
1010
class AnonymousAuthenticator
1111

12+
# An optional token sent for the +ANONYMOUS+ mechanism., up to 255 UTF-8
13+
# characters in length.
14+
#
15+
# If it contains an "@" sign, the message must be a valid email address
16+
# (+addr-spec+ from RFC-2822[https://tools.ietf.org/html/rfc2822]).
17+
# Email syntax is _not_ validated by AnonymousAuthenticator.
18+
#
19+
# Otherwise, it can be any UTF8 string which is permitted by the
20+
# StringPrep::Trace profile.
21+
attr_reader :anonymous_message
22+
1223
# :call-seq:
1324
# new(anonymous_message = "", **) -> authenticator
1425
# new(anonymous_message: "", **) -> authenticator
@@ -21,7 +32,7 @@ class AnonymousAuthenticator
2132
# #anonymous_message is an optional message which is sent to the server.
2233
# It may be sent as a positional argument or as a keyword argument.
2334
#
24-
# Any other keyword parameters are quietly ignored.
35+
# Any other keyword arguments are silently ignored.
2536
def initialize(anon_msg = nil, anonymous_message: nil, **)
2637
message = (anonymous_message || anon_msg || "").to_str
2738
@anonymous_message = StringPrep::Trace.stringprep_trace message
@@ -31,24 +42,16 @@ def initialize(anon_msg = nil, anonymous_message: nil, **)
3142
end
3243
end
3344

34-
# A token sent for the +ANONYMOUS+ mechanism.
35-
#
36-
# If it contains an "@" sign, the message must be a valid email address
37-
# (+addr-spec+ from RFC-2822[https://tools.ietf.org/html/rfc2822]).
38-
# Email syntax is _not_ validated by AnonymousAuthenticator.
39-
#
40-
# Otherwise, it can be any UTF8 string which is permitted by the
41-
# StringPrep::Trace profile, up to 255 UTF-8 characters in length.
42-
attr_reader :anonymous_message
43-
4445
# :call-seq:
4546
# initial_response? -> true
4647
#
4748
# +ANONYMOUS+ can send an initial client response.
4849
def initial_response?; true end
4950

5051
# Returns #anonymous_message.
51-
def process(_server_challenge_string) anonymous_message end
52+
def process(_server_challenge_string)
53+
anonymous_message
54+
end
5255

5356
end
5457
end

lib/net/imap/sasl/cram_md5_authenticator.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@
1414
# of cleartext and recommends TLS version 1.2 or greater be used for all
1515
# traffic. With TLS +CRAM-MD5+ is okay, but so is +PLAIN+
1616
class Net::IMAP::SASL::CramMD5Authenticator
17-
def process(challenge)
18-
digest = hmac_md5(challenge, @password)
19-
return @user + " " + digest
20-
end
21-
22-
private
23-
2417
def initialize(user, password, warn_deprecation: true, **_ignored)
2518
if warn_deprecation
2619
warn "WARNING: CRAM-MD5 mechanism is deprecated." # TODO: recommend SCRAM
@@ -30,6 +23,13 @@ def initialize(user, password, warn_deprecation: true, **_ignored)
3023
@password = password
3124
end
3225

26+
def process(challenge)
27+
digest = hmac_md5(challenge, @password)
28+
return @user + " " + digest
29+
end
30+
31+
private
32+
3333
def hmac_md5(text, key)
3434
if key.length > 64
3535
key = Digest::MD5.digest(key)

lib/net/imap/sasl/digest_md5_authenticator.rb

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,72 @@
11
# frozen_string_literal: true
22

33
# Net::IMAP authenticator for the "`DIGEST-MD5`" SASL mechanism type, specified
4-
# in RFC2831(https://tools.ietf.org/html/rfc2831). See Net::IMAP#authenticate.
4+
# in RFC-2831[https://tools.ietf.org/html/rfc2831]. See Net::IMAP#authenticate.
55
#
66
# == Deprecated
77
#
88
# "+DIGEST-MD5+" has been deprecated by
9-
# {RFC6331}[https://tools.ietf.org/html/rfc6331] and should not be relied on for
9+
# RFC-6331[https://tools.ietf.org/html/rfc6331] and should not be relied on for
1010
# security. It is included for compatibility with existing servers.
1111
class Net::IMAP::SASL::DigestMD5Authenticator
12+
STAGE_ONE = :stage_one
13+
STAGE_TWO = :stage_two
14+
private_constant :STAGE_ONE, :STAGE_TWO
15+
16+
# Authentication identity: the identity that matches the #password.
17+
#
18+
# RFC-2831[https://tools.ietf.org/html/rfc2831] uses the term +username+.
19+
# "Authentication identity" is the generic term used by
20+
# RFC-4422[https://tools.ietf.org/html/rfc4422].
21+
# RFC-4616[https://tools.ietf.org/html/rfc4616] and many later RFCs abbreviate
22+
# that to +authcid+. So +authcid+ is available as an alias for #username.
23+
attr_reader :username
24+
25+
# A password or passphrase that matches the #username.
26+
#
27+
# The +password+ will be used to create the response digest.
28+
attr_reader :password
29+
30+
# Authorization identity: an identity to act as or on behalf of. The identity
31+
# form is application protocol specific. If not provided or left blank, the
32+
# server derives an authorization identity from the authentication identity.
33+
# The server is responsible for verifying the client's credentials and
34+
# verifying that the identity it associates with the client's authentication
35+
# identity is allowed to act as (or on behalf of) the authorization identity.
36+
#
37+
# For example, an administrator or superuser might take on another role:
38+
#
39+
# imap.authenticate "DIGEST-MD5", "root", ->{passwd}, authzid: "user"
40+
#
41+
attr_reader :authzid
42+
43+
# :call-seq:
44+
# new(username, password, authzid = nil) -> authenticator
45+
#
46+
# Creates an Authenticator for the "+DIGEST-MD5+" SASL mechanism.
47+
#
48+
# Called by Net::IMAP#authenticate and similar methods on other clients.
49+
#
50+
# ==== Parameters
51+
#
52+
# * #username — Identity whose #password is used.
53+
# * #password — A password or passphrase associated with this #username.
54+
# * #authzid ― Alternate identity to act as or on behalf of. Optional.
55+
# * +warn_deprecation+ — Set to +false+ to silence the warning.
56+
#
57+
# See the documentation for each attribute for more details.
58+
def initialize(username, password, authzid = nil, warn_deprecation: true)
59+
if warn_deprecation
60+
warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
61+
# TODO: recommend SCRAM instead.
62+
end
63+
require "digest/md5"
64+
require "strscan"
65+
@username, @password, @authzid = username, password, authzid
66+
@nc, @stage = {}, STAGE_ONE
67+
end
68+
69+
# Responds to server challenge in two stages.
1270
def process(challenge)
1371
case @stage
1472
when STAGE_ONE
@@ -31,7 +89,7 @@ def process(challenge)
3189

3290
response = {
3391
:nonce => sparams['nonce'],
34-
:username => @user,
92+
:username => @username,
3593
:realm => sparams['realm'],
3694
:cnonce => Digest::MD5.hexdigest("%.15f:%.15f:%d" % [Time.now.to_f, rand, Process.pid.to_s]),
3795
:'digest-uri' => 'imap/' + sparams['realm'],
@@ -41,7 +99,7 @@ def process(challenge)
4199
:charset => sparams['charset'],
42100
}
43101

44-
response[:authzid] = @authname unless @authname.nil?
102+
response[:authzid] = @authzid unless @authzid.nil?
45103

46104
# now, the real thing
47105
a0 = Digest::MD5.digest( [ response.values_at(:username, :realm), @password ].join(':') )
@@ -74,23 +132,8 @@ def process(challenge)
74132
end
75133
end
76134

77-
def initialize(user, password, authname = nil, warn_deprecation: true)
78-
if warn_deprecation
79-
warn "WARNING: DIGEST-MD5 SASL mechanism was deprecated by RFC6331."
80-
# TODO: recommend SCRAM instead.
81-
end
82-
require "digest/md5"
83-
require "strscan"
84-
@user, @password, @authname = user, password, authname
85-
@nc, @stage = {}, STAGE_ONE
86-
end
87-
88-
89135
private
90136

91-
STAGE_ONE = :stage_one
92-
STAGE_TWO = :stage_two
93-
94137
def nc(nonce)
95138
if @nc.has_key? nonce
96139
@nc[nonce] = @nc[nonce] + 1

lib/net/imap/sasl/external_authenticator.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ 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.
16+
#
17+
# If not explicitly provided, the server defaults to using the identity
18+
# that was authenticated by the external credentials.
19+
attr_reader :authzid
20+
1521
# :call-seq:
1622
# new(authzid: nil, **) -> authenticator
1723
#
@@ -30,12 +36,6 @@ def initialize(authzid: nil)
3036
end
3137
end
3238

33-
# Authorization identity: an identity to act as or on behalf of.
34-
#
35-
# If not explicitly provided, the server defaults to using the identity
36-
# that was authenticated by the external credentials.
37-
attr_reader :authzid
38-
3939
# :call-seq:
4040
# initial_response? -> true
4141
#

lib/net/imap/sasl/login_authenticator.rb

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,9 @@
1818
# {draft-murchison-sasl-login}[https://www.iana.org/go/draft-murchison-sasl-login]
1919
# for both specification and deprecation.
2020
class Net::IMAP::SASL::LoginAuthenticator
21-
def process(data)
22-
case @state
23-
when STATE_USER
24-
@state = STATE_PASSWORD
25-
return @user
26-
when STATE_PASSWORD
27-
return @password
28-
end
29-
end
30-
31-
private
32-
3321
STATE_USER = :USER
3422
STATE_PASSWORD = :PASSWORD
23+
private_constant :STATE_USER, :STATE_PASSWORD
3524

3625
def initialize(user, password, warn_deprecation: true, **_ignored)
3726
if warn_deprecation
@@ -42,4 +31,13 @@ def initialize(user, password, warn_deprecation: true, **_ignored)
4231
@state = STATE_USER
4332
end
4433

34+
def process(data)
35+
case @state
36+
when STATE_USER
37+
@state = STATE_PASSWORD
38+
return @user
39+
when STATE_PASSWORD
40+
return @password
41+
end
42+
end
4543
end

0 commit comments

Comments
 (0)