Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📚 Update SASL documentation #308

Merged
merged 1 commit into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,9 @@ def starttls(**options)
# +SASL-IR+ capability, below). Defaults to the #config value for
# {sasl_ir}[rdoc-ref:Config#sasl_ir], which defaults to +true+.
#
# The +registry+ kwarg can be used to select the mechanism implementation
# from a custom registry. See SASL.authenticator and SASL::Authenticators.
#
# All other arguments are forwarded to the registered SASL authenticator for
# the requested mechanism. <em>The documentation for each individual
# mechanism must be consulted for its specific parameters.</em>
Expand Down
9 changes: 6 additions & 3 deletions lib/net/imap/sasl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ module SASL
# messages has not passed integrity checks.
AuthenticationFailed = Class.new(Error)

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

# Delegates to <tt>registry.new</tt> See Authenticators#new.
# Creates a new SASL authenticator, using SASL::Authenticators#new.
#
# +registry+ defaults to SASL.authenticators. All other arguments are
# forwarded to to <tt>registry.new</tt>.
def self.authenticator(*args, registry: authenticators, **kwargs, &block)
registry.new(*args, **kwargs, &block)
end
Expand Down
67 changes: 49 additions & 18 deletions lib/net/imap/sasl/authentication_exchange.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,70 @@ module SASL
# TODO: catch exceptions in #process and send #cancel_response.
# TODO: raise an error if the command succeeds after being canceled.
# TODO: use with more clients, to verify the API can accommodate them.
# TODO: pass ClientAdapter#service to SASL.authenticator
#
# Create an AuthenticationExchange from a client adapter and a mechanism
# authenticator:
# def authenticate(mechanism, ...)
# authenticator = SASL.authenticator(mechanism, ...)
# SASL::AuthenticationExchange.new(
# sasl_adapter, mechanism, authenticator
# ).authenticate
# end
#
# private
# An AuthenticationExchange represents a single attempt to authenticate
# a SASL client to a SASL server. It is created from a client adapter, a
# mechanism name, and a mechanism authenticator. When #authenticate is
# called, it will send the appropriate authenticate command to the server,
# returning the client response on success and raising an exception on
# failure.
#
# def sasl_adapter = MyClientAdapter.new(self, &method(:send_command))
# In most cases, the client will not need to use
# SASL::AuthenticationExchange directly at all. Instead, use
# SASL::ClientAdapter#authenticate. If customizations are needed, the
# custom client adapter is probably the best place for that code.
#
# Or delegate creation of the authenticator to ::build:
# def authenticate(...)
# SASL::AuthenticationExchange.build(sasl_adapter, ...)
# .authenticate
# MyClient::SASLAdapter.new(self).authenticate(...)
# end
#
# As a convenience, ::authenticate combines ::build and #authenticate:
# SASL::ClientAdapter#authenticate delegates to ::authenticate, like so:
#
# def authenticate(...)
# sasl_adapter = MyClient::SASLAdapter.new(self)
# SASL::AuthenticationExchange.authenticate(sasl_adapter, ...)
# end
#
# Likewise, ClientAdapter#authenticate delegates to #authenticate:
# def authenticate(...) = sasl_adapter.authenticate(...)
# ::authenticate simply delegates to ::build and #authenticate, like so:
#
# def authenticate(...)
# sasl_adapter = MyClient::SASLAdapter.new(self)
# SASL::AuthenticationExchange
# .build(sasl_adapter, ...)
# .authenticate
# end
#
# And ::build delegates to SASL.authenticator and ::new, like so:
#
# def authenticate(mechanism, ...)
# sasl_adapter = MyClient::SASLAdapter.new(self)
# authenticator = SASL.authenticator(mechanism, ...)
# SASL::AuthenticationExchange
# .new(sasl_adapter, mechanism, authenticator)
# .authenticate
# end
#
class AuthenticationExchange
# Convenience method for <tt>build(...).authenticate</tt>
#
# See also: SASL::ClientAdapter#authenticate
def self.authenticate(...) build(...).authenticate end

# Use +registry+ to override the global Authenticators registry.
# Convenience method to combine the creation of a new authenticator and
# a new Authentication exchange.
#
# +client+ must be an instance of SASL::ClientAdapter.
#
# +mechanism+ must be a SASL mechanism name, as a string or symbol.
#
# +sasl_ir+ allows or disallows sending an "initial response", depending
# also on whether the server capabilities, mechanism authenticator, and
# client adapter all support it. Defaults to +true+.
#
# +mechanism+, +args+, +kwargs+, and +block+ are all forwarded to
# SASL.authenticator. Use the +registry+ kwarg to override the global
# SASL::Authenticators registry.
def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)
authenticator = SASL.authenticator(mechanism, *args, **kwargs, &block)
new(client, mechanism, authenticator, sasl_ir: sasl_ir)
Expand Down
52 changes: 39 additions & 13 deletions lib/net/imap/sasl/client_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,52 +19,78 @@ module SASL
class ClientAdapter
include ProtocolAdapters::Generic

attr_reader :client, :command_proc
# The client that handles communication with the protocol server.
attr_reader :client

# +command_proc+ can used to avoid exposing private methods on #client.
# It should run a command with the arguments sent to it, yield each
# continuation payload, respond to the server with the result of each
# yield, and return the result. Non-successful results *MUST* raise an
# exception. Exceptions in the block *MUST* cause the command to fail.
# It's value is set by the block that is passed to ::new, and it is used
# by the default implementation of #run_command. Subclasses that
# override #run_command may use #command_proc for any other purpose they
# find useful.
#
# Subclasses that override #run_command may use #command_proc for
# other purposes.
# In the default implementation of #run_command, command_proc is called
# with the protocols authenticate +command+ name, the +mechanism+ name,
# an _optional_ +initial_response+ argument, and a +continuations+
# block. command_proc must run the protocol command with the arguments
# sent to it, _yield_ the payload of each continuation, respond to the
# continuation with the result of each _yield_, and _return_ the
# command's successful result. Non-successful results *MUST* raise
# an exception.
attr_reader :command_proc

# By default, this simply sets the #client and #command_proc attributes.
# Subclasses may override it, for example: to set the appropriate
# command_proc automatically.
def initialize(client, &command_proc)
@client, @command_proc = client, command_proc
end

# Delegates to AuthenticationExchange.authenticate.
# Attempt to authenticate #client to the server.
#
# By default, this simply delegates to
# AuthenticationExchange.authenticate.
def authenticate(...) AuthenticationExchange.authenticate(self, ...) end

# Do the protocol and server both support an initial response?
# Do the protocol, server, and client all support an initial response?
#
# By default, this simply delegates to <tt>client.sasl_ir_capable?</tt>.
def sasl_ir_capable?; client.sasl_ir_capable? end

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

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

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

# Drop the connection gracefully.
#
# By default, this simply delegates to <tt>client.drop_connection</tt>.
def drop_connection; client.drop_connection end

# Drop the connection abruptly.
#
# By default, this simply delegates to <tt>client.drop_connection!</tt>.
def drop_connection!; client.drop_connection! end
end
end
Expand Down
Loading