10000 🔒 Add SASL ANONYMOUS mechanism · ruby/net-imap@7d2819e · GitHub
[go: up one dir, main page]

Skip to content

Commit 7d2819e

Browse files

Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,12 @@ def starttls(options = {}, verify = true)
10021002
# Each mechanism has different properties and requirements. Please consult
10031003
# the documentation for the specific mechanisms you are using:
10041004
#
1005+
# +ANONYMOUS+::
1006+
# See AnonymousAuthenticator[Net::IMAP::SASL::AnonymousAuthenticator].
1007+
#
1008+
# Allows the user to gain access to public services or resources without
1009+
# authenticating or disclosing an identity.
1010+
#
10051011
# +PLAIN+::
10061012
# See PlainAuthenticator[Net::IMAP::SASL::PlainAuthenticator].
10071013
#
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ class IMAP
2727
# Each mechanism has different properties and requirements. Please consult
2828
# the documentation for the specific mechanisms you are using:
2929
#
30+
# +ANONYMOUS+::
31+
# See AnonymousAuthenticator[Net::IMAP::SASL::AnonymousAuthenticator].
32+
#
33+
# Allows the user to gain access to public services or resources without
34+
# authenticating or disclosing an identity.
35+
#
3036
# +PLAIN+::
3137
# See PlainAuthenticator[Net::IMAP::SASL::PlainAuthenticator].
3238
#
@@ -70,6 +76,7 @@ module SASL
7076
sasl_dir = File.expand_path("sasl", __dir__)
7177
autoload :Authenticators, "#{sasl_dir}/authenticators"
7278

79+
autoload :AnonymousAuthenticator, "#{sasl_dir}/anonymous_authenticator"
7380
autoload :PlainAuthenticator, "#{sasl_dir}/plain_authenticator"
7481
autoload :XOAuth2Authenticator, "#{sasl_dir}/xoauth2_authenticator"
7582

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

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