@@ -7,8 +7,6 @@ module SASL
7
7
# AuthenticationExchange is used internally by Net::IMAP#authenticate.
8
8
# But the API is still *experimental*, and may change.
9
9
#
10
- # TODO: catch exceptions in #process and send #cancel_response.
11
- # TODO: raise an error if the command succeeds after being canceled.
12
10
# TODO: use with more clients, to verify the API can accommodate them.
13
11
# TODO: pass ClientAdapter#service to SASL.authenticator
14
12
#
@@ -81,6 +79,9 @@ def self.build(client, mechanism, *args, sasl_ir: true, **kwargs, &block)
81
79
82
80
attr_reader :mechanism , :authenticator
83
81
82
+ # An exception that has been raised by <tt>authenticator.process</tt>.
83
+ attr_reader :process_error
84
+
84
85
def initialize ( client , mechanism , authenticator , sasl_ir : true )
85
86
@client = client
86
87
@mechanism = Authenticators . normalize_name ( mechanism )
@@ -93,8 +94,17 @@ def initialize(client, mechanism, authenticator, sasl_ir: true)
93
94
# using #authenticator. Authentication failures will raise an
94
95
# exception. Any exceptions other than AuthenticationCanceled or those
95
96
# in <tt>client.response_errors</tt> will drop the connection.
97
+ #
98
+ # When <tt>authenticator.process</tt> raises any StandardError
99
+ # (including AuthenticationCanceled), the authentication exchange will
100
+ # be canceled before re-raising the exception. The server will usually
101
+ # respond with an error response, and the client will most likely raise
102
+ # that error. This client error will supercede the original error.
103
+ # Unfortunately, the original error will not be the +#cause+ for the
104
+ # client error. But it will be available on #process_error.
96
105
def authenticate
97
106
client . run_command ( mechanism , initial_response ) { process _1 }
107
+ . tap { raise process_error if process_error }
98
108
. tap { raise AuthenticationIncomplete , _1 unless done? }
99
109
rescue AuthenticationCanceled , *client . response_errors
100
110
raise # but don't drop the connection
@@ -128,9 +138,12 @@ def initial_response
128
138
end
129
139
130
140
def process ( challenge )
131
- client . encode authenticator . process client . decode challenge
132
- ensure
133
141
@processed = true
142
+ return client . cancel_response if process_error
143
+ client . encode authenticator . process client . decode challenge
144
+ rescue => process_error
145
+ @process_error = process_error
146
+ client . cancel_response
134
147
end
135
148
136
149
end
0 commit comments