Module: Legate::Auth

Defined in:
lib/legate/auth.rb,
lib/legate/auth/error.rb,
lib/legate/auth/config.rb,
lib/legate/auth/runner.rb,
lib/legate/auth/scheme.rb,
lib/legate/auth/manager.rb,
lib/legate/auth/schemes.rb,
lib/legate/auth/url_guard.rb,
lib/legate/auth/credential.rb,
lib/legate/auth/encryption.rb,
lib/legate/auth/coordinator.rb,
lib/legate/auth/token_store.rb,
lib/legate/auth/manager_store.rb,
lib/legate/auth/token_manager.rb,
lib/legate/auth/schemes/oauth2.rb,
lib/legate/auth/schemes/api_key.rb,
lib/legate/auth/excon_middleware.rb,
lib/legate/auth/tool_integration.rb,
lib/legate/auth/http_client_utils.rb,
lib/legate/auth/middleware_factory.rb,
lib/legate/auth/schemes/http_bearer.rb,
lib/legate/auth/exchanged_credential.rb,
lib/legate/auth/schemes/openid_connect.rb,
lib/legate/auth/tool_context_extension.rb,
lib/legate/auth/schemes/service_account.rb,
lib/legate/auth/coordinators/oidc_coordinator.rb,
lib/legate/auth/schemes/google_service_account.rb,
lib/legate/auth/coordinators/oauth2_coordinator.rb,
lib/legate/auth/coordinators/service_account_coordinator.rb

Overview

The Auth module provides authentication capabilities for Legate tools. It supports various authentication schemes such as API Key, Bearer Token, OAuth2, OpenID Connect, and Service Accounts.

Examples:

Configure a tool with authentication

credential = Legate::Auth::Credential.new(
  auth_type: :api_key,
  api_key: ENV['API_KEY']
)

scheme = Legate::Auth::Schemes::ApiKey.new(
  location: :header,
  name: 'X-API-Key'
)

# Configure the tool with authentication
tool.configure(auth: {
  scheme: scheme,
  credential: credential
})

Defined Under Namespace

Modules: Coordinators, Encryption, HttpClientUtils, ManagerStore, Schemes, ToolContextExtension, ToolIntegration, UrlGuard Classes: Config, ConfigurationError, Coordinator, Credential, CredentialError, EnvironmentVariableNotFoundError, Error, ExchangedCredential, ExconMiddleware, Manager, MiddlewareFactory, ProviderError, Runner, Scheme, SchemeValidationError, TokenExchangeError, TokenManager, TokenRefreshError, TokenRevocationError, TokenStore

Constant Summary collapse

VERSION =

Version of the authentication module

'0.1.0'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.token_storeLegate::Auth::TokenStore

Get the global token store instance

Returns:



270
271
272
# File 'lib/legate/auth.rb', line 270

def token_store
  @token_store
end

Class Method Details

.apply_authentication(request, credential, scheme = nil) ⇒ Hash

Apply authentication to a request

Parameters:

Returns:

  • (Hash)

    The authenticated request

Raises:



77
78
79
80
81
82
83
84
85
86
87
# File 'lib/legate/auth.rb', line 77

def apply_authentication(request, credential, scheme = nil)
  if credential.is_a?(ExchangedCredential) && credential.provider_id
    # Look up the scheme from the stored config
    scheme ||= get_scheme_for_provider(credential.provider_id)
  end

  raise Legate::Auth::CredentialError, 'Authentication scheme is required' unless scheme

  # Apply the authentication to the request
  scheme.apply_to_request(request, credential)
end

.authenticate_with_coordinator(coordinator) ⇒ Legate::Auth::ExchangedCredential

Authenticate using a coordinator

Parameters:

Returns:

Raises:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/legate/auth.rb', line 93

def authenticate_with_coordinator(coordinator)
  # Start the authentication flow
  auth_request = coordinator.start

  # For non-interactive coordinators, the result might be available immediately
  if coordinator.complete?
    return coordinator.result if coordinator.success?

    raise coordinator.error || Legate::Auth::Error.new('Authentication failed')

  end

  # For interactive coordinators, we need to wait for user interaction
  # This method should only be used with non-interactive coordinators
  # or in testing scenarios where we can directly resume the coordinator
  raise Legate::Auth::Error.new('Coordinator requires interaction but no handler provided')
rescue StandardError => e
  # Wrap any errors that aren't already Legate::Auth::Error
  raise e.is_a?(Legate::Auth::Error) ? e : Legate::Auth::Error.new(e.message)
