95 lines
2.7 KiB
C#
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; }
|
|
}
|