Class: A2A::Client::Auth::Interceptor

Inherits:
Object
  • Object
show all
Defined in:
lib/a2a/client/auth/interceptor.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(strategy, auto_retry: true) ⇒ Interceptor

Initialize authentication interceptor

Parameters:

  • strategy (Object)

    Authentication strategy (OAuth2, JWT, ApiKey, etc.)

  • auto_retry (Boolean) (defaults to: true)

    Whether to automatically retry on auth failures



25
26
27
28
29
# File 'lib/a2a/client/auth/interceptor.rb', line 25

def initialize(strategy, auto_retry: true)
  @strategy = strategy
  @auto_retry = auto_retry
  @retry_mutex = Mutex.new
end

Instance Attribute Details

#auto_retryObject (readonly)

Returns the value of attribute auto_retry.



18
19
20
# File 'lib/a2a/client/auth/interceptor.rb', line 18

def auto_retry
  @auto_retry
end

#strategyObject (readonly)

Returns the value of attribute strategy.



18
19
20
# File 'lib/a2a/client/auth/interceptor.rb', line 18

def strategy
  @strategy
end

Class Method Details

.from_config(config) ⇒ Interceptor

Create interceptor from configuration

Parameters:

  • config (Hash)

    Authentication configuration

Returns:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/a2a/client/auth/interceptor.rb', line 80

def self.from_config(config)
  strategy = case config["type"] || config[:type]
             when "oauth2"
               OAuth2.new(
                 client_id: config["client_id"] || config[:client_id],
                 client_secret: config["client_secret"] || config[:client_secret],
                 token_url: config["token_url"] || config[:token_url],
                 scope: config["scope"] || config[:scope]
               )
             when "jwt"
               JWT.new(
                 token: config["token"] || config[:token],
                 secret: config["secret"] || config[:secret],
                 algorithm: config["algorithm"] || config[:algorithm] || "HS256",
                 payload: config["payload"] || config[:payload],
                 headers: config["headers"] || config[:headers],
                 expires_in: config["expires_in"] || config[:expires_in]
               )
             when "api_key"
               ApiKey.new(
                 key: config["key"] || config[:key],
                 name: config["name"] || config[:name] || "X-API-Key",
                 location: config["location"] || config[:location] || "header"
               )
             else
               raise ArgumentError, "Unknown authentication type: #{config['type'] || config[:type]}"
             end

  new(strategy, auto_retry: config["auto_retry"] || config[:auto_retry] || true)
end

.from_security_scheme(scheme, credentials) ⇒ Interceptor

Create interceptor from security scheme

Parameters:

  • scheme (Hash)

    Security scheme definition

  • credentials (Hash)

    Authentication credentials

Returns:



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/a2a/client/auth/interceptor.rb', line 117

def self.from_security_scheme(scheme, credentials)
  strategy = case scheme["type"]
             when "oauth2"
               OAuth2.new(
                 client_id: credentials["client_id"],
                 client_secret: credentials["client_secret"],
                 token_url: scheme["tokenUrl"],
                 scope: credentials["scope"]
               )
             when "http"
               case scheme["scheme"]
               when "bearer"
                 JWT.new(token: credentials["token"])
               when "basic"
                 # Basic auth would be handled differently
                 raise ArgumentError, "Basic auth not yet implemented"
               else
                 raise ArgumentError, "Unsupported HTTP scheme: #{scheme['scheme']}"
               end
             when "apiKey"
               ApiKey.from_security_scheme(scheme, credentials["key"])
             else
               raise ArgumentError, "Unsupported security scheme type: #{scheme['type']}"
             end

  new(strategy)
end

Instance Method Details

#apply_authentication(request) ⇒ Object

Apply authentication to a request

Parameters:

  • request (Object)

    The request to authenticate



63
64
65
66
67
68
69
70
71
72
73
# File 'lib/a2a/client/auth/interceptor.rb', line 63

def apply_authentication(request)
  case @strategy
  when OAuth2, JWT, ApiKey
    @strategy.apply_to_request(request)
  when Hash
    # Handle configuration-based authentication
    apply_config_authentication(request, @strategy)
  else
    raise ArgumentError, "Unsupported authentication strategy: #{@strategy.class}"
  end
end

#apply_config_authentication(request, config) ⇒ Object (private)

Apply configuration-based authentication

Parameters:

  • request (Object)

    The request to authenticate

  • config (Hash)

    Authentication configuration



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/a2a/client/auth/interceptor.rb', line 192

