Skip to content

Commit 7eb9e54

Browse files
committed
✨ SASL ANONYMOUS: Add new mechanism
1 parent 86ccc2b commit 7eb9e54

File tree

5 files changed

+176
-0
lines changed

5 files changed

+176
-0
lines changed

lib/net/imap.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,11 @@ def starttls(options = {}, verify = true)
981981
# Non-standard and obsoleted by +OAUTHBEARER+, but widely
982982
# supported.
983983
#
984+
# +ANONYMOUS+:: See SASL::AnonymousAuthenticator.
985+
# Allow the user to gain access to public services or
986+
# resources without authenticating or disclosing an
987+
# identity.
988+
#
984989
# >>>
985990
# *Deprecated:* <em>Obsolete mechanisms are available for backwards
986991
# compatibility.</em>

lib/net/imap/authenticators.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def authenticators
6363

6464
Net::IMAP.extend Net::IMAP::Authenticators
6565

66+
require_relative "sasl/anonymous_authenticator"
6667
require_relative "sasl/plain_authenticator"
6768
require_relative "sasl/xoauth2_authenticator"
6869

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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 "+ANONYMOUS+" SASL mechanism, as specified by
10+
# RFC-4505[https://tools.ietf.org/html/rfc4505]. See
11+
# Net::IMAP#authenticate.
12+
class AnonymousAuthenticator < Authenticator
13+
register Net::IMAP
14+
15+
# :call-seq:
16+
# initial_response? -> true
17+
#
18+
# +ANONYMOUS+ can send an initial client response.
19+
def initial_response?; true end
20+
21+
##
22+
# :call-seq:
23+
# new -> authenticator
24+
# new(anonymous_message, **) -> authenticator
25+
# new(anonymous_message:, **) -> authenticator
26+
# new(message:, **) -> authenticator
27+
# new {|propname, auth_ctx| propval } -> authenticator
28+
#
29+
# Creates an Authenticator for the "+ANONYMOUS+" SASL mechanism, as
30+
# specified in RFC-4505[https://tools.ietf.org/html/rfc4505]. To use
31+
# this, see Net::IMAP#authenticate or your client's authentication
32+
# method.
33+
#
34+
# ==== Properties
35+
# Only one optional property:
36+
#
37+
# * #anonymous_message --- an optional message sent to the server which
38+
# doesn't contain an <tt>"@"</tt> character, or if it does have an
39+
# <tt>"@"</tt> it must be a valid email address.
40+
#
41+
# May be sent as positional argument or as a keyword argument.
42+
# Aliased as #message.
43+
#
44+
# See Net::IMAP::SASL::Authenticator@Properties for a detailed
45+
# description of property assignment, lazy loading, and callbacks.
46+
def initialize(message_arg = nil, anonymous_message: nil, message: nil)
47+
super
48+
propinit :anonymous_message, message_arg, anonymous_message, message
49+
end
50+
51+
##
52+
# method: anonymous_message
53+
# :call-seq:
54+
# anonymous_message -> string or nil
55+
#
56+
# A token sent for the +ANONYMOUS+ mechanism.
57+
#
58+
# Restricted to 255 UTF8 encoded characters, which will be validated by
59+
# #process.
60+
#
61+
# If an "@" sign is included, the message must be a valid email address
62+
# (+addr-spec+ from RFC-2822[https://tools.ietf.org/html/rfc2822]).
63+
# Email syntax will _not_ be validated by AnonymousAuthenticator.
64+
#
65+
# Otherwise, it can be any UTF8 string which is permitted by the
66+
# StringPrep "+trace+" profile. This is validated by #process.
67+
# See AnonymousAuthenticator.stringprep_trace.
68+
property :anonymous_message
69+
alias message anonymous_message
70+
71+
# From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace"
72+
# Profile of "Stringprep":
73+
# >>>
74+
# Characters from the following tables of [StringPrep] are prohibited:
75+
#
76+
# - C.2.1 (ASCII control characters)
77+
# - C.2.2 (Non-ASCII control characters)
78+
# - C.3 (Private use characters)
79+
# - C.4 (Non-character code points)
80+
# - C.5 (Surrogate codes)
81+
# - C.6 (Inappropriate for plain text)
82+
# - C.8 (Change display properties are deprecated)
83+
# - C.9 (Tagging characters)
84+
#
85+
# No additional characters are prohibited.
86+
SASLPREP_TRACE_TABLES = %w[C.2.1 C.2.2 C.3 C.4 C.5 C.6 C.8 C.9].freeze
87+
88+
# From RFC-4505[https://tools.ietf.org/html/rfc4505] §3, The "trace"
89+
# Profile of "Stringprep":
90+
# >>>
91+
# The character repertoire of this profile is Unicode 3.2 [Unicode].
92+
#
93+
# No mapping is required by this profile.
94+
#
95+
# No Unicode normalization is required by this profile.
96+
#
97+
# The list of unassigned code points for this profile is that provided
98+
# in Appendix A of [StringPrep]. Unassigned code points are not
99+
# prohibited.
100+
#
101+
# Characters from the following tables of [StringPrep] are prohibited:
102+
# (documented on SASLPREP_TRACE_TABLES)
103+
#
104+
# This profile requires bidirectional character checking per Section 6
105+
# of [StringPrep].
106+
def self.stringprep_trace(string)
107+
StringPrep.check_prohibited!(string,
108+
*SASLPREP_TRACE_TABLES,
109+
bidi: true,
110+
profile: "trace")
111+
string
112+
end
113+
114+
# Returns the #anonymous_message, after checking it with
115+
# rdoc-ref:AnonymousAuthenticator.stringprep_trace.
116+
def process(_server_challenge_string)
117+
if (size = anonymous_message&.length)&.> 255
118+
raise Error, "anonymous_message is too long. (%d codepoints)" % [
119+
size
120+
]
121+
end
122+
self.class.stringprep_trace(anonymous_message || "")
123+
end
124+
125+
end
126+
end
127+
end
128+
end