end

.complete_oauth_flow(provider_id, response_uri = nil) ⇒ Legate::Auth::ExchangedCredential

Complete the OAuth2 flow with a callback URI

Parameters:

  • provider_id (String)

    The provider ID

  • response_uri (String, nil) (defaults to: nil)

    The callback URI from the OAuth provider

Returns:

Raises:



218
219
220
221
222
223
224
225
226
# File 'lib/legate/auth.rb', line 218

def complete_oauth_flow(provider_id, response_uri = nil)
  # If no response URI is provided, wait for the callback
  response_uri ||= wait_for_oauth_callback

  raise Legate::Auth::ConfigurationError, 'No response URI provided or received' unless response_uri

  # Exchange the code for tokens
  exchange_oauth_code(provider_id, response_uri)
end

.configure_connection(connection, scheme:, credential:, **options) ⇒ Excon::Connection

Configure an existing Excon connection with authentication

Parameters:

  • connection (Excon::Connection)

    The connection to configure

  • scheme (Legate::Auth::Scheme)

    The authentication scheme to use

  • credential (Legate::Auth::Credential)

    The credential to use

  • options (Hash)

    Additional options for the middleware

Returns:

  • (Excon::Connection)

    The configured connection



297
298
299
300
301
302
303
304
305
306
# File 'lib/legate/auth.rb', line 297

