using System; using System.Net; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using StellaOps.Auth.Abstractions; using StellaOps.Auth.ServerIntegration; using Xunit; namespace StellaOps.Auth.ServerIntegration.Tests; public class StellaOpsScopeAuthorizationHandlerTests { [Fact] public async Task HandleRequirement_Succeeds_WhenScopePresent() { var optionsMonitor = CreateOptionsMonitor(options => { options.Authority = "https://authority.example"; options.Validate(); }); var (handler, accessor) = CreateHandler(optionsMonitor, remoteAddress: IPAddress.Parse("10.0.0.1")); var requirement = new StellaOpsScopeRequirement(new[] { StellaOpsScopes.ConcelierJobsTrigger }); var principal = new StellaOpsPrincipalBuilder() .WithSubject("user-1") .WithScopes(new[] { StellaOpsScopes.ConcelierJobsTrigger }) .Build(); var context = new AuthorizationHandlerContext(new[] { requirement }, principal, accessor.HttpContext); await handler.HandleAsync(context); Assert.True(context.HasSucceeded); } [Fact] public async Task HandleRequirement_Succeeds_WhenBypassNetworkMatches() { var optionsMonitor = CreateOptionsMonitor(options => { options.Authority = "https://authority.example"; options.BypassNetworks.Add("127.0.0.1/32"); options.Validate(); }); var (handler, accessor) = CreateHandler(optionsMonitor, remoteAddress: IPAddress.Parse("127.0.0.1")); var requirement = new StellaOpsScopeRequirement(new[] { StellaOpsScopes.ConcelierJobsTrigger }); var principal = new ClaimsPrincipal(new ClaimsIdentity()); var context = new AuthorizationHandlerContext(new[] { requirement }, principal, accessor.HttpContext); await handler.HandleAsync(context); Assert.True(context.HasSucceeded); } [Fact] public async Task HandleRequirement_Fails_WhenScopeMissingAndNoBypass() { var optionsMonitor = CreateOptionsMonitor(options => { options.Authority = "https://authority.example"; options.Validate(); }); var (handler, accessor) = CreateHandler(optionsMonitor, remoteAddress: IPAddress.Parse("203.0.113.10")); var requirement = new StellaOpsScopeRequirement(new[] { StellaOpsScopes.ConcelierJobsTrigger }); var principal = new ClaimsPrincipal(new ClaimsIdentity()); var context = new AuthorizationHandlerContext(new[] { requirement }, principal, accessor.HttpContext); await handler.HandleAsync(context); Assert.False(context.HasSucceeded); } private static (StellaOpsScopeAuthorizationHandler Handler, IHttpContextAccessor Accessor) CreateHandler(IOptionsMonitor optionsMonitor, IPAddress remoteAddress) { var accessor = new HttpContextAccessor(); var httpContext = new DefaultHttpContext(); httpContext.Connection.RemoteIpAddress = remoteAddress; accessor.HttpContext = httpContext; var bypassEvaluator = new StellaOpsBypassEvaluator(optionsMonitor, NullLogger.Instance); var handler = new StellaOpsScopeAuthorizationHandler( accessor, bypassEvaluator, NullLogger.Instance); return (handler, accessor); } private static IOptionsMonitor CreateOptionsMonitor(Action configure) => new TestOptionsMonitor(configure); private sealed class TestOptionsMonitor : IOptionsMonitor where TOptions : class, new() { private readonly TOptions value; public TestOptionsMonitor(Action configure) { value = new TOptions(); configure(value); } public TOptions CurrentValue => value; public TOptions Get(string? name) => value; public IDisposable OnChange(Action listener) => NullDisposable.Instance; private sealed class NullDisposable : IDisposable { public static NullDisposable Instance { get; } = new(); public void Dispose() { } } } }