Skip to content

Commit 7cbf096

Browse files
committed
📚 Update SASL documentation
1 parent 725a10e commit 7cbf096

File tree

4 files changed

+97
-34
lines changed

4 files changed

+97
-34
lines changed

lib/net/imap.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,9 @@ def starttls(**options)
12421242
# +SASL-IR+ capability, below). Defaults to the #config value for
12431243
# {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
12441244
#
1245+
# The +registry+ kwarg can be used to select the mechanism implementation
1246+
# from a custom registry. See SASL.authenticator and SASL::Authenticators.
1247+
#
12451248
# All other arguments are forwarded to the registered SASL authenticator for
12461249
# the requested mechanism. <em>The documentation for each individual
12471250
# mechanism must be consulted for its specific parameters.</em>

lib/net/imap/sasl.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ module SASL
114114
# messages has not passed integrity checks.
115115
AuthenticationFailed = Class.new(Error)
116116

117-
# Indicates that authentication cannot proceed because one of the server's
118-
# ended authentication prematurely.
117+
# Indicates that authentication cannot proceed because the server ended
118+
# authentication prematurely.
119119
class AuthenticationIncomplete < AuthenticationFailed
120120
# The success response from the server
121121
attr_reader :response
@@ -159,7 +159,10 @@ def initialize(response, message = "authentication ended prematurely")
159159
# Returns the default global SASL::Authenticators instance.
160160
def self.authenticators; @authenticators ||= Authenticators.new end
161161

162-
# Delegates to <tt>registry.new</tt> See Authenticators#new.
162+
# Creates a new SASL authenticator, using SASL::Authenticators#new.
163+
#
164+
# +registry+ defaults to SASL.authenticators. All other arguments are
165+
# forwarded to to <tt>registry.new</tt>.
163166
def self.authenticator(*args, registry: authenticators, **kwargs, &block)
164167
registry.new(*args, **kwargs, &block)
165168
end

lib/net/imap/sasl/authentication_exchange.rb

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,39 +9,70 @@ module SASL
99
# TODO: catch exceptions in #process and send #cancel_response.
1010
# TODO: raise an error if the command succeeds after being canceled.
1111
# TODO: use with more clients, to verify the API can accommodate them.
12+
# TODO: pass ClientAdapter#service to SASL.authenticator
1213
#
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
14+
# An AuthenticationExchange represents a single attempt to authenticate
15+
# a SASL client to a SASL server. It is created from a client adapter, a
16+
# mechanism name, and a mechanism authenticator. When #authenticate is
17+
# called, it will send the appropriate authenticate command to the server,
18+
# returning the client response on success and raising an exception on
19+
# failure.
2320
#
24-
# def sasl_adapter = MyClientAdapter.new(self, &method(:send_command))
21+
# In most cases, the client will not need to use
22+
# SASL::AuthenticationExchange directly at all. Instead, use
23+
# SASL::ClientAdapter#authenticate. If customizations are needed, the
24+
# custom client adapter is probably the best place for that code.
2525
#
26-
# Or delegate creation of the authenticator to ::build:
2726
# def authenticate(...)
28-
# SASL::AuthenticationExchange.build(sasl_adapter, ...)
29-
# .authenticate
27+
# MyClient::SASLAdapter.new(self).authenticate(...)
3028
# end
3129
#
32-
# As a convenience, ::authenticate combines ::build and #authenticate:
30+
# SASL::ClientAdapter#authenticate delegates to ::authenticate, like so:
31+
#
3332
# def authenticate(...)
33+
# sasl_adapter = MyClient::SASLAdapter.new(self)
3434
# SASL::AuthenticationExchange.authenticate(sasl_adapter, ...)
3535
# end
3636
#
37-
# Likewise, ClientAdapter#authenticate delegates to #authenticate:
38-
# def authenticate(...) = sasl_adapter.authenticate(...)
37+
# ::authenticate simply delegates to ::build and #authenticate, like so:
38+
#
39+
# def authenticate(...)
40+
# sasl_adapter = MyClient::SASLAdapter.new(self)
41+
# SASL::AuthenticationExchange
42+
# .build(sasl_adapter, ...)
43+
# .authenticate
44+
# end
45+
#
46+
# And ::build delegates to SASL.authenticator and ::new, like so:
47+
#
48+
# def authenticate(mechanism, ...)
49+
# sasl_adapter = MyClient::SASLAdapter.new(self)
50+
# authenticator = SASL.authenticator(mechanism, ...)
51+
# SASL::AuthenticationExchange
52+
# .new(sasl_adapter, mechanism, authenticator)
53+
# .authenticate
54+
# end
3955
#
4056
class AuthenticationExchange
4157
# Convenience method for <tt>build(...).authenticate</tt>
58+
#
59+
# See also: SASL::ClientAdapter#authenticate
4260
def self.authenticate(...) build(...).authenticate end
4361