lib/net/imap/sasl/authenticator.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ module SASL
2727
# Non-standard and obsoleted by +OAUTHBEARER+, but widely
2828
# supported.
2929
#
30+
# +ANONYMOUS+:: See SASL::AnonymousAuthenticator.
31+
# Allow the user to gain access to public services or
32+
# resources without authenticating or disclosing an
33+
# identity.
34+
#
3035
# >>>
3136
# *Deprecated:* <em>Obsolete mechanisms are available for backwards
3237
# compatibility.</em>

test/net/imap/sasl/test_authenticators.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,43 @@ def test_xoauth2_callbacks
8080
)
8181
end
8282

83+
# ----------------------
84+
# ANONYMOUS
85+
# ----------------------
86+
87+
def anonymous(*args, **kwargs, &block)
88+
Net::IMAP.authenticator("ANONYMOUS", *args, **kwargs, &block)
89+
end
90+
91+
def test_anonymous_matches_mechanism
92+
assert_kind_of(Net::IMAP::SASL::AnonymousAuthenticator, anonymous)
93+
end
94+
95+
def test_anonymous_response
96+
assert_equal("", anonymous.process(nil))
97+
assert_equal("hello world", anonymous("hello world").process(nil))
98+
assert_equal("kwargs",
99+
anonymous(anonymous_message: "kwargs").process(nil))
100+
end
101+
102+
def test_anonymous_stringprep
103+
assert_raise(Net::IMAP::SASL::ProhibitedCodepoint) {
104+
anonymous("no\ncontrol\rchars").process(nil)
105+
}
106+
assert_raise(Net::IMAP::SASL::ProhibitedCodepoint) {
107+
anonymous("regional flags use tagging chars: e.g." \
108+
"🏴󠁧󠁢󠁥󠁮󠁧󠁿 England, " \
109+
"🏴󠁧󠁢󠁳󠁣󠁴󠁿 Scotland, " \
110+
"🏴󠁧󠁢󠁷󠁬󠁳󠁿 Wales.").process(nil)
111+
}
112+
end
113+
114+
def test_anonymous_length_over_255
115+
assert_raise(Net::IMAP::Error) {
116+
anonymous("a" * 256).process(nil)
117+
}
118+
end
119+
83120
# ----------------------
84121
# LOGIN (obsolete)
85122
# ----------------------

0 commit comments

Comments
 (0)