Skip to content

Commit

Permalink
🥅 Cancel AUTHENTICATE if process raises an error
Browse files Browse the repository at this point in the history
The exception will be re-raised after the protocol cancel response has
been sent.
  • Loading branch information
nevans committed Jun 30, 2024
1 parent 4ba5f94 commit 999e5ac
Showing 1 changed file with 17 additions and 4 deletions.
21 changes: 17 additions & 4 deletions lib/net/imap/sasl/authentication_exchange.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ module SASL

# This API is *experimental*, and may change.
#
# 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.
#
# An AuthenticationExchange represents a single attempt to authenticate
Expand Down Expand Up @@ -79,6 +77,9 @@ def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)

attr_reader :mechanism, :authenticator

# An exception that has been raised by <tt>authenticator.process</tt>.
attr_reader :process_error

def initialize(client, mechanism, authenticator, sasl_ir: true)
client => SASL::ClientAdapter
@client = client
Expand All @@ -92,8 +93,17 @@ def initialize(client, mechanism, authenticator, sasl_ir: true)
# using #authenticator. Authentication failures will raise an
# exception. Any exceptions other than AuthenticationCanceled or those
# in <tt>client.response_errors</tt> will drop the connection.
#
# When <tt>authenticator.process</tt> raises any StandardError
# (including AuthenticationCanceled), the authentication exchange will
# be canceled before re-raising the exception. The server will usually
# respond with an error response, and the client will most likely raise
# that error. This client error will supercede the original error.
# Unfortunately, the original error will not be the +#cause+ for the
# client error. But it will be available on #process_error.
def authenticate
client.run_command(mechanism, initial_response) { process _1 }
.tap { raise process_error if process_error }
.tap { raise AuthenticationIncomplete, _1 unless done? }
rescue AuthenticationCanceled, *client.response_errors
raise # but don't drop the connection
Expand Down Expand Up @@ -127,9 +137,12 @@ def initial_response
end

def process(challenge)
client.encode authenticator.process client.decode challenge
ensure
@processed = true
return client.cancel_response if process_error
client.encode authenticator.process client.decode challenge
rescue => process_error
@process_error = process_error
client.cancel_response
end

end
Expand Down

0 comments on commit 999e5ac

Please sign in to comment.