44-
# Use +registry+ to override the global Authenticators registry.
62+
# Convenience method to combine the creation of a new authenticator and
63+
# a new Authentication exchange.
64+
#
65+
# +client+ must be an instance of SASL::ClientAdapter.
66+
#
67+
# +mechanism+ must be a SASL mechanism name, as a string or symbol.
68+
#
69+
# +sasl_ir+ allows or disallows sending an "initial response", depending
70+
# also on whether the server capabilities, mechanism authenticator, and
71+
# client adapter all support it. Defaults to +true+.
72+
#
73+
# +mechanism+, +args+, +kwargs+, and +block+ are all forwarded to
74+
# SASL.authenticator. Use the +registry+ kwarg to override the global
75+
# SASL::Authenticators registry.
4576
def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)
4677
authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block)
4778
new(client, mechanism, authenticator, sasl_ir: sasl_ir)

lib/net/imap/sasl/client_adapter.rb

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,52 +19,78 @@ module SASL
1919
class ClientAdapter
2020
include ProtocolAdapters::Generic
2121

22-
attr_reader :client, :command_proc
22+
# The client that handles communication with the protocol server.
23+
attr_reader :client
2324

2425
# +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.
26+
# It's value is set by the block that is passed to ::new, and it is used
27+
# by the default implementation of #run_command. Subclasses that
28+
# override #run_command may use #command_proc for any other purpose they
29+
# find useful.
2930
#
30-
# Subclasses that override #run_command may use #command_proc for
31-
# other purposes.
31+
# In the default implementation of #run_command, command_proc is called
32+
# with the protocols authenticate +command+ name, the +mechanism+ name,
33+
# an _optional_ +initial_response+ argument, and a +continuations+
34+
# block. command_proc must run the protocol command with the arguments
35+
# sent to it, _yield_ the payload of each continuation, respond to the
36+
# continuation with the result of each _yield_, and _return_ the
37+
# command's successful result. Non-successful results *MUST* raise
38+
# an exception.
39+
attr_reader :command_proc
40+
41+
# By default, this simply sets the #client and #command_proc attributes.
42+
# Subclasses may override it, for example: to set the appropriate
43+
# command_proc automatically.
3244
def initialize(client, &command_proc)
3345
@client, @command_proc = client, command_proc
3446
end
3547

36-
# Delegates to AuthenticationExchange.authenticate.
48+
# Attempt to authenticate #client to the server.
49+
#
50+
# By default, this simply delegates to
51+
# AuthenticationExchange.authenticate.
3752
def authenticate(...) AuthenticationExchange.authenticate(self, ...) end
3853

39-
# Do the protocol and server both support an initial response?
54+
# Do the protocol, server, and client all support an initial response?
55+
#
56+
# By default, this simply delegates to <tt>client.sasl_ir_capable?</tt>.
4057
def sasl_ir_capable?; client.sasl_ir_capable? end
4158

4259
# Does the server advertise support for the mechanism?
60+
#
61+
# By default, this simply delegates to <tt>client.auth_capable?</tt>.
4362
def auth_capable?(mechanism); client.auth_capable?(mechanism) end
4463

45-
# Runs the authenticate command with +mechanism+ and +initial_response+.
46-
# When +initial_response+ is nil, an initial response must NOT be sent.
64+
# Calls command_proc with +command_name+ (see
65+
# SASL::ProtocolAdapters::Generic#command_name),
66+
# +mechanism+, +initial_response+, and a +continuations_handler+ block.
67+
# The +initial_response+ is optional; when it's nil, it won't be sent to
68+
# command_proc.
4769
#
4870
# Yields each continuation payload, responds to the server with the
4971
# result of each yield, and returns the result. Non-successful results
5072
# *MUST* raise an exception. Exceptions in the block *MUST* cause the
5173
# command to fail.
5274
#
5375
# Subclasses that override this may use #command_proc differently.
54-
def run_command(mechanism, initial_response = nil, &block)
76+
def run_command(mechanism, initial_response = nil, &continuations_handler)
5577
command_proc or raise Error, "initialize with block or override"
5678
args = [command_name, mechanism, initial_response].compact
57-
command_proc.call(*args, &block)
79+
command_proc.call(*args, &continuations_handler)
5880
end
5981

6082
# Returns an array of server responses errors raised by run_command.
6183
# Exceptions in this array won't drop the connection.
6284
def response_errors; [] end
6385

6486
# Drop the connection gracefully.
87+
#
88+
# By default, this simply delegates to <tt>client.drop_connection</tt>.
6589
def drop_connection; client.drop_connection end
6690

6791
# Drop the connection abruptly.
92+
#
93+
# By default, this simply delegates to <tt>client.drop_connection!</tt>.
6894
def drop_connection!; client.drop_connection! end
6995
end
7096
end

0 commit comments

Comments
 (0)