- 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.
266 lines
7.7 KiB
C#
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;
|
|
}
|
|
}
|