From 69cc3c5369abbddc8361874843841f77106ef92a Mon Sep 17 00:00:00 2001 From: nick evans Date: Wed, 23 Apr 2025 21:17:00 -0400 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20`enforce=5F?= =?UTF-8?q?logindisabled=3F`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/net/imap.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index a1a8aeea..04faff95 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -3578,11 +3578,11 @@ def put_string(str) end def enforce_logindisabled? - if config.enforce_logindisabled == :when_capabilities_cached - capabilities_cached? - else - config.enforce_logindisabled - end + may_depend_on_capabilities_cached?(config.enforce_logindisabled) + end + + def may_depend_on_capabilities_cached?(value) + value == :when_capabilities_cached ? capabilities_cached? : value end def expunge_internal(...) From 4de513c6b55f6de3ea42c1045e1f60e3b5d0c600 Mon Sep 17 00:00:00 2001 From: nick evans Date: Wed, 23 Apr 2025 22:19:51 -0400 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=92=20Add=20when=5Fcapabilities=5F?= =?UTF-8?q?cached=20option=20for=20sasl=5Fir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the same basic approach used by `enforce_logindisabled`. The default config (`true`) will not change. --- lib/net/imap.rb | 1 + lib/net/imap/config.rb | 15 ++++++- test/net/imap/test_imap_authenticate.rb | 52 +++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 04faff95..e012a488 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -1518,6 +1518,7 @@ def starttls(**options) # completes. If the TaggedResponse to #authenticate includes updated # capabilities, they will be cached. def authenticate(*args, sasl_ir: config.sasl_ir, **props, &callback) + sasl_ir = may_depend_on_capabilities_cached?(sasl_ir) sasl_adapter.authenticate(*args, sasl_ir: sasl_ir, **props, &callback) .tap do state_authenticated! _1 end end diff --git a/lib/net/imap/config.rb b/lib/net/imap/config.rb index 8e2f230d..35a7f346 100644 --- a/lib/net/imap/config.rb +++ b/lib/net/imap/config.rb @@ -234,13 +234,26 @@ def self.[](config) # Do not use +SASL-IR+, even when it is supported by the server and the # mechanism. # + # [+:when_capabilities_cached+] + # Use +SASL-IR+ when Net::IMAP#capabilities_cached? is +true+ and it is + # supported by the server and the mechanism, but do not send a + # +CAPABILITY+ command to discover the server capabilities. + # + # (+:when_capabilities_cached+ option was added by +v0.6.0+) + # # [+true+ (default since +v0.4+)] # Use +SASL-IR+ when it is supported by the server and the mechanism. - attr_accessor :sasl_ir, type: :boolean, defaults: { + attr_accessor :sasl_ir, type: Enum[ + false, :when_capabilities_cached, true + ], defaults: { 0.0r => false, 0.4r => true, } + # :stopdoc: + alias sasl_ir? sasl_ir + # :startdoc: + # Controls the behavior of Net::IMAP#login when the +LOGINDISABLED+ # capability is present. When enforced, Net::IMAP will raise a # LoginDisabledError when that capability is present. diff --git a/test/net/imap/test_imap_authenticate.rb b/test/net/imap/test_imap_authenticate.rb index dce1af23..35780909 100644 --- a/test/net/imap/test_imap_authenticate.rb +++ b/test/net/imap/test_imap_authenticate.rb @@ -84,6 +84,58 @@ class IMAPAuthenticateTest < Net::IMAP::TestCase end end + test("#authenticate without cached capabilities never sends initial response " \ + "when config.sasl_ir: :when_capabilities_cached") do + [true, false].each do |server_support| + with_fake_server( + preauth: false, cleartext_auth: true, sasl_ir: server_support, + greeting_capabilities: false, + ) do |server, imap| + imap.config.sasl_ir = :when_capabilities_cached + imap.authenticate("PLAIN", "test_user", "test-password") + cmd, cont = 2.times.map { server.commands.pop } + assert_equal %w[AUTHENTICATE PLAIN], [cmd.name, *cmd.args] + assert_equal(["\x00test_user\x00test-password"].pack("m0"), + cont[:continuation].strip) + assert_empty server.commands + end + end + end + + test("#authenticate with cached capabilities sends an initial response " \ + "when config.sasl_ir: :when_capabilities_cached " \ + "and supported by both the mechanism and the server") do + with_fake_server( + preauth: false, cleartext_auth: true, sasl_ir: true, + greeting_capabilities: true, + ) do |server, imap| + imap.config.sasl_ir = :when_capabilities_cached + imap.authenticate("PLAIN", "test_user", "test-password") + cmd = server.commands.pop + assert_equal "AUTHENTICATE", cmd.name + assert_equal(["PLAIN", ["\x00test_user\x00test-password"].pack("m0")], + cmd.args) + assert_empty server.commands + end + end + + test("#authenticate with cached capabilities doesn't send initial response " \ + "when config.sasl_ir: :when_capabilities_cached " \ + "and not supported by the server") do + with_fake_server( + preauth: false, cleartext_auth: true, sasl_ir: false, + greeting_capabilities: true, + ) do |server, imap| + imap.config.sasl_ir = :when_capabilities_cached + imap.authenticate("PLAIN", "test_user", "test-password") + cmd, cont = 2.times.map { server.commands.pop } + assert_equal %w[AUTHENTICATE PLAIN], [cmd.name, *cmd.args] + assert_equal(["\x00test_user\x00test-password"].pack("m0"), + cont[:continuation].strip) + assert_empty server.commands + end + end + test("#authenticate never sends an initial response " \ "when config.sasl_ir: false") do [true, false].each do |server_support|