Class: Legate::Auth::ExconMiddleware

Inherits:
Excon::Middleware::Base
  • Object
show all
Defined in:
lib/legate/auth/excon_middleware.rb

Overview

Excon middleware for automatically handling authentication This middleware can be inserted into the Excon middleware stack to automatically apply authentication to requests and handle authentication errors.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(stack, options = {}) ⇒ ExconMiddleware

Initialize the middleware

Parameters:

  • stack (Array)

    The middleware stack

  • options (Hash) (defaults to: {})

    The options for configuring the middleware



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/legate/auth/excon_middleware.rb', line 33

def initialize(stack, options = {})
  super(stack)

  @scheme = options[:scheme]
  @credential = options[:credential]
  @token_store = options[:token_store]
  @token_manager = options[:token_manager]
  @auto_retry = options.fetch(:auto_retry, true)
  @max_retries = options.fetch(:max_retries, 3)
  @backoff_strategy = options.fetch(:backoff_strategy, :exponential)
  @backoff_factor = options.fetch(:backoff_factor, 1.0)
  @retry_non_idempotent = options.fetch(:retry_non_idempotent, false)
  @retry_on = Array(options.fetch(:retry_on, [])) + [401, 403]

  if @scheme && @credential
    Legate.logger.debug("ExconMiddleware: Factory-created instance configured: #{@scheme.scheme_type}") if defined?(Legate.logger)
    register_token_lifecycle_callbacks if @token_manager && @token_manager.respond_to?(:register_callback)
  elsif defined?(Legate.logger)
    # This is the shell instance created by Excon
    Legate.logger.debug('ExconMiddleware: Shell instance initialized by Excon.')
  end
end

Instance Attribute Details

#auto_retryObject (readonly)

Returns the value of attribute auto_retry.



28
29
30
# File 'lib/legate/auth/excon_middleware.rb', line 28

def auto_retry
  @auto_retry
end

#backoff_factorObject (readonly)

Returns the value of attribute backoff_factor.



28
29
30
# File 'lib/legate/auth/excon_middleware.rb', line 28

def backoff_factor
  @backoff_factor
end

#backoff_strategyObject (readonly)

Returns the value of attribute backoff_strategy.



28
29
30
# File 'lib/legate/auth/excon_middleware.rb', line 28

def backoff_strategy
  @backoff_strategy
end

#credentialObject (readonly)

Attributes needed by the shell middleware when accessing the configured instance



27
28
29
# File 'lib/legate/auth/excon_middleware.rb', line 27

def credential
  @credential
end

#max_retriesObject (readonly)

Returns the value of attribute max_retries.



28
29
30
# File 'lib/legate/auth/excon_middleware.rb', line 28

def max_retries
  @max_retries
end

#retry_non_idempotentObject (readonly)

Returns the value of attribute retry_non_idempotent.



28
29
30
# File 'lib/legate/auth/excon_middleware.rb', line 28

def retry_non_idempotent
  @retry_non_idempotent
end

#retry_onObject (readonly)

Returns the value of attribute retry_on.



28
29
30
# File 'lib/legate/auth/excon_middleware.rb', line 28

def retry_on
  @retry_on
end

#schemeObject (readonly)

Attributes needed by the shell middleware when accessing the configured instance



27
28
29
# File 'lib/legate/auth/excon_middleware.rb', line 27

def scheme
  @scheme
end

#token_managerObject (readonly)

Attributes needed by the shell middleware when accessing the configured instance



27
28
29
# File 'lib/legate/auth/excon_middleware.rb', line 27

def token_manager
  @token_manager
end

#token_storeObject (readonly)

Attributes needed by the shell middleware when accessing the configured instance



27
28
29
# File 'lib/legate/auth/excon_middleware.rb', line 27

def token_store
  @token_store
end

Class Method Details

.new(*args) ⇒ Object

Class-level new method for both factory creation and Excon middleware stack



15
16
17
18
19
20
21
22
23
24
# File 'lib/legate/auth/excon_middleware.rb', line 15

def self.new(*args)
  if args.length == 1 && args[0].is_a?(Array)
    # Called by Excon's middleware stack with just the stack
    super(args[0])
  else
    # Called by our factory with options
    stack, options = args
    super(stack, options || {})
  end
