Files
git.stella-ops.org/tests/StellaOps.Gateway.WebService.Tests/Authorization/AuthorizationMiddlewareTests.cs
StellaOps Bot 6a299d231f
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Add unit tests for Router configuration and transport layers
- Implemented tests for RouterConfig, RoutingOptions, StaticInstanceConfig, and RouterConfigOptions to ensure default values are set correctly.
- Added tests for RouterConfigProvider to validate configurations and ensure defaults are returned when no file is specified.
- Created tests for ConfigValidationResult to check success and error scenarios.
- Developed tests for ServiceCollectionExtensions to verify service registration for RouterConfig.
- Introduced UdpTransportTests to validate serialization, connection, request-response, and error handling in UDP transport.
- Added scripts for signing authority gaps and hashing DevPortal SDK snippets.
2025-12-05 08:01:47 +02:00

266 lines
7.7 KiB
C#

using System.Security.Claims;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using StellaOps.Gateway.WebService.Authorization;
using StellaOps.Router.Common;
using StellaOps.Router.Common.Models;
using Xunit;
namespace StellaOps.Gateway.WebService.Tests.Authorization;
/// <summary>
/// Tests for <see cref="AuthorizationMiddleware"/>.
/// </summary>
public sealed class AuthorizationMiddlewareTests
{
private readonly Mock<IEffectiveClaimsStore> _claimsStore;
private readonly Mock<RequestDelegate> _next;
private readonly AuthorizationMiddleware _middleware;
public AuthorizationMiddlewareTests()
{
_claimsStore = new Mock<IEffectiveClaimsStore>();
_next = new Mock<RequestDelegate>();
_middleware = new AuthorizationMiddleware(
_next.Object,
_claimsStore.Object,
NullLogger<AuthorizationMiddleware>.Instance);
}
[Fact]
public async Task InvokeAsync_NoEndpointResolved_CallsNext()
{
// Arrange
var context = CreateHttpContext();
// Act
await _middleware.InvokeAsync(context);
// Assert
_next.Verify(n => n(context), Times.Once);
}
[Fact]
public async Task InvokeAsync_NoClaims_CallsNext()
{
// Arrange
var context = CreateHttpContextWithEndpoint();
_claimsStore
.Setup(s => s.GetEffectiveClaims("test-service", "GET", "/api/test"))
.Returns(Array.Empty<ClaimRequirement>());
// Act
await _middleware.InvokeAsync(context);
// Assert
_next.Verify(n => n(context), Times.Once);
context.Response.StatusCode.Should().NotBe(403);
}
[Fact]
public async Task InvokeAsync_UserHasRequiredClaims_CallsNext()
{
// Arrange
var context = CreateHttpContextWithEndpoint(new[]
{
new Claim("scope", "read"),
new Claim("role", "user")
});
_claimsStore
.Setup(s => s.GetEffectiveClaims("test-service", "GET", "/api/test"))
.Returns(new List<ClaimRequirement>
{
new() { Type = "scope", Value = "read" },
new() { Type = "role", Value = "user" }
});
// Act
await _middleware.InvokeAsync(context);
// Assert
_next.Verify(n => n(context), Times.Once);
context.Response.StatusCode.Should().NotBe(403);
}
[Fact]
public async Task InvokeAsync_UserMissingRequiredClaim_Returns403()
{
// Arrange
var context = CreateHttpContextWithEndpoint(new[]
{
new Claim("scope", "read")
});
_claimsStore
.Setup(s => s.GetEffectiveClaims("test-service", "GET", "/api/test"))
.Returns(new List<ClaimRequirement>
{
new() { Type = "scope", Value = "read" },
new() { Type = "role", Value = "admin" } // User doesn't have this
});
// Act
await _middleware.InvokeAsync(context);
// Assert
_next.Verify(n => n(It.IsAny<HttpContext>()), Times.Never);
context.Response.StatusCode.Should().Be(403);
}
[Fact]
public async Task InvokeAsync_UserHasClaimTypeButWrongValue_Returns403()
{
// Arrange
var context = CreateHttpContextWithEndpoint(new[]
{
new Claim("role", "user")
});
_claimsStore
.Setup(s => s.GetEffectiveClaims("test-service", "GET", "/api/test"))
.Returns(new List<ClaimRequirement>
{
new() { Type = "role", Value = "admin" }
});
// Act
await _middleware.InvokeAsync(context);
// Assert
_next.Verify(n => n(It.IsAny<HttpContext>()), Times.Never);
context.Response.StatusCode.Should().Be(403);
}
[Fact]
public async Task InvokeAsync_ClaimWithNullValue_MatchesAnyValue()
{
// Arrange - user has claim of type "authenticated" with some value
var context = CreateHttpContextWithEndpoint(new[]
{
new Claim("authenticated", "true")
});
// Requirement only checks that type exists, any value is ok
_claimsStore
.Setup(s => s.GetEffectiveClaims("test-service", "GET", "/api/test"))
.Returns(new List<ClaimRequirement>
{
new() { Type = "authenticated", Value = null }
});
// Act
await _middleware.InvokeAsync(context);
// Assert
_next.Verify(n => n(context), Times.Once);
}
[Fact]
public async Task InvokeAsync_MultipleClaims_AllMustMatch()
{
// Arrange - user has 2 of 3 required claims
var context = CreateHttpContextWithEndpoint(new[]
{
new Claim("scope", "read"),
new Claim("role", "user")
});
_claimsStore
.Setup(s => s.GetEffectiveClaims("test-service", "GET", "/api/test"))
.Returns(new List<ClaimRequirement>
{
new() { Type = "scope", Value = "read" },
new() { Type = "role", Value = "user" },
new() { Type = "department", Value = "IT" } // Missing
});
// Act
await _middleware.InvokeAsync(context);
// Assert
_next.Verify(n => n(It.IsAny<HttpContext>()), Times.Never);
context.Response.StatusCode.Should().Be(403);
}
[Fact]
public async Task InvokeAsync_UserHasExtraClaims_StillAuthorized()
{
// Arrange - user has more claims than required
var context = CreateHttpContextWithEndpoint(new[]
{
new Claim("scope", "read"),
new Claim("scope", "write"),
new Claim("role", "admin"),
new Claim("department", "IT")
});
_claimsStore
.Setup(s => s.GetEffectiveClaims("test-service", "GET", "/api/test"))
.Returns(new List<ClaimRequirement>
{
new() { Type = "scope", Value = "read" }
});
// Act
await _middleware.InvokeAsync(context);
// Assert
_next.Verify(n => n(context), Times.Once);
}
[Fact]
public async Task InvokeAsync_ForbiddenResponse_ContainsErrorDetails()
{
// Arrange
var context = CreateHttpContextWithEndpoint();
context.Response.Body = new MemoryStream();
_claimsStore
.Setup(s => s.GetEffectiveClaims("test-service", "GET", "/api/test"))
.Returns(new List<ClaimRequirement>
{
new() { Type = "admin", Value = "true" }
});
// Act
await _middleware.InvokeAsync(context);
// Assert
context.Response.StatusCode.Should().Be(403);
context.Response.ContentType.Should().Contain("application/json");
}
private static HttpContext CreateHttpContext()
{
var context = new DefaultHttpContext();
return context;
}
private static HttpContext CreateHttpContextWithEndpoint(Claim[]? userClaims = null)
{
var context = new DefaultHttpContext();
// Set resolved endpoint
var endpoint = new EndpointDescriptor
{
ServiceName = "test-service",
Version = "1.0.0",
Method = "GET",
Path = "/api/test"
};
context.Items[RouterHttpContextKeys.EndpointDescriptor] = endpoint;
// Set user with claims
if (userClaims != null)
{
var identity = new ClaimsIdentity(userClaims, "Test");
context.User = new ClaimsPrincipal(identity);
}
return context;
}
}