Class: Legate::Auth::TokenManager
- Inherits:
-
Object
- Object
- Legate::Auth::TokenManager
- Defined in:
- lib/legate/auth/token_manager.rb
Overview
TokenManager is responsible for managing the lifecycle of authentication tokens. It provides a centralized system for token acquisition, refresh, and invalidation. This class works with the TokenStore for persistence and the various authentication schemes for token operations.
Constant Summary collapse
- DEFAULT_CONFIG =
Default configuration values
{ refresh_buffer: 60, # Seconds before expiration to trigger refresh retry_max_attempts: 3, # Maximum number of refresh retry attempts retry_delay: 2, # Initial delay between retries (seconds) retry_backoff: 1.5, # Backoff multiplier for subsequent retries auto_refresh: true, # Whether to automatically refresh tokens background_refresh: false # Whether to refresh tokens in background }.freeze
Instance Method Summary collapse
-
#get_token(scheme, credential, force_refresh: false) ⇒ Legate::Auth::ExchangedCredential?
Get a token for the given scheme and credential.
-
#initialize(token_store, config = {}) ⇒ TokenManager
constructor
Initialize a new TokenManager.
-
#invalidate_token(cache_key) ⇒ Boolean
Invalidate a token, removing it from the store.
-
#on(event, &callback) ⇒ self
Register a callback for token lifecycle events.
-
#refresh_token(scheme, credential, token = nil, cache_key = nil) ⇒ Legate::Auth::ExchangedCredential?
Explicitly refresh a token.
-
#revoke_token(scheme, credential, token) ⇒ Boolean
Revoke a token with the authentication provider.
Constructor Details
#initialize(token_store, config = {}) ⇒ TokenManager
Initialize a new TokenManager
27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/legate/auth/token_manager.rb', line 27 def initialize(token_store, config = {}) @token_store = token_store @config = DEFAULT_CONFIG.merge(config) @callbacks = { before_expiry: [], refresh_success: [], refresh_failure: [], invalidated: [] } @lock = Mutex.new end |
Instance Method Details
#get_token(scheme, credential, force_refresh: false) ⇒ Legate::Auth::ExchangedCredential?
Get a token for the given scheme and credential
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/legate/auth/token_manager.rb', line 44 def get_token(scheme, credential, force_refresh: false) raise ArgumentError, 'Scheme must be an Legate::Auth::Scheme' unless scheme.is_a?(Legate::Auth::Scheme) cache_key = generate_cache_key(scheme, credential) # Use a mutex to prevent race conditions during token retrieval/refresh @lock.synchronize do # Try to get the token from the store token = @token_store.get(cache_key) # If no token, refresh it return refresh_token(scheme, credential, nil, cache_key) if token.nil? # Check if a scheme supports refresh supports_refresh = scheme.respond_to?(:supports_refresh?) && scheme.supports_refresh? is_refreshable = token.respond_to?(:refreshable?) && token.refreshable? # If force refresh is requested, refresh the token regardless of expiration if force_refresh # Check if the token is refreshable before attempting to refresh return refresh_token(scheme, credential, token, cache_key) if supports_refresh && is_refreshable # For tokens that aren't refreshable, create a new one invalidate_token(cache_key) new_token = exchange_token(scheme, credential) if new_token @token_store.store(cache_key, new_token) trigger_callback(:refresh_success, new_token, scheme, credential) return new_token end end # Check if token needs refresh based on expiration if needs_refresh?(token) # Only try to refresh if the scheme supports it and the token is refreshable return refresh_token(scheme, credential, token, cache_key) if supports_refresh && is_refreshable # If not refreshable, invalidate and return nil invalidate_token(cache_key) return nil end # Check if token is approaching expiration and trigger callback if approaching_expiration?(token) trigger_callback(:before_expiry, token, scheme, credential) # If auto_refresh is enabled and we can refresh, do it return refresh_token(scheme, credential, token, cache_key) if @config[:auto_refresh] && supports_refresh && is_refreshable end token end end |
#invalidate_token(cache_key) ⇒ Boolean
Invalidate a token, removing it from the store
171 172 173 174 175 |
# File 'lib/legate/auth/token_manager.rb', line 171 def invalidate_token(cache_key) result = @token_store.clear(cache_key) trigger_callback(:invalidated, nil, nil, nil, cache_key: cache_key) if result result end |
#on(event, &callback) ⇒ self
Register a callback for token lifecycle events
216 217 218 219 220 221 |
# File 'lib/legate/auth/token_manager.rb', line 216 def on(event, &callback) raise ArgumentError, "Unknown event: #{event}. Valid events: #{@callbacks.keys.join(', ')}" unless @callbacks.key?(event) @callbacks[event] << callback self end |
#refresh_token(scheme, credential, token = nil, cache_key = nil) ⇒ Legate::Auth::ExchangedCredential?
Explicitly refresh a token
105 106 107 108 109 110 111 112 113 114 115 116 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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/legate/auth/token_manager.rb', line 105 def refresh_token(scheme, credential, token = nil, cache_key = nil) raise ArgumentError, 'Scheme must be an Legate::Auth::Scheme' unless scheme.is_a?(Legate::Auth::Scheme) cache_key ||= generate_cache_key(scheme, credential) # If we don't have a token and it's an oauth or service account scheme, # we need to authenticate from scratch if token.nil? if %i[oauth2 oidc service_account].include?(scheme.scheme_type) # For these schemes, we need a complete authentication flow # which can't be handled here - return nil to indicate need for full auth return nil end # For other schemes, we can simply apply the credential begin # Basic auth, API key, etc. - create a new token directly token = exchange_token(scheme, credential) if token @token_store.store(cache_key, token) trigger_callback(:refresh_success, token, scheme, credential) end return token rescue Legate::Auth::Error => e Legate.logger.error("Failed to create token: #{e.}") trigger_callback(:refresh_failure, nil, scheme, credential, error: e) return nil end end # Token exists - attempt to refresh it if scheme supports refresh supports_refresh = scheme.respond_to?(:supports_refresh?) && scheme.supports_refresh? is_refreshable = token.respond_to?(:refreshable?) && token.refreshable? if supports_refresh && is_refreshable begin refreshed = scheme.refresh_token(token, credential) if refreshed @token_store.store(cache_key, refreshed) trigger_callback(:refresh_success, refreshed, scheme, credential) return refreshed else # Handle the case where refresh_token returns nil but doesn't raise an error Legate.logger.error('Failed to refresh token: refresh_token returned nil') trigger_callback(:refresh_failure, token, scheme, credential, error: nil) return nil end rescue Legate::Auth::TokenRefreshError => e Legate.logger.error("Failed to refresh token: #{e.}") trigger_callback(:refresh_failure, token, scheme, credential, error: e) return nil end end # Scheme doesn't support refresh or token isn't refreshable # Return existing token if it's not expired return token unless token.expired? # Otherwise, invalidate it invalidate_token(cache_key) nil end |
#revoke_token(scheme, credential, token) ⇒ Boolean
Revoke a token with the authentication provider
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/legate/auth/token_manager.rb', line 182 def revoke_token(scheme, credential, token) raise ArgumentError, 'Scheme must be an Legate::Auth::Scheme' unless scheme.is_a?(Legate::Auth::Scheme) raise ArgumentError, 'Token must be an ExchangedCredential' unless token.is_a?(Legate::Auth::ExchangedCredential) # Check if scheme supports revocation unless scheme.respond_to?(:revoke_token) Legate.logger.warn("Scheme #{scheme.scheme_type} does not support token revocation") return false end begin # Attempt to revoke the token result = scheme.revoke_token(token, credential) # Invalidate the token in our store if revocation succeeded if result cache_key = generate_cache_key(scheme, credential) invalidate_token(cache_key) end result rescue Legate::Auth::Error => e Legate.logger.error("Failed to revoke token: #{e.}") false rescue NotImplementedError => e Legate.logger.error("#{e.}") false end end |