Files
git.stella-ops.org/src/Registry/StellaOps.Registry.TokenService/PlanRegistry.cs
StellaOps Bot 564df71bfb
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
up
2025-12-13 00:20:26 +02:00

151 lines
4.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.RegularExpressions;
namespace StellaOps.Registry.TokenService;
/// <summary>
/// Evaluates repository access against configured plan rules.
/// </summary>
public sealed class PlanRegistry
{
private readonly IReadOnlyDictionary<string, PlanDescriptor> _plans;
private readonly IReadOnlySet<string> _revokedLicenses;
private readonly string? _defaultPlan;
public PlanRegistry(RegistryTokenServiceOptions options)
{
ArgumentNullException.ThrowIfNull(options);
_plans = options.Plans
.Select(plan => new PlanDescriptor(plan))
.ToDictionary(static plan => plan.Name, StringComparer.OrdinalIgnoreCase);
_revokedLicenses = options.RevokedLicenses.Count == 0
? new HashSet<string>(StringComparer.OrdinalIgnoreCase)
: new HashSet<string>(options.RevokedLicenses, StringComparer.OrdinalIgnoreCase);
_defaultPlan = options.DefaultPlan;
}
public RegistryAccessDecision Authorize(
ClaimsPrincipal principal,
IReadOnlyList<RegistryAccessRequest> requests)
{
ArgumentNullException.ThrowIfNull(principal);
ArgumentNullException.ThrowIfNull(requests);
if (requests.Count == 0)
{
return new RegistryAccessDecision(false, "no_scopes_requested");
}
var licenseId = principal.FindFirstValue("stellaops:license")?.Trim();
if (!string.IsNullOrEmpty(licenseId) && _revokedLicenses.Contains(licenseId))
{
return new RegistryAccessDecision(false, "license_revoked");
}
var planName = principal.FindFirstValue("stellaops:plan")?.Trim();
if (string.IsNullOrEmpty(planName))
{
planName = _defaultPlan;
}
if (string.IsNullOrEmpty(planName) || !_plans.TryGetValue(planName, out var descriptor))
{
return new RegistryAccessDecision(false, "plan_unknown");
}
foreach (var request in requests)
{
if (!descriptor.IsRepositoryAllowed(request))
{
return new RegistryAccessDecision(false, "scope_not_permitted");
}
}
return new RegistryAccessDecision(true);
}
private sealed class PlanDescriptor
{
private readonly IReadOnlyList<RepositoryDescriptor> _repositories;
public PlanDescriptor(RegistryTokenServiceOptions.PlanRule source)
{
Name = source.Name;
_repositories = source.Repositories
.Select(rule => new RepositoryDescriptor(rule))
.ToArray();
}
public string Name { get; }
public bool IsRepositoryAllowed(RegistryAccessRequest request)
{
if (!string.Equals(request.Type, "repository", StringComparison.OrdinalIgnoreCase))
{
return false;
}
foreach (var repo in _repositories)
{
if (!repo.Matches(request.Name))
{
continue;
}
if (repo.AllowsActions(request.Actions))
{
return true;
}
}
return false;
}
}
private sealed class RepositoryDescriptor
{
private readonly Regex _pattern;
private readonly IReadOnlySet<string> _allowedActions;
public RepositoryDescriptor(RegistryTokenServiceOptions.RepositoryRule rule)
{
Pattern = rule.Pattern;
_pattern = Compile(rule.Pattern);
_allowedActions = new HashSet<string>(rule.Actions, StringComparer.OrdinalIgnoreCase);
}
public string Pattern { get; }
public bool Matches(string repository)
{
return _pattern.IsMatch(repository);
}
public bool AllowsActions(IReadOnlyList<string> actions)
{
foreach (var action in actions)
{
if (!_allowedActions.Contains(action))
{
return false;
}
}
return true;
}
private static Regex Compile(string pattern)
{
var escaped = Regex.Escape(pattern);
escaped = escaped.Replace(@"\*", ".*", StringComparison.Ordinal);
return new Regex($"^{escaped}$", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
}
}
}