129 lines
5.0 KiB
C#
129 lines
5.0 KiB
C#
|
|
using StellaOps.Auth.Client;
|
|
using StellaOps.Cli.Services;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace StellaOps.Cli.Extensions;
|
|
|
|
/// <summary>
|
|
/// Extension methods for IStellaOpsTokenClient providing compatibility with older CLI patterns.
|
|
/// These bridge the gap between the old API (GetTokenAsync, GetAccessTokenAsync) and the
|
|
/// new API (RequestClientCredentialsTokenAsync, GetCachedTokenAsync).
|
|
/// </summary>
|
|
public static class StellaOpsTokenClientExtensions
|
|
{
|
|
/// <summary>
|
|
/// Requests an access token using client credentials flow with the specified scopes.
|
|
/// This is a compatibility shim for the old GetAccessTokenAsync pattern.
|
|
/// </summary>
|
|
public static async Task<StellaOpsTokenResult> GetAccessTokenAsync(
|
|
this IStellaOpsTokenClient client,
|
|
IEnumerable<string> scopes,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(client);
|
|
var scope = scopes is not null ? string.Join(" ", scopes.Where(s => !string.IsNullOrWhiteSpace(s))) : null;
|
|
return await client.RequestClientCredentialsTokenAsync(scope, null, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requests an access token using client credentials flow with a single scope.
|
|
/// </summary>
|
|
public static async Task<StellaOpsTokenResult> GetAccessTokenAsync(
|
|
this IStellaOpsTokenClient client,
|
|
string scope,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(client);
|
|
return await client.RequestClientCredentialsTokenAsync(scope, null, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a cached access token or requests a new one if not cached or expired.
|
|
/// This is a compatibility shim for the old GetCachedAccessTokenAsync pattern.
|
|
/// Cache key includes effective tenant to prevent cross-tenant cache collisions.
|
|
/// </summary>
|
|
public static async Task<StellaOpsTokenCacheEntry> GetCachedAccessTokenAsync(
|
|
this IStellaOpsTokenClient client,
|
|
IEnumerable<string> scopes,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(client);
|
|
|
|
var scopeList = scopes?.Where(s => !string.IsNullOrWhiteSpace(s)).OrderBy(s => s).ToArray() ?? [];
|
|
var scope = string.Join(" ", scopeList);
|
|
var cacheKey = BuildCacheKey(scope);
|
|
|
|
// Check cache first
|
|
var cached = await client.GetCachedTokenAsync(cacheKey, cancellationToken).ConfigureAwait(false);
|
|
if (cached is not null && !cached.IsExpired(TimeProvider.System, TimeSpan.FromMinutes(1)))
|
|
{
|
|
return cached;
|
|
}
|
|
|
|
// Request new token
|
|
var result = await client.RequestClientCredentialsTokenAsync(scope, null, cancellationToken).ConfigureAwait(false);
|
|
var entry = result.ToCacheEntry();
|
|
|
|
// Cache the result
|
|
await client.CacheTokenAsync(cacheKey, entry, cancellationToken).ConfigureAwait(false);
|
|
|
|
return entry;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a cached access token or requests a new one if not cached or expired.
|
|
/// Single scope version.
|
|
/// </summary>
|
|
public static async Task<StellaOpsTokenCacheEntry> GetCachedAccessTokenAsync(
|
|
this IStellaOpsTokenClient client,
|
|
string scope,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(client);
|
|
|
|
var cacheKey = BuildCacheKey(scope ?? "default");
|
|
|
|
// Check cache first
|
|
var cached = await client.GetCachedTokenAsync(cacheKey, cancellationToken).ConfigureAwait(false);
|
|
if (cached is not null && !cached.IsExpired(TimeProvider.System, TimeSpan.FromMinutes(1)))
|
|
{
|
|
return cached;
|
|
}
|
|
|
|
// Request new token
|
|
var result = await client.RequestClientCredentialsTokenAsync(scope, null, cancellationToken).ConfigureAwait(false);
|
|
var entry = result.ToCacheEntry();
|
|
|
|
// Cache the result
|
|
await client.CacheTokenAsync(cacheKey, entry, cancellationToken).ConfigureAwait(false);
|
|
|
|
return entry;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requests a token using client credentials. Parameterless version for simple cases.
|
|
/// </summary>
|
|
public static async Task<StellaOpsTokenResult> GetTokenAsync(
|
|
this IStellaOpsTokenClient client,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(client);
|
|
return await client.RequestClientCredentialsTokenAsync(null, null, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds a cache key that includes the effective tenant to prevent cross-tenant
|
|
/// token cache collisions when users switch tenants between CLI invocations.
|
|
/// </summary>
|
|
private static string BuildCacheKey(string scope)
|
|
{
|
|
var tenant = TenantProfileStore.GetEffectiveTenant(null) ?? "none";
|
|
return $"cc:{tenant}:{scope}";
|
|
}
|
|
}
|