Files
git.stella-ops.org/src/AirGap/StellaOps.AirGap.Controller/Auth/HeaderScopeAuthenticationHandler.cs
StellaOps Bot ca578801fd save progress
2026-01-03 00:49:19 +02:00

101 lines
3.2 KiB
C#

using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using StellaOps.Auth.Abstractions;
namespace StellaOps.AirGap.Controller.Auth;
public sealed class HeaderScopeAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string SchemeName = "HeaderScope";
#pragma warning disable CS0618 // ISystemClock obsolete; base ctor signature still requires it on this TF.
public HeaderScopeAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{
}
#pragma warning restore CS0618
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var scopes = ExtractScopes(Request.Headers);
if (scopes.Count == 0)
{
return Task.FromResult(AuthenticateResult.Fail("scope_header_missing"));
}
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, "header-scope"),
new(StellaOpsClaimTypes.Subject, "header-scope"),
new(StellaOpsClaimTypes.Scope, string.Join(' ', scopes))
};
foreach (var scope in scopes)
{
claims.Add(new Claim(StellaOpsClaimTypes.ScopeItem, scope));
}
if (TryGetTenantHeader(Request.Headers, out var tenantId))
{
claims.Add(new Claim(StellaOpsClaimTypes.Tenant, tenantId));
claims.Add(new Claim("tid", tenantId));
}
var identity = new ClaimsIdentity(claims, SchemeName);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, SchemeName);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
private static HashSet<string> ExtractScopes(IHeaderDictionary headers)
{
var scopes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
AddScopes(headers, "scope", scopes);
AddScopes(headers, "scp", scopes);
return scopes;
}
private static void AddScopes(IHeaderDictionary headers, string headerName, ISet<string> scopes)
{
if (!headers.TryGetValue(headerName, out var values))
{
return;
}
foreach (var value in values)
{
foreach (var scope in value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
scopes.Add(scope);
}
}
}
private static bool TryGetTenantHeader(IHeaderDictionary headers, out string tenantId)
{
tenantId = string.Empty;
if (headers.TryGetValue("x-tenant-id", out var headerValue) && !string.IsNullOrWhiteSpace(headerValue))
{
tenantId = headerValue.ToString().Trim();
return true;
}
if (headers.TryGetValue("tid", out var legacyValue) && !string.IsNullOrWhiteSpace(legacyValue))
{
tenantId = legacyValue.ToString().Trim();
return true;
}
return false;
}
}