From 9c8c90f3559dabee91c6f75173cbf5f7f1518d7e Mon Sep 17 00:00:00 2001 From: nicholas evans Date: Sun, 6 Aug 2023 22:04:51 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9A=20Update=20SASL-related=20rdoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update that not all methods ignore capabilities. * Separate paragraphs for #authenticate args. * Simplify #authenticate call-seq * Reword #authenticate capabilities rdoc... again. * remove obsolete `DIGEST-MD5` from example. * Update the mechanisms listing and copy to SASL module rdoc. * Provide citation for preferring authenticate over login. --- lib/net/imap.rb | 116 ++++++++++++++------------------ lib/net/imap/sasl.rb | 57 +++++++++++++--- lib/net/imap/sasl/stringprep.rb | 2 +- 3 files changed, 98 insertions(+), 77 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 6f4ace7a..50307b3d 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -768,13 +768,12 @@ def disconnected? # cached #capabilities are used without sending a new #capability command to # the server. # - # *NOTE:* Net::IMAP does not currently modify its behaviour - # according to the server's advertised capabilities. + # *NOTE:* Most Net::IMAP methods do not _currently_ modify their + # behaviour according to the server's advertised #capabilities. # # See Net::IMAP@Capabilities for more about \IMAP capabilities. # # Related: #auth_capable?, #capabilities, #capability, #enable - # def capable?(capability) capabilities.include? capability.to_s.upcase end alias capability? capable? @@ -783,12 +782,12 @@ def capable?(capability) capabilities.include? capability.to_s.upcase end # # To ensure a case-insensitive comparison, #capable? can be used instead. # - # *NOTE:* Net::IMAP does not currently modify its behaviour - # according to the server's advertised capabilities. + # *NOTE:* Most Net::IMAP methods do not _currently_ modify their + # behaviour according to the server's advertised #capabilities. # # See Net::IMAP@Capabilities for more about \IMAP capabilities. # - # Related: #capable?, #auth_capable?, #capability + # Related: #capable?, #auth_capable?, #auth_mechanisms, #capability, #enable def capabilities @capabilities || capability end @@ -812,13 +811,7 @@ def capabilities # imap.authenticate("XOAUTH2", username, oauth2_access_token) # imap.auth_mechanisms # => [] # - # *NOTE:* Net::IMAP does not currently modify its behaviour - # according to the server's advertised capabilities. - # - # See Net::IMAP@Capabilities for more about \IMAP capabilities. - # - # Related: #auth_capable?, #capabilities - # + # Related: #authenticate, #auth_capable?, #capabilities def auth_mechanisms capabilities .grep(/\AAUTH=/i) @@ -835,12 +828,7 @@ def auth_mechanisms # imap.auth_capable? "PLAIN" # => true # imap.auth_capable? "blurdybloop" # => false # - # *NOTE:* Net::IMAP does not currently modify its behaviour - # according to the server's advertised capabilities. - # - # See Net::IMAP@Capabilities for more about \IMAP capabilities. - # - # Related: #authenticate, #capable?, #capabilities + # Related: #authenticate, #auth_mechanisms, #capable?, #capabilities def auth_capable?(mechanism) capable? "AUTH=#{mechanism}" end @@ -875,8 +863,8 @@ def clear_cached_capabilities # and returns an array of capabilities that are supported by the server. # The result is stored for use by #capable? and #capabilities. # - # *NOTE:* Net::IMAP does not currently modify its behaviour - # according to the server's advertised capabilities. + # *NOTE:* Most Net::IMAP methods do not _currently_ modify their + # behaviour according to the server's advertised #capabilities. # # Net::IMAP automatically stores and discards capability data according to # the requirements and recommendations in @@ -992,82 +980,76 @@ def starttls(options = {}, verify = true) end # :call-seq: - # authenticate(mechanism, ...) -> ok_resp - # authenticate(mech, *creds, sasl_ir: true, **attrs, &callback) -> ok_resp + # authenticate(mechanism, *, sasl_ir: true, **, &) -> ok_resp # # Sends an {AUTHENTICATE command [IMAP4rev1 ยง6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2] # to authenticate the client. If successful, the connection enters the # "_authenticated_" state. # # +mechanism+ is the name of the \SASL authentication mechanism to be used. + # # +sasl_ir+ allows or disallows sending an "initial response" (see the - # +SASL-IR+ capability, below). All other arguments are forwarded to the - # registered SASL authenticator for the requested mechanism. The - # documentation for each individual mechanism must be consulted for its - # specific parameters. + # +SASL-IR+ capability, below). # - # An exception Net::IMAP::NoResponseError is raised if authentication fails. + # All other arguments are forwarded to the registered SASL authenticator for + # the requested mechanism. The documentation for each individual + # mechanism must be consulted for its specific parameters. # # Related: #login, #starttls, #auth_capable?, #auth_mechanisms # - # ==== Supported SASL Mechanisms + # ==== Mechanisms # - # +PLAIN+:: See PlainAuthenticator. - # Login using clear-text username and password. + # Each mechanism has different properties and requirements. Please consult + # the documentation for the specific mechanisms you are using: # - # +XOAUTH2+:: See XOAuth2Authenticator. - # Login using a username and OAuth2 access token. - # Non-standard and obsoleted by +OAUTHBEARER+, but widely - # supported. + # +PLAIN+:: + # See PlainAuthenticator[Net::IMAP::SASL::PlainAuthenticator]. # - # >>> - # *Deprecated:* Obsolete mechanisms are available for backwards - # compatibility. + # Login using clear-text username and password. # - # For +DIGEST-MD5+ see DigestMD5Authenticator. + # +XOAUTH2+:: + # See XOAuth2Authenticator[Net::IMAP::SASL::XOAuth2Authenticator]. # - # For +LOGIN+, see LoginAuthenticator. + # Login using a username and an OAuth2 access token. Non-standard and + # obsoleted by +OAUTHBEARER+, but widely supported. # - # For +CRAM-MD5+, see CramMD5Authenticator. + # See the {SASL mechanism + # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] + # for a list of all SASL mechanisms and their specifications. To register + # new authenticators, see Authenticators. # - # Using a deprecated mechanism will print a warning. + # ===== Deprecated mechanisms # - # See Net::IMAP::Authenticators for information on plugging in - # authenticators for other mechanisms. See the {SASL mechanism - # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] - # for information on these and other SASL mechanisms. + # Obsolete mechanisms should be avoided, but are still available for + # backwards compatibility. See Net::IMAP::SASL@Deprecated+mechanisms. + # Using a deprecated mechanism will print a warning. # - # ===== Capabilities + # ==== Capabilities # - # The server should list "AUTH=#{mechanism}" capabilities for - # supported mechanisms. Check #auth_capable? or #auth_mechanisms before - # using a particular mechanism. + # "AUTH=#{mechanism}" capabilities indicate server support for + # mechanisms. Use #auth_capable? or #auth_mechanisms to check for support + # before using a particular mechanism. # # if imap.auth_capable? "XOAUTH2" # imap.authenticate "XOAUTH2", username, oauth2_access_token # elsif imap.auth_capable? "PLAIN" # imap.authenticate "PLAIN", username, password - # elsif imap.auth_capable? "DIGEST-MD5" - # imap.authenticate "DIGEST-MD5", username, password # elsif !imap.capability? "LOGINDISABLED" # imap.login username, password # else # raise "No acceptable authentication mechanism is available" # end # - # The SASL exchange provides a method for server challenges and client - # responses, but many mechanisms expect the client to "respond" first. When - # the server's capabilities include +SASL-IR+ - # [RFC4959[https://tools.ietf.org/html/rfc4959]], this "initial response" - # may be sent as an argument to the +AUTHENTICATE+ command, saving a - # round-trip. The initial response will _only_ be sent when it is supported - # by both the mechanism and the server. Set +sasl_ir+ to +false+ to prevent - # sending an initial response, even when it is supported. + # Although servers should list all supported \SASL mechanisms, they may + # allow authentication with an unlisted +mechanism+. # - # Although servers _should_ advertise all supported auth mechanisms, it is - # possible to attempt to authenticate with a +mechanism+ that isn't listed. - # However the initial response will not be sent unless the appropriate - # "AUTH=#{mechanism}" capability is also present. + # If [SASL-IR[https://www.rfc-editor.org/rfc/rfc4959.html]] is supported + # and the appropriate "AUTH=#{mechanism}" capability is present, + # an "initial response" may be sent as an argument to the +AUTHENTICATE+ + # command, saving a round-trip. The SASL exchange allows for server + # challenges and client responses, but many mechanisms expect the client to + # "respond" first. The initial response will only be sent for + # "client-first" mechanisms. # # Server capabilities may change after #starttls, #login, and #authenticate. # Previously cached #capabilities will be cleared when this method @@ -1098,8 +1080,10 @@ def authenticate(mechanism, *creds, sasl_ir: true, **props, &callback) # this +user+. If successful, the connection enters the "_authenticated_" # state. # - # Using #authenticate is generally preferred over #login. The LOGIN command - # is not the same as #authenticate with the "LOGIN" +mechanism+. + # Using #authenticate {should be + # preferred}[https://www.rfc-editor.org/rfc/rfc9051.html#name-login-command] + # over #login. The LOGIN command is not the same as #authenticate with the + # "LOGIN" +mechanism+. # # A Net::IMAP::NoResponseError is raised if authentication fails. # diff --git a/lib/net/imap/sasl.rb b/lib/net/imap/sasl.rb index 6f2db923..921df2ea 100644 --- a/lib/net/imap/sasl.rb +++ b/lib/net/imap/sasl.rb @@ -6,12 +6,11 @@ class IMAP # Pluggable authentication mechanisms for protocols which support SASL # (Simple Authentication and Security Layer), such as IMAP4, SMTP, LDAP, and # XMPP. {RFC-4422}[https://tools.ietf.org/html/rfc4422] specifies the - # common SASL framework and the +EXTERNAL+ mechanism, and the - # {SASL mechanism registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] - # lists the specification for others. - # - # "SASL is conceptually a framework that provides an abstraction layer - # between protocols and mechanisms as illustrated in the following diagram." + # common \SASL framework: + # >>> + # SASL is conceptually a framework that provides an abstraction layer + # between protocols and mechanisms as illustrated in the following + # diagram. # # SMTP LDAP XMPP Other protocols ... # \ | | / @@ -21,10 +20,46 @@ class IMAP # / | | \ # EXTERNAL GSSAPI PLAIN Other mechanisms ... # + # Net::IMAP uses SASL via the Net::IMAP#authenticate method. + # + # == Mechanisms + # + # Each mechanism has different properties and requirements. Please consult + # the documentation for the specific mechanisms you are using: + # + # +PLAIN+:: + # See PlainAuthenticator[Net::IMAP::SASL::PlainAuthenticator]. + # + # Login using clear-text username and password. + # + # +XOAUTH2+:: + # See XOAuth2Authenticator[Net::IMAP::SASL::XOAuth2Authenticator]. + # + # Login using a username and an OAuth2 access token. Non-standard and + # obsoleted by +OAUTHBEARER+, but widely supported. + # + # See the {SASL mechanism + # registry}[https://www.iana.org/assignments/sasl-mechanisms/sasl-mechanisms.xhtml] + # for a list of all SASL mechanisms and their specifications. To register + # new authenticators, see Authenticators. + # + # === Deprecated mechanisms + # + # Obsolete mechanisms should be avoided, but are still available for + # backwards compatibility. + # + # >>> + # For +DIGEST-MD5+ see DigestMD5Authenticator. + # + # For +LOGIN+, see LoginAuthenticator. + # + # For +CRAM-MD5+, see CramMD5Authenticator. + # + # Using a deprecated mechanism will print a warning. + # module SASL # autoloading to avoid loading all of the regexps when they aren't used. - sasl_stringprep_rb = File.expand_path("sasl/stringprep", __dir__) autoload :StringPrep, sasl_stringprep_rb autoload :SASLprep, sasl_stringprep_rb @@ -60,11 +95,13 @@ def saslprep(string, **opts) Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts) end - def initial_response?(mechanism) - mechanism.respond_to?(:initial_response?) && mechanism.initial_response? + # Returns whether the authenticator is client-first and supports sending + # an "initial response". + def initial_response?(authenticator) + authenticator.respond_to?(:initial_response?) && + authenticator.initial_response? end end end - end diff --git a/lib/net/imap/sasl/stringprep.rb b/lib/net/imap/sasl/stringprep.rb index eee8d8fc..0823de71 100644 --- a/lib/net/imap/sasl/stringprep.rb +++ b/lib/net/imap/sasl/stringprep.rb @@ -2,7 +2,7 @@ module Net::IMAP::SASL - # Alias for Net::IMAP::StringPrep::SASLPrep. + # Alias for Net::IMAP::StringPrep::SASLprep. SASLprep = Net::IMAP::StringPrep::SASLprep StringPrep = Net::IMAP::StringPrep # :nodoc: BidiStringError = Net::IMAP::StringPrep::BidiStringError # :nodoc: