net-imap 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 473842922edfdfd112a9cd3fa96df2831efa0d4640a38c0969725d6c04f45dbe
4
- data.tar.gz: 7f93b8a1882f2225f404db0d175c29ead931de531f918e17cbb6f8515300b9e9
3
+ metadata.gz: 2bba73e8db611b37b7c5b9be540d99c2288e9188e7f12620259862b30db47815
4
+ data.tar.gz: fde1bcda99236beafea397d36768fb9fe185ec20ca2cd5d9c991b48cfdf51bd3
5
5
  SHA512:
6
- metadata.gz: d9470c52ca3b689a1dd314501a91f5651d1b11fd26cc5628cb6ccf4bf59aeb93ebdcca141ab12d0ec5e69b0e5aa5e29881c85433d9c6dcdeb5902591c1c62aaa
7
- data.tar.gz: 6caae2c7cf684d10ca54ac8b53b498acb5c8d210135d2f583d254fa3dbdc41b7e50382c43aeb05438713b723d9d5da7d48980569024e36d55683e9e5b041df8a
6
+ metadata.gz: 6ee43fab9eaea8c870940e0cc625e3223722e4b722e1055da6f3629707aaf12e580143139abb97ae1294757a99db977428c8aa23bc57d8e9a23ff9ab5eb695cf
7
+ data.tar.gz: 67a965fdd6b4af0480917241a8efad57c3356c82ec975bcc10373242e477718a1751e78cc9f0e51286b553e6222c26cddca119dbaa9dfef855c9611de6a55a64
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ module SASL
6
+
7
+ # This API is *experimental*, and may change.
8
+ #
9
+ # TODO: catch exceptions in #process and send #cancel_response.
10
+ # TODO: raise an error if the command succeeds after being canceled.
11
+ # TODO: use with more clients, to verify the API can accommodate them.
12
+ #
13
+ # Create an AuthenticationExchange from a client adapter and a mechanism
14
+ # authenticator:
15
+ # def authenticate(mechanism, ...)
16
+ # authenticator = SASL.authenticator(mechanism, ...)
17
+ # SASL::AuthenticationExchange.new(
18
+ # sasl_adapter, mechanism, authenticator
19
+ # ).authenticate
20
+ # end
21
+ #
22
+ # private
23
+ #
24
+ # def sasl_adapter = MyClientAdapter.new(self, &method(:send_command))
25
+ #
26
+ # Or delegate creation of the authenticator to ::build:
27
+ # def authenticate(...)
28
+ # SASL::AuthenticationExchange.build(sasl_adapter, ...)
29
+ # .authenticate
30
+ # end
31
+ #
32
+ # As a convenience, ::authenticate combines ::build and #authenticate:
33
+ # def authenticate(...)
34
+ # SASL::AuthenticationExchange.authenticate(sasl_adapter, ...)
35
+ # end
36
+ #
37
+ # Likewise, ClientAdapter#authenticate delegates to #authenticate:
38
+ # def authenticate(...) = sasl_adapter.authenticate(...)
39
+ #
40
+ class AuthenticationExchange
41
+ # Convenience method for <tt>build(...).authenticate</tt>
42
+ def self.authenticate(...) build(...).authenticate end
43
+
44
+ # Use +registry+ to override the global Authenticators registry.
45
+ def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)
46
+ authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block)
47
+ new(client, mechanism, authenticator, sasl_ir: sasl_ir)
48
+ end
49
+
50
+ attr_reader :mechanism, :authenticator
51
+
52
+ def initialize(client, mechanism, authenticator, sasl_ir: true)
53
+ @client = client
54
+ @mechanism = -mechanism.to_s.upcase.tr(?_, ?-)
55
+ @authenticator = authenticator
56
+ @sasl_ir = sasl_ir
57
+ @processed = false
58
+ end
59
+
60
+ # Call #authenticate to execute an authentication exchange for #client
61
+ # using #authenticator. Authentication failures will raise an
62
+ # exception. Any exceptions other than those in RESPONSE_ERRORS will
63
+ # drop the connection.
64
+ def authenticate
65
+ client.run_command(mechanism, initial_response) { process _1 }
66
+ .tap { raise AuthenticationIncomplete, _1 unless done? }
67
+ rescue *client.response_errors
68
+ raise # but don't drop the connection
69
+ rescue
70
+ client.drop_connection
71
+ raise
72
+ rescue Exception # rubocop:disable Lint/RescueException
73
+ client.drop_connection!
74
+ raise
75
+ end
76
+
77
+ def send_initial_response?
78
+ @sasl_ir &&
79
+ authenticator.respond_to?(:initial_response?) &&
80
+ authenticator.initial_response? &&
81
+ client.sasl_ir_capable? &&
82
+ client.auth_capable?(mechanism)
83
+ end
84
+
85
+ def done?
86
+ authenticator.respond_to?(:done?) ? authenticator.done? : @processed
87
+ end
88
+
89
+ private
90
+
91
+ attr_reader :client
92
+
93
+ def initial_response
94
+ return unless send_initial_response?
95
+ client.encode_ir authenticator.process nil
96
+ end
97
+
98
+ def process(challenge)
99
+ client.encode authenticator.process client.decode challenge
100
+ ensure
101
+ @processed = true
102
+ end
103
+
104
+ end
105
+ end
106
+ end
107
+ end
@@ -65,7 +65,7 @@ module Net::IMAP::SASL
65
65
  # lazily loaded from <tt>Net::IMAP::SASL::#{name}Authenticator</tt> (case is
66
66
  # preserved and non-alphanumeric characters are removed..
67
67
  def add_authenticator(name, authenticator = nil)
68
- key = name.upcase.to_sym
68
+ key = -name.to_s.upcase.tr(?_, ?-)
69
69
  authenticator ||= begin
70
70
  class_name = "#{name.gsub(/[^a-zA-Z0-9]/, "")}Authenticator".to_sym
71
71
  auth_class = nil
@@ -79,10 +79,15 @@ module Net::IMAP::SASL
79
79
 
80
80
  # Removes the authenticator registered for +name+
81
81
  def remove_authenticator(name)
82
- key = name.upcase.to_sym
82
+ key = -name.to_s.upcase.tr(?_, ?-)
83
83
  @authenticators.delete(key)
84
84
  end
85
85
 
86
+ def mechanism?(name)
87
+ key = -name.to_s.upcase.tr(?_, ?-)
88
+ @authenticators.key?(key)
89
+ end
90
+
86
91
  # :call-seq:
87
92
  # authenticator(mechanism, ...) -> auth_session
88
93
  #
@@ -100,8 +105,9 @@ module Net::IMAP::SASL
100
105
  # only. Protocol client users should see refer to their client's
101
106
  # documentation, e.g. Net::IMAP#authenticate.
102
107
  def authenticator(mechanism, ...)
103
- auth = @authenticators.fetch(mechanism.upcase.to_sym) do
104
- raise ArgumentError, 'unknown auth type - "%s"' % mechanism
108
+ key = -mechanism.to_s.upcase.tr(?_, ?-)
109
+ auth = @authenticators.fetch(key) do
110
+ raise ArgumentError, 'unknown auth type - "%s"' % key
105
111
  end
106
112
  auth.respond_to?(:new) ? auth.new(...) : auth.call(...)
107
113
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ module SASL
6
+
7
+ # This API is *experimental*, and may change.
8
+ #
9
+ # TODO: use with more clients, to verify the API can accommodate them.
10
+ #
11
+ # An abstract base class for implementing a SASL authentication exchange.
12
+ # Different clients will each have their own adapter subclass, overridden
13
+ # to match their needs.
14
+ #
15
+ # Although the default implementations _may_ be sufficient, subclasses
16
+ # will probably need to override some methods. Additionally, subclasses
17
+ # may need to include a protocol adapter mixin, if the default
18
+ # ProtocolAdapters::Generic isn't sufficient.
19
+ class ClientAdapter
20
+ include ProtocolAdapters::Generic
21
+
22
+ attr_reader :client, :command_proc
23
+
24
+ # +command_proc+ can used to avoid exposing private methods on #client.
25
+ # It should run a command with the arguments sent to it, yield each
26
+ # continuation payload, respond to the server with the result of each
27
+ # yield, and return the result. Non-successful results *MUST* raise an
28
+ # exception. Exceptions in the block *MUST* cause the command to fail.
29
+ #
30
+ # Subclasses that override #run_command may use #command_proc for
31
+ # other purposes.
32
+ def initialize(client, &command_proc)
33
+ @client, @command_proc = client, command_proc
34
+ end
35
+
36
+ # Delegates to AuthenticationExchange.authenticate.
37
+ def authenticate(...) AuthenticationExchange.authenticate(self, ...) end
38
+
39
+ # Do the protocol and server both support an initial response?
40
+ def sasl_ir_capable?; client.sasl_ir_capable? end
41
+
42
+ # Does the server advertise support for the mechanism?
43
+ def auth_capable?(mechanism); client.auth_capable?(mechanism) end
44
+
45
+ # Runs the authenticate command with +mechanism+ and +initial_response+.
46
+ # When +initial_response+ is nil, an initial response must NOT be sent.
47
+ #
48
+ # Yields each continuation payload, responds to the server with the
49
+ # result of each yield, and returns the result. Non-successful results
50
+ # *MUST* raise an exception. Exceptions in the block *MUST* cause the
51
+ # command to fail.
52
+ #
53
+ # Subclasses that override this may use #command_proc differently.
54
+ def run_command(mechanism, initial_response = nil, &block)
55
+ command_proc or raise Error, "initialize with block or override"
56
+ args = [command_name, mechanism, initial_response].compact
57
+ command_proc.call(*args, &block)
58
+ end
59
+
60
+ # Returns an array of server responses errors raised by run_command.
61
+ # Exceptions in this array won't drop the connection.
62
+ def response_errors; [] end
63
+
64
+ # Drop the connection gracefully.
65
+ def drop_connection; client.drop_connection end
66
+
67
+ # Drop the connection abruptly.
68
+ def drop_connection!; client.drop_connection! end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+ module SASL
6
+
7
+ module ProtocolAdapters
8
+ # This API is experimental, and may change.
9
+ module Generic
10
+ def command_name; "AUTHENTICATE" end
11
+ def service; raise "Implement in subclass or module" end
12
+ def host; client.host end
13
+ def port; client.port end
14
+ def encode_ir(string) string.empty? ? "=" : encode(string) end
15
+ def encode(string) [string].pack("m0") end
16
+ def decode(string) string.unpack1("m0") end
17
+ def cancel_response; "*" end
18
+ end
19
+
20
+ # See RFC-3501 (IMAP4rev1), RFC-4959 (SASL-IR capability),
21
+ # and RFC-9051 (IMAP4rev2).
22
+ module IMAP
23
+ include Generic
24
+ def service; "imap" end
25
+ end
26
+
27
+ # See RFC-4954 (AUTH capability).
28
+ module SMTP
29
+ include Generic
30
+ def command_name; "AUTH" end
31
+ def service; "smtp" end
32
+ end
33
+
34
+ # See RFC-5034 (SASL capability).
35
+ module POP
36
+ include Generic
37
+ def command_name; "AUTH" end
38
+ def service; "pop" end
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
45
+ end
data/lib/net/imap/sasl.rb CHANGED
@@ -135,6 +135,10 @@ module Net
135
135
  autoload :BidiStringError, sasl_stringprep_rb
136
136
 
137
137
  sasl_dir = File.expand_path("sasl", __dir__)
138
+ autoload :AuthenticationExchange, "#{sasl_dir}/authentication_exchange"
139
+ autoload :ClientAdapter, "#{sasl_dir}/client_adapter"
140
+ autoload :ProtocolAdapters, "#{sasl_dir}/protocol_adapters"
141
+
138
142
  autoload :Authenticators, "#{sasl_dir}/authenticators"
139
143
  autoload :GS2Header, "#{sasl_dir}/gs2_header"
140
144
  autoload :ScramAlgorithm, "#{sasl_dir}/scram_algorithm"
@@ -155,8 +159,10 @@ module Net
155
159
  # Returns the default global SASL::Authenticators instance.
156
160
  def self.authenticators; @authenticators ||= Authenticators.new end
157
161
 
158
- # Delegates to ::authenticators. See Authenticators#authenticator.
159
- def self.authenticator(...) authenticators.authenticator(...) end
162
+ # Delegates to <tt>registry.new</tt> See Authenticators#new.
163
+ def self.authenticator(*args, registry: authenticators, **kwargs, &block)
164
+ registry.new(*args, **kwargs, &block)
165
+ end
160
166
 
161
167
  # Delegates to ::authenticators. See Authenticators#add_authenticator.
162
168
  def self.add_authenticator(...) authenticators.add_authenticator(...) end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP
5
+
6
+ # Experimental
7
+ class SASLAdapter < SASL::ClientAdapter
8
+ include SASL::ProtocolAdapters::IMAP
9
+
10
+ RESPONSE_ERRORS = [NoResponseError, BadResponseError, ByeResponseError]
11
+ .freeze
12
+
13
+ def response_errors; RESPONSE_ERRORS end
14
+ def sasl_ir_capable?; client.capable?("SASL-IR") end
15
+ def auth_capable?(mechanism); client.auth_capable?(mechanism) end
16
+ def drop_connection; client.logout! end
17
+ def drop_connection!; client.disconnect end
18
+ end
19
+
20
+ end
21
+ end
data/lib/net/imap.rb CHANGED
@@ -127,7 +127,7 @@ module Net
127
127
  # end
128
128
  #
129
129
  # # Support for "UTF8=ACCEPT" implies support for "ENABLE"
130
- # imap.enable :utf8 if imap.auth_capable?("UTF8=ACCEPT")
130
+ # imap.enable :utf8 if imap.capable?("UTF8=ACCEPT")
131
131
  #
132
132
  # namespaces = imap.namespace if imap.capable?(:namespace)
133
133
  # mbox_prefix = namespaces&.personal&.first&.prefix || ""
@@ -662,7 +662,7 @@ module Net
662
662
  # * {IMAP URLAUTH Authorization Mechanism Registry}[https://www.iana.org/assignments/urlauth-authorization-mechanism-registry/urlauth-authorization-mechanism-registry.xhtml]
663
663
  #
664
664
  class IMAP < Protocol
665
- VERSION = "0.4.0"
665
+ VERSION = "0.4.1"
666
666
 
667
667
  # Aliases for supported capabilities, to be used with the #enable command.
668
668
  ENABLE_ALIASES = {
@@ -670,8 +670,9 @@ module Net
670
670
  "UTF8=ONLY" => "UTF8=ACCEPT",
671
671
  }.freeze
672
672
 
673
- autoload :SASL, File.expand_path("imap/sasl", __dir__)
674
- autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
673
+ autoload :SASL, File.expand_path("imap/sasl", __dir__)
674
+ autoload :SASLAdapter, File.expand_path("imap/sasl_adapter", __dir__)
675
+ autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
675
676
 
676
677
  include MonitorMixin
677
678
  if defined?(OpenSSL::SSL)
@@ -1142,7 +1143,10 @@ module Net
1142
1143
  end
1143
1144
 
1144
1145
  # :call-seq:
1145
- # authenticate(mechanism, *, sasl_ir: true, **, &) -> ok_resp
1146
+ # authenticate(mechanism, *,
1147
+ # sasl_ir: true,
1148
+ # registry: Net::IMAP::SASL.authenticators,
1149
+ # **, &) -> ok_resp
1146
1150
  #
1147
1151
  # Sends an {AUTHENTICATE command [IMAP4rev1 §6.2.2]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.2.2]
1148
1152
  # to authenticate the client. If successful, the connection enters the
@@ -1886,8 +1890,7 @@ module Net
1886
1890
  # +attr+ is a list of attributes to fetch; see the documentation
1887
1891
  # for FetchData for a list of valid attributes.
1888
1892
  #
1889
- # The return value is an array of FetchData or nil
1890
- # (instead of an empty array) if there is no matching message.
1893
+ # The return value is an array of FetchData.
1891
1894
  #
1892
1895
  # Related: #uid_search, FetchData
1893
1896
  #
@@ -2747,6 +2750,10 @@ module Net
2747
2750
  end
2748
2751
  end
2749
2752
 
2753
+ def sasl_adapter
2754
+ SASLAdapter.new(self, &method(:send_command_with_continuations))
2755
+ end
2756
+
2750
2757
  #--
2751
2758
  # We could get the saslprep method by extending the SASLprep module
2752
2759
  # directly. It's done indirectly, so SASLprep can be lazily autoloaded,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-imap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shugo Maeda
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-10-04 00:00:00.000000000 Z
12
+ date: 2023-10-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: net-protocol
@@ -100,7 +100,9 @@ files:
100
100
  - lib/net/imap/response_parser/parser_utils.rb
101
101
  - lib/net/imap/sasl.rb
102
102
  - lib/net/imap/sasl/anonymous_authenticator.rb
103
+ - lib/net/imap/sasl/authentication_exchange.rb
103
104
  - lib/net/imap/sasl/authenticators.rb
105
+ - lib/net/imap/sasl/client_adapter.rb
104
106
  - lib/net/imap/sasl/cram_md5_authenticator.rb
105
107
  - lib/net/imap/sasl/digest_md5_authenticator.rb
106
108
  - lib/net/imap/sasl/external_authenticator.rb
@@ -108,10 +110,12 @@ files:
108
110
  - lib/net/imap/sasl/login_authenticator.rb
109
111
  - lib/net/imap/sasl/oauthbearer_authenticator.rb
110
112
  - lib/net/imap/sasl/plain_authenticator.rb
113
+ - lib/net/imap/sasl/protocol_adapters.rb
111
114
  - lib/net/imap/sasl/scram_algorithm.rb
112
115
  - lib/net/imap/sasl/scram_authenticator.rb
113
116
  - lib/net/imap/sasl/stringprep.rb
114
117
  - lib/net/imap/sasl/xoauth2_authenticator.rb
118
+ - lib/net/imap/sasl_adapter.rb
115
119
  - lib/net/imap/stringprep.rb
116
120
  - lib/net/imap/stringprep/nameprep.rb
117
121
  - lib/net/imap/stringprep/saslprep.rb