124 lines
4.5 KiB
C#
124 lines
4.5 KiB
C#
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<StellaOpsResourceServerOptions> optionsMonitor, IPAddress remoteAddress)
|
|
{
|
|
var accessor = new HttpContextAccessor();
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Connection.RemoteIpAddress = remoteAddress;
|
|
accessor.HttpContext = httpContext;
|
|
|
|
var bypassEvaluator = new StellaOpsBypassEvaluator(optionsMonitor, NullLogger<StellaOpsBypassEvaluator>.Instance);
|
|
|
|
var handler = new StellaOpsScopeAuthorizationHandler(
|
|
accessor,
|
|
bypassEvaluator,
|
|
NullLogger<StellaOpsScopeAuthorizationHandler>.Instance);
|
|
return (handler, accessor);
|
|
}
|
|
|
|
private static IOptionsMonitor<StellaOpsResourceServerOptions> CreateOptionsMonitor(Action<StellaOpsResourceServerOptions> configure)
|
|
=> new TestOptionsMonitor<StellaOpsResourceServerOptions>(configure);
|
|
|
|
private sealed class TestOptionsMonitor<TOptions> : IOptionsMonitor<TOptions>
|
|
where TOptions : class, new()
|
|
{
|
|
private readonly TOptions value;
|
|
|
|
public TestOptionsMonitor(Action<TOptions> configure)
|
|
{
|
|
value = new TOptions();
|
|
configure(value);
|
|
}
|
|
|
|
public TOptions CurrentValue => value;
|
|
|
|
public TOptions Get(string? name) => value;
|
|
|
|
public IDisposable OnChange(Action<TOptions, string> listener) => NullDisposable.Instance;
|
|
|
|
private sealed class NullDisposable : IDisposable
|
|
{
|
|
public static NullDisposable Instance { get; } = new();
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|