106 lines
3.3 KiB
C#
106 lines
3.3 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)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|