def configure_connection(connection, scheme:, credential:, **options)
  Legate::Auth::HttpClientUtils.configure_connection(
    connection,
    scheme: scheme,
    credential: credential,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.create_api_key_connection(url, api_key:, location: 'header', name: 'X-API-Key', **options) ⇒ Excon::Connection

Create a new Excon connection with API key authentication

Parameters:

  • url (String)

    The URL for the connection

  • api_key (String)

    The API key to use

  • location (String) (defaults to: 'header')

    Where to place the API key (‘header’, ‘query’, ‘cookie’)

  • name (String) (defaults to: 'X-API-Key')

    The name of the parameter/header

  • options (Hash)

    Additional options for the connection and middleware

Returns:

  • (Excon::Connection)

    The configured connection



332
333
334
335
336
337
338
339
340
341
342
# File 'lib/legate/auth.rb', line 332

def create_api_key_connection(url, api_key:, location: 'header', name: 'X-API-Key', **options)
  Legate::Auth::HttpClientUtils.create_api_key_connection(
    url,
    api_key: api_key,
    location: location,
    name: name,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.create_basic_auth_connection(url, username:, password:, **options) ⇒ Excon::Connection

Create a new Excon connection with Basic authentication

Parameters:

  • url (String)

    The URL for the connection

  • username (String)

    The username to use

  • password (String)

    The password to use

  • options (Hash)

    Additional options for the connection and middleware

Returns:

  • (Excon::Connection)

    The configured connection



426
427
428
429
430
431
432
433
434
435
# File 'lib/legate/auth.rb', line 426

def create_basic_auth_connection(url, username:, password:, **options)
  Legate::Auth::HttpClientUtils.create_basic_auth_connection(
    url,
    username: username,
    password: password,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.create_bearer_connection(url, token:, **options) ⇒ Excon::Connection

Create a new Excon connection with bearer token authentication

Parameters:

  • url (String)

    The URL for the connection

  • token (String)

    The bearer token to use

  • options (Hash)

    Additional options for the connection and middleware

Returns:

  • (Excon::Connection)

    The configured connection



349
350
351
352
353
354
355
356
357
# File 'lib/legate/auth.rb', line 349

def create_bearer_connection(url, token:, **options)
  Legate::Auth::HttpClientUtils.create_bearer_connection(
    url,
    token: token,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.create_connection(url, scheme:, credential:, **options) ⇒ Excon::Connection

Create a new Excon connection with authentication

Parameters:

  • url (String)

    The URL for the connection

  • scheme (Legate::Auth::Scheme)

    The authentication scheme to use

  • credential (Legate::Auth::Credential)

    The credential to use

  • options (Hash)

    Additional options for the connection and middleware

Returns:

  • (Excon::Connection)

    The configured connection



314
315
316
317
318
319
320
321
322
323
# File 'lib/legate/auth.rb', line 314

def create_connection(url, scheme:, credential:, **options)
  Legate::Auth::HttpClientUtils.create_connection(
    url,
    scheme: scheme,
    credential: credential,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.create_connection_from_provider(url, provider_id, **options) ⇒ Excon::Connection

Create a new Excon connection using a previously configured provider

Parameters:

  • url (String)

    The URL for the connection

  • provider_id (String)

    The provider ID to use

  • options (Hash)

    Additional options for the connection and middleware

Returns:

  • (Excon::Connection)

    The configured connection



442
443
444
445
446
447
448
449
450
# File 'lib/legate/auth.rb', line 442

def create_connection_from_provider(url, provider_id, **options)
  Legate::Auth::HttpClientUtils.create_connection_from_provider(
    url,
    provider_id,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.create_middleware(scheme:, credential:, **options) ⇒ Legate::Auth::ExconMiddleware

Create an authentication middleware for the specified scheme and credential

Parameters:

Returns:



281
282
283
284
285
286
287
288
289
# File 'lib/legate/auth.rb', line 281

def create_middleware(scheme:, credential:, **options)
  Legate::Auth::MiddlewareFactory.create(
    scheme: scheme,
    credential: credential,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.create_oauth2_connection(url, client_id:, client_secret:, authorization_url:, token_url:, scopes: nil, **options) ⇒ Excon::Connection

Create a new Excon connection with OAuth2 authentication

Parameters:

  • url (String)

    The URL for the connection

  • client_id (String)

    The OAuth client ID

  • client_secret (String)

    The OAuth client secret

  • authorization_url (String)

    The authorization URL for the OAuth provider

  • token_url (String)

    The token URL for the OAuth provider

  • scopes (Array<String>, String, nil) (defaults to: nil)

    The scopes to request

  • options (Hash)

    Additional options for the connection and middleware

Returns:

  • (Excon::Connection)

    The configured connection



368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/legate/auth.rb', line 368

def create_oauth2_connection(url, client_id:, client_secret:, authorization_url:, token_url:, scopes: nil, **options)
  Legate::Auth::HttpClientUtils.create_oauth2_connection(
    url,
    client_id: client_id,
    client_secret: client_secret,
    authorization_url: authorization_url,
    token_url: token_url,
    scopes: scopes,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.create_oidc_connection(url, client_id:, client_secret:, discovery_url:, **options) ⇒ Excon::Connection

Create a new Excon connection with OpenID Connect authentication

Parameters:

  • url (String)

    The URL for the connection

  • client_id (String)

    The client ID to use

  • client_secret (String)

    The client secret to use

  • discovery_url (String)

    The OIDC discovery URL

  • options (Hash)

    Additional options for the connection and middleware

Returns:

  • (Excon::Connection)

    The configured connection



389
390
391
392
393
394
395
396
397
398
399
# File 'lib/legate/auth.rb', line 389

def create_oidc_connection(url, client_id:, client_secret:, discovery_url:, **options)
  Legate::Auth::HttpClientUtils.create_oidc_connection(
    url,
    client_id: client_id,
    client_secret: client_secret,
    discovery_url: discovery_url,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.create_service_account_connection(url, service_account_key:, scopes: nil, audience: nil, **options) ⇒ Excon::Connection

Create a new Excon connection with service account authentication

Parameters:

  • url (String)

    The URL for the connection

  • service_account_key (String, Hash)

    The service account key as JSON string or Hash

  • scopes (Array<String>, String, nil) (defaults to: nil)

    The scopes to request

  • audience (String, nil) (defaults to: nil)

    The audience for the token

  • options (Hash)

    Additional options for the connection and middleware

Returns:

  • (Excon::Connection)

    The configured connection



408
409
410
411
412
413
414
415
416
417
418
# File 'lib/legate/auth.rb', line 408

def (url, service_account_key:, scopes: nil, audience: nil, **options)
  Legate::Auth::HttpClientUtils.(
    url,
    service_account_key: ,
    scopes: scopes,
    audience: audience,
    token_store: options[:token_store] || token_store,
    token_manager: options[:token_manager],
    **options
  )
end

.exchange_oauth_code(provider_id, response_uri) ⇒ Legate::Auth::ExchangedCredential

Exchange an authorization code for tokens

Parameters:

  • provider_id (String)

    The provider ID

  • response_uri (String)

    The callback URI from the OAuth provider

Returns:

Raises:



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/legate/auth.rb', line 176

def exchange_oauth_code(provider_id, response_uri)
  config = @config_store[provider_id]

  raise Legate::Auth::ConfigurationError, "No stored configuration for provider #{provider_id}" unless config

  # Create a response config with the response URI
  response_config = Config.new(
    scheme: config.scheme,
    credential: config.credential,
    auth_request_id: config.auth_request_id
  )
  response_config.response_uri = response_uri
  response_config.state = config.state

  # Validate the response
  config.validate_response!(response_config)

  # Exchange the code for tokens
  exchanged_credential = config.scheme.exchange_token(response_config, config.credential)

  # Add the provider ID to the credential
  exchanged_credential.provider_id = provider_id

  # Store the credential in the token store
  @token_store.store(provider_id, exchanged_credential)

  exchanged_credential
end

.generate_request_idString

Generate a unique request ID

Returns:

  • (String)

    A unique request ID



66
67
68
69
# File 'lib/legate/auth.rb', line 66

def generate_request_id
  require 'securerandom'
  SecureRandom.uuid
end

.get_exchanged_credential(provider_id) ⇒ Legate::Auth::ExchangedCredential?

Get a stored exchanged credential for a provider

Parameters:

  • provider_id (String)

    The provider ID

Returns:



208
209
210
# File 'lib/legate/auth.rb', line 208

def get_exchanged_credential(provider_id)
  @token_store.retrieve(provider_id)
end

.handle_oauth_callback(response_uri) ⇒ Boolean

Handle an OAuth callback

Parameters:

  • response_uri (String)

    The callback URI from the OAuth provider

Returns:

  • (Boolean)

    True if the callback was successfully handled

Raises:



139
140
141
142
143
144
145
146
# File 'lib/legate/auth.rb', line 139

def handle_oauth_callback(response_uri)
  @oauth_mutex.synchronize do
    @oauth_response_uri = response_uri
    @oauth_condition.signal
  end

  true
end

.refresh_token(provider_id) ⇒ Legate::Auth::ExchangedCredential

Refresh an access token

Parameters:

  • provider_id (String)

    The provider ID

Returns:

Raises:



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/legate/auth.rb', line 232

def refresh_token(provider_id)
  # Get the stored credential
  exchanged_credential = get_exchanged_credential(provider_id)

  raise Legate::Auth::TokenRefreshError, "No stored credential for provider #{provider_id}" unless exchanged_credential

  # Get the scheme
  scheme = get_scheme_for_provider(provider_id)

  raise Legate::Auth::TokenRefreshError, "Scheme for provider #{provider_id} does not support token refresh" unless scheme && scheme.supports_refresh?

  # Get the original credential
  config = @config_store[provider_id]

  raise Legate::Auth::TokenRefreshError, "No stored configuration for provider #{provider_id}" unless config

  # Refresh the token
  refreshed_credential = scheme.refresh_token(exchanged_credential, config.credential)

  # Add the provider ID to the credential
  refreshed_credential.provider_id = provider_id

  # Store the refreshed credential
  @token_store.store(provider_id, refreshed_credential)

  refreshed_credential
end

.start_oauth_flow(provider_id, scheme, credential, redirect_uri = nil, options = {}) ⇒ String

Start the OAuth2 authentication flow

Parameters:

  • provider_id (String)

    A unique identifier for the provider

  • scheme (Legate::Auth::Scheme)

    The authentication scheme

  • credential (Legate::Auth::Credential)

    The credential to use

  • redirect_uri (String, nil) (defaults to: nil)

    The redirect URI for the authorization request

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

    Additional options for the authentication process

Returns:

  • (String)

    The authorization URI to redirect the user to

Raises:



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/legate/auth.rb', line 122

def start_oauth_flow(provider_id, scheme, credential, redirect_uri = nil, options = {})
  # Create a new config
  config = Config.new(scheme: scheme, credential: credential, options: options)

  # Build the authorization URI
  auth_uri = config.build_authorization_uri(redirect_uri)

  # Store the config
  @config_store[provider_id] = config

  auth_uri
end

.wait_for_oauth_callback(timeout = nil) ⇒ String?

Wait for the OAuth callback to be received

Parameters:

  • timeout (Integer, nil) (defaults to: nil)

    The timeout in seconds (nil for no timeout)

Returns:

  • (String, nil)

    The response URI from the OAuth provider



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/legate/auth.rb', line 151

def wait_for_oauth_callback(timeout = nil)
  response_uri = nil

  @oauth_mutex.synchronize do
    if timeout
      # Wait with timeout
      @oauth_condition.wait(@oauth_mutex, timeout) if @oauth_response_uri.nil?
    else
      # Wait indefinitely
      @oauth_condition.wait(@oauth_mutex) while @oauth_response_uri.nil?
    end

    response_uri = @oauth_response_uri
    @oauth_response_uri = nil
  end

  response_uri
end