end

Instance Method Details

#request_call(datum) {|Hash| ... } ⇒ Hash

Called for each request in the Excon middleware stack

Parameters:

  • datum (Hash)

    The request/response data

Yields:

  • (Hash)

    The updated request/response data

Returns:

  • (Hash)

    The request/response data



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/legate/auth/excon_middleware.rb', line 60

def request_call(datum)
  # Determine if this is the shell or the configured instance
  # The shell instance will have a non-nil @stack from Excon,
  # and its @scheme will be nil (as Excon doesn't pass those to initialize by default)
  is_shell_instance = @scheme.nil? && @stack

  if is_shell_instance
    config_instance = datum[:connection].data[:auth_middleware_config]
    unless config_instance
      Legate.logger.warn('ExconMiddleware (shell): No :auth_middleware_config found. Passing through.') if defined?(Legate.logger)
      return @stack.request_call(datum)
    end
    Legate.logger.debug('ExconMiddleware (shell) delegating to configured instance for request logic.') if defined?(Legate.logger)
    # Modify datum using logic from config_instance, then shell calls @stack
    apply_authentication_logic(datum, config_instance)
    result = @stack.request_call(datum)
    result[:request] = datum[:request] if datum[:request]
    result
  else
    # This is the factory-configured instance, being called directly (e.g. by the shell, or in tests)
    # It should not call @stack.request_call itself if its @stack is the factory-provided nil.
    Legate.logger.debug('ExconMiddleware (configured instance) applying auth logic directly.') if defined?(Legate.logger)
    apply_authentication_logic(datum, self) # Apply logic using its own config
    datum
  end
end

#response_call(datum) {|Hash| ... } ⇒ Hash

Called after each response in the Excon middleware stack

Parameters:

  • datum (Hash)

    The request/response data

Yields:

  • (Hash)

    The updated request/response data

Returns:

  • (Hash)

    The request/response data



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/legate/auth/excon_middleware.rb', line 91

def response_call(datum)
  is_shell_instance = @scheme.nil? && @stack

  if is_shell_instance
    # Shell instance calls down the stack first
    response_datum = @stack.response_call(datum)

    config_instance = datum[:connection].data[:auth_middleware_config]
    unless config_instance
      Legate.logger.warn('ExconMiddleware (shell): No :auth_middleware_config for response. Passing through.') if defined?(Legate.logger)
      return response_datum
    end
    Legate.logger.debug('ExconMiddleware (shell) delegating to configured instance for response logic.') if defined?(Legate.logger)

    # Process response and handle retries
    if config_instance.auto_retry && should_retry?(response_datum[:request], response_datum[:response])
      config_instance.token_manager.invalidate_token(config_instance.scheme, config_instance.credential) if config_instance.token_manager && authentication_error?(response_datum[:response])
      # Re-apply authentication with fresh credentials
      apply_authentication_logic(response_datum, config_instance)
    end

    response_datum
  else
    # This is the factory-configured instance, being called by the shell.
    Legate.logger.debug('ExconMiddleware (configured instance) processing response logic directly.') if defined?(Legate.logger)

    # Process response and handle retries
    if @auto_retry && should_retry?(datum[:request], datum[:response])
      @token_manager.invalidate_token(@scheme, @credential) if @token_manager && authentication_error?(datum[:response])
      # Re-apply authentication with fresh credentials
      apply_authentication_logic(datum, self)
    end

    datum
  end
end

#should_retry?(request_datum, response_details) ⇒ Boolean

Returns:

  • (Boolean)


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/legate/auth/excon_middleware.rb', line 128

def should_retry?(request_datum, response_details)
  return false unless request_datum && response_details
  return false unless @auto_retry

  status = response_details[:status]
  return false unless status

  # Check if it's a non-idempotent request
  unless @retry_non_idempotent
    method = request_datum[:method]&.to_s&.upcase
    return false if method && !%w[GET HEAD OPTIONS].include?(method)
  end

  # Check retry conditions
  return true if @retry_on.include?(status)
  return true if authentication_error?(response_details)
  return true if (500..599).cover?(status)
  return true if response_details[:headers]&.key?('Retry-After')

  false
end