def apply_config_authentication(request, config)
  case config["type"] || config[:type]
  when "bearer"
    request.headers["Authorization"] = "Bearer #{config['token'] || config[:token]}"
  when "basic"
    require "base64"
    username = config["username"] || config[:username]
    password = config["password"] || config[:password]
    credentials = Base64.strict_encode64("#{username}:#{password}")
    request.headers["Authorization"] = "Basic #{credentials}"
  when "api_key"
    name = config["name"] || config[:name] || "X-API-Key"
    key = config["key"] || config[:key]
    location = config["location"] || config[:location] || "header"

    case location
    when "header"
      request.headers[name] = key
    when "query"
      request.params[name] = key
    end
  end
end

#authentication_error?(response) ⇒ Boolean (private)

Check if response indicates authentication error

Parameters:

  • response (Object)

    The response to check

Returns:

  • (Boolean)

    True if authentication error



221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/a2a/client/auth/interceptor.rb', line 221

def authentication_error?(response)
  # This would depend on the response format
  # For HTTP responses, check status codes
  return [401, 403].include?(response.status) if response.respond_to?(:status)

  # For JSON-RPC responses, check error codes
  if response.respond_to?(:[]) && response["error"]
    error_code = response["error"]["code"]
    return [A2A::Protocol::JsonRpc::AUTHENTICATION_REQUIRED, A2A::Protocol::JsonRpc::AUTHORIZATION_FAILED].include?(error_code)
  end

  false
end

#call(request, context, next_middleware) ⇒ Object

Middleware call method for Faraday

Parameters:

  • request (Object)

    The request object

  • context (Hash)

    Request context

  • next_middleware (Proc)

    Next middleware in chain

Returns:

  • (Object)

    Response from next middleware



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/a2a/client/auth/interceptor.rb', line 38

def call(request, context, next_middleware)
  # Apply authentication to the request
  apply_authentication(request)

  # Execute the request
  begin
    response = next_middleware.call(request, context)

    # Check for authentication errors and retry if configured
    if authentication_error?(response) && @auto_retry
      handle_auth_error_retry(request, context, next_middleware)
    else
      response
    end
  rescue A2A::Errors::AuthenticationError, A2A::Errors::AuthorizationFailed => e
    raise e unless @auto_retry

    handle_auth_error_retry(request, context, next_middleware)
  end
end

#handle_auth_error_retry(request, context, next_middleware) ⇒ Object (private)

Handle authentication error with retry

Parameters:

  • request (Object)

    The original request

  • context (Hash)

    Request context

  • next_middleware (Proc)

    Next middleware in chain

Returns:

  • (Object)

    Response from retry attempt

Raises:



242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/a2a/client/auth/interceptor.rb', line 242

def handle_auth_error_retry(request, context, next_middleware)
  @retry_mutex.synchronize do
    # Try to refresh credentials
    if refresh!
      # Reapply authentication and retry
      apply_authentication(request)
      return next_middleware.call(request, context)
    end
  end

  # If refresh failed, raise the original error
  raise A2A::Errors::AuthenticationError, "Authentication failed and refresh unsuccessful"
end

#refresh!Boolean

Refresh authentication credentials

Returns:

  • (Boolean)

    True if refresh was successful



157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/a2a/client/auth/interceptor.rb', line 157

def refresh!
  return false unless supports_refresh?

  begin
    if @strategy.respond_to?(:refresh_token!)
      @strategy.refresh_token!
    elsif @strategy.respond_to?(:regenerate_token!)
      @strategy.regenerate_token!
    end
    true
  rescue StandardError
    false
  end
end

#statusHash

Get current authentication status

Returns:

  • (Hash)

    Status information



176
177
178
179
180
181
182
183
# File 'lib/a2a/client/auth/interceptor.rb', line 176

def status
  {
    strategy: @strategy.class.name,
    valid: strategy_valid?,
    supports_refresh: supports_refresh?,
    expires_at: strategy_expires_at
  }
end

#strategy_expires_atTime? (private)

Get strategy expiration time

Returns:

  • (Time, nil)

    Expiration time if available



277
278
279
280
281
282
283
284
# File 'lib/a2a/client/auth/interceptor.rb', line 277

def strategy_expires_at
  case @strategy
  when OAuth2
    @strategy.expires_at
  when JWT
    @strategy.expires_at
  end
end

#strategy_valid?Boolean (private)

Check if the current strategy is valid

Returns:

  • (Boolean)

    True if strategy is valid



260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/a2a/client/auth/interceptor.rb', line 260

def strategy_valid?
  case @strategy
  when OAuth2
    @strategy.token_valid?
  when JWT
    !@strategy.token_expired?
  when ApiKey
    @strategy.valid?
  else
    true # Assume valid for unknown strategies
  end
end

#supports_refresh?Boolean

Check if the strategy supports token refresh

Returns:

  • (Boolean)

    True if strategy supports refresh



149
150
151
# File 'lib/a2a/client/auth/interceptor.rb', line 149

def supports_refresh?
  @strategy.respond_to?(:refresh_token!) || @strategy.respond_to?(:regenerate_token!)
end