Files
git.stella-ops.org/src/Registry/StellaOps.Registry.TokenService/RegistryScopeParser.cs
2026-02-01 21:37:40 +02:00

95 lines
2.7 KiB
C#

using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
namespace StellaOps.Registry.TokenService;
public static class RegistryScopeParser
{
public static IReadOnlyList<RegistryAccessRequest> Parse(IQueryCollection query)
{
ArgumentNullException.ThrowIfNull(query);
var scopes = new List<string>();
if (query.TryGetValue("scope", out var scopeValues))
{
foreach (var scope in scopeValues)
{
if (string.IsNullOrWhiteSpace(scope))
{
continue;
}
// Support space-delimited scopes per OAuth2 spec
foreach (var component in scope.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
scopes.Add(component);
}
}
}
var requests = new List<RegistryAccessRequest>(scopes.Count);
foreach (var scope in scopes)
{
var request = ParseScope(scope);
requests.Add(request);
}
return requests;
}
private static RegistryAccessRequest ParseScope(string scope)
{
var segments = scope.Split(':', StringSplitOptions.TrimEntries);
if (segments.Length < 1)
{
throw new InvalidScopeException(scope, "scope missing resource type");
}
var type = segments[0];
if (!string.Equals(type, "repository", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidScopeException(scope, $"unsupported resource type '{type}'");
}
if (segments.Length < 2 || string.IsNullOrWhiteSpace(segments[1]))
{
throw new InvalidScopeException(scope, "repository scope missing name");
}
var name = segments[1];
var actions = segments.Length >= 3 && !string.IsNullOrWhiteSpace(segments[2])
? segments[2].Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
: Array.Empty<string>();
if (actions.Length == 0)
{
actions = new[] { "pull" };
}
var normalized = actions
.Select(action => action.ToLowerInvariant())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
return new RegistryAccessRequest(type.ToLowerInvariant(), name, normalized);
}
}
public sealed class InvalidScopeException : Exception
{
public InvalidScopeException(string scope, string reason)
: base($"Invalid scope '{scope}': {reason}")
{
Scope = scope;
Reason = reason;
}
public string Scope { get; }
public string Reason { get; }
}