feat: Implement console session management with tenant and profile handling
- Add ConsoleSessionStore for managing console session state including tenants, profile, and token information. - Create OperatorContextService to manage operator context for orchestrator actions. - Implement OperatorMetadataInterceptor to enrich HTTP requests with operator context metadata. - Develop ConsoleProfileComponent to display user profile and session details, including tenant information and access tokens. - Add corresponding HTML and SCSS for ConsoleProfileComponent to enhance UI presentation. - Write unit tests for ConsoleProfileComponent to ensure correct rendering and functionality.
This commit is contained in:
@@ -1,34 +1,58 @@
|
||||
using System;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.Cli.Configuration;
|
||||
|
||||
internal static class AuthorityTokenUtilities
|
||||
{
|
||||
public static string ResolveScope(StellaOpsCliOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var scope = options.Authority?.Scope;
|
||||
return string.IsNullOrWhiteSpace(scope)
|
||||
? StellaOpsScopes.ConcelierJobsTrigger
|
||||
: scope.Trim();
|
||||
}
|
||||
|
||||
public static string BuildCacheKey(StellaOpsCliOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (options.Authority is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var scope = ResolveScope(options);
|
||||
var credential = !string.IsNullOrWhiteSpace(options.Authority.Username)
|
||||
? $"user:{options.Authority.Username}"
|
||||
: $"client:{options.Authority.ClientId}";
|
||||
|
||||
return $"{options.Authority.Url}|{credential}|{scope}";
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.Cli.Configuration;
|
||||
|
||||
internal static class AuthorityTokenUtilities
|
||||
{
|
||||
public static string ResolveScope(StellaOpsCliOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var scope = options.Authority?.Scope;
|
||||
return string.IsNullOrWhiteSpace(scope)
|
||||
? StellaOpsScopes.ConcelierJobsTrigger
|
||||
: scope.Trim();
|
||||
}
|
||||
|
||||
public static string BuildCacheKey(StellaOpsCliOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (options.Authority is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var scope = ResolveScope(options);
|
||||
var credential = !string.IsNullOrWhiteSpace(options.Authority.Username)
|
||||
? $"user:{options.Authority.Username}"
|
||||
: $"client:{options.Authority.ClientId}";
|
||||
|
||||
var cacheKey = $"{options.Authority.Url}|{credential}|{scope}";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(scope) && scope.Contains("orch:operate", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var reasonHash = HashOperatorMetadata(options.Authority.OperatorReason);
|
||||
var ticketHash = HashOperatorMetadata(options.Authority.OperatorTicket);
|
||||
cacheKey = $"{cacheKey}|op_reason:{reasonHash}|op_ticket:{ticketHash}";
|
||||
}
|
||||
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
private static string HashOperatorMetadata(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return "none";
|
||||
}
|
||||
|
||||
var trimmed = value.Trim();
|
||||
var bytes = Encoding.UTF8.GetBytes(trimmed);
|
||||
var hash = SHA256.HashData(bytes);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,35 +6,35 @@ using System.Linq;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using StellaOps.Configuration;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
|
||||
namespace StellaOps.Cli.Configuration;
|
||||
|
||||
public static class CliBootstrapper
|
||||
{
|
||||
public static (StellaOpsCliOptions Options, IConfigurationRoot Configuration) Build(string[] args)
|
||||
{
|
||||
var bootstrap = StellaOpsConfigurationBootstrapper.Build<StellaOpsCliOptions>(options =>
|
||||
{
|
||||
options.BindingSection = "StellaOps";
|
||||
options.ConfigureBuilder = builder =>
|
||||
{
|
||||
if (args.Length > 0)
|
||||
{
|
||||
builder.AddCommandLine(args);
|
||||
}
|
||||
};
|
||||
options.PostBind = (cliOptions, configuration) =>
|
||||
{
|
||||
|
||||
namespace StellaOps.Cli.Configuration;
|
||||
|
||||
public static class CliBootstrapper
|
||||
{
|
||||
public static (StellaOpsCliOptions Options, IConfigurationRoot Configuration) Build(string[] args)
|
||||
{
|
||||
var bootstrap = StellaOpsConfigurationBootstrapper.Build<StellaOpsCliOptions>(options =>
|
||||
{
|
||||
options.BindingSection = "StellaOps";
|
||||
options.ConfigureBuilder = builder =>
|
||||
{
|
||||
if (args.Length > 0)
|
||||
{
|
||||
builder.AddCommandLine(args);
|
||||
}
|
||||
};
|
||||
options.PostBind = (cliOptions, configuration) =>
|
||||
{
|
||||
cliOptions.ApiKey = ResolveWithFallback(cliOptions.ApiKey, configuration, "API_KEY", "StellaOps:ApiKey", "ApiKey");
|
||||
cliOptions.BackendUrl = ResolveWithFallback(cliOptions.BackendUrl, configuration, "STELLAOPS_BACKEND_URL", "StellaOps:BackendUrl", "BackendUrl");
|
||||
cliOptions.ConcelierUrl = ResolveWithFallback(cliOptions.ConcelierUrl, configuration, "STELLAOPS_CONCELIER_URL", "StellaOps:ConcelierUrl", "ConcelierUrl");
|
||||
cliOptions.ScannerSignaturePublicKeyPath = ResolveWithFallback(cliOptions.ScannerSignaturePublicKeyPath, configuration, "SCANNER_PUBLIC_KEY", "STELLAOPS_SCANNER_PUBLIC_KEY", "StellaOps:ScannerSignaturePublicKeyPath", "ScannerSignaturePublicKeyPath");
|
||||
|
||||
|
||||
cliOptions.ApiKey = cliOptions.ApiKey?.Trim() ?? string.Empty;
|
||||
cliOptions.BackendUrl = cliOptions.BackendUrl?.Trim() ?? string.Empty;
|
||||
cliOptions.ConcelierUrl = cliOptions.ConcelierUrl?.Trim() ?? string.Empty;
|
||||
cliOptions.ScannerSignaturePublicKeyPath = cliOptions.ScannerSignaturePublicKeyPath?.Trim() ?? string.Empty;
|
||||
|
||||
cliOptions.ScannerSignaturePublicKeyPath = cliOptions.ScannerSignaturePublicKeyPath?.Trim() ?? string.Empty;
|
||||
|
||||
var attemptsRaw = ResolveWithFallback(
|
||||
string.Empty,
|
||||
configuration,
|
||||
@@ -42,17 +42,17 @@ public static class CliBootstrapper
|
||||
"STELLAOPS_SCANNER_DOWNLOAD_ATTEMPTS",
|
||||
"StellaOps:ScannerDownloadAttempts",
|
||||
"ScannerDownloadAttempts");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(attemptsRaw))
|
||||
{
|
||||
attemptsRaw = cliOptions.ScannerDownloadAttempts.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (int.TryParse(attemptsRaw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedAttempts) && parsedAttempts > 0)
|
||||
{
|
||||
cliOptions.ScannerDownloadAttempts = parsedAttempts;
|
||||
}
|
||||
|
||||
|
||||
if (string.IsNullOrWhiteSpace(attemptsRaw))
|
||||
{
|
||||
attemptsRaw = cliOptions.ScannerDownloadAttempts.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (int.TryParse(attemptsRaw, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedAttempts) && parsedAttempts > 0)
|
||||
{
|
||||
cliOptions.ScannerDownloadAttempts = parsedAttempts;
|
||||
}
|
||||
|
||||
if (cliOptions.ScannerDownloadAttempts <= 0)
|
||||
{
|
||||
cliOptions.ScannerDownloadAttempts = 3;
|
||||
@@ -104,6 +104,20 @@ public static class CliBootstrapper
|
||||
"StellaOps:Authority:Scope",
|
||||
"Authority:Scope");
|
||||
|
||||
authority.OperatorReason = ResolveWithFallback(
|
||||
authority.OperatorReason,
|
||||
configuration,
|
||||
"STELLAOPS_ORCH_REASON",
|
||||
"StellaOps:Authority:OperatorReason",
|
||||
"Authority:OperatorReason");
|
||||
|
||||
authority.OperatorTicket = ResolveWithFallback(
|
||||
authority.OperatorTicket,
|
||||
configuration,
|
||||
"STELLAOPS_ORCH_TICKET",
|
||||
"StellaOps:Authority:OperatorTicket",
|
||||
"Authority:OperatorTicket");
|
||||
|
||||
authority.TokenCacheDirectory = ResolveWithFallback(
|
||||
authority.TokenCacheDirectory,
|
||||
configuration,
|
||||
@@ -117,6 +131,8 @@ public static class CliBootstrapper
|
||||
authority.Username = authority.Username?.Trim() ?? string.Empty;
|
||||
authority.Password = string.IsNullOrWhiteSpace(authority.Password) ? null : authority.Password.Trim();
|
||||
authority.Scope = string.IsNullOrWhiteSpace(authority.Scope) ? StellaOpsScopes.ConcelierJobsTrigger : authority.Scope.Trim();
|
||||
authority.OperatorReason = authority.OperatorReason?.Trim() ?? string.Empty;
|
||||
authority.OperatorTicket = authority.OperatorTicket?.Trim() ?? string.Empty;
|
||||
|
||||
authority.Resilience ??= new StellaOpsCliAuthorityResilienceOptions();
|
||||
authority.Resilience.RetryDelays ??= new List<TimeSpan>();
|
||||
|
||||
@@ -13,12 +13,12 @@ public sealed class StellaOpsCliOptions
|
||||
|
||||
public string ConcelierUrl { get; set; } = string.Empty;
|
||||
|
||||
public string ScannerCacheDirectory { get; set; } = "scanners";
|
||||
|
||||
public string ResultsDirectory { get; set; } = "results";
|
||||
|
||||
public string DefaultRunner { get; set; } = "docker";
|
||||
|
||||
public string ScannerCacheDirectory { get; set; } = "scanners";
|
||||
|
||||
public string ResultsDirectory { get; set; } = "results";
|
||||
|
||||
public string DefaultRunner { get; set; } = "docker";
|
||||
|
||||
public string ScannerSignaturePublicKeyPath { get; set; } = string.Empty;
|
||||
|
||||
public int ScannerDownloadAttempts { get; set; } = 3;
|
||||
@@ -46,6 +46,10 @@ public sealed class StellaOpsCliAuthorityOptions
|
||||
|
||||
public string Scope { get; set; } = StellaOpsScopes.ConcelierJobsTrigger;
|
||||
|
||||
public string OperatorReason { get; set; } = string.Empty;
|
||||
|
||||
public string OperatorTicket { get; set; } = string.Empty;
|
||||
|
||||
public string TokenCacheDirectory { get; set; } = string.Empty;
|
||||
|
||||
public StellaOpsCliAuthorityResilienceOptions Resilience { get; set; } = new();
|
||||
|
||||
Reference in New Issue
Block a user