125 lines
5.6 KiB
C#
125 lines
5.6 KiB
C#
using System.Security.Claims;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Security.Auth;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Security.Tests;
|
|
|
|
/// <summary>
|
|
/// Pins the semantics of the four AdminUI authorization policies registered by
|
|
/// <c>AddOtOpcUaAuth</c>. Mirrors <see cref="AddOtOpcUaAuthWiringTests"/> provider
|
|
/// construction (in-memory config, real registration, resolve <see cref="IAuthorizationService"/>).
|
|
/// These are the CI-side negative-authz proof: the docker-dev rig auto-authenticates with
|
|
/// all roles (DisableLogin=true) so a role-denied path can never be observed live.
|
|
/// </summary>
|
|
public class AdminUiPoliciesTests
|
|
{
|
|
private static IAuthorizationService BuildAuthorizationService()
|
|
{
|
|
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["Security:Auth:DisableLogin"] = "false",
|
|
}).Build();
|
|
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
services.AddOtOpcUaAuth(config);
|
|
|
|
var sp = services.BuildServiceProvider();
|
|
return sp.GetRequiredService<IAuthorizationService>();
|
|
}
|
|
|
|
/// <summary>An authenticated principal carrying the given role claims.</summary>
|
|
private static ClaimsPrincipal AuthenticatedWith(params string[] roles)
|
|
=> new(new ClaimsIdentity(
|
|
roles.Select(r => new Claim(ClaimTypes.Role, r)),
|
|
authenticationType: "Test"));
|
|
|
|
/// <summary>An unauthenticated principal (no authenticationType ⇒ IsAuthenticated=false).</summary>
|
|
private static ClaimsPrincipal Unauthenticated() => new(new ClaimsIdentity());
|
|
|
|
private static async Task<bool> AllowedAsync(ClaimsPrincipal user, string policy)
|
|
=> (await BuildAuthorizationService().AuthorizeAsync(user, resource: null, policy)).Succeeded;
|
|
|
|
// ── ConfigEditor: Administrator or Designer ────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task ConfigEditor_denies_Viewer()
|
|
=> (await AllowedAsync(AuthenticatedWith("Viewer"), AdminUiPolicies.ConfigEditor)).ShouldBeFalse();
|
|
|
|
[Fact]
|
|
public async Task ConfigEditor_denies_Operator()
|
|
=> (await AllowedAsync(AuthenticatedWith(DevAuthRoles.Operator), AdminUiPolicies.ConfigEditor)).ShouldBeFalse();
|
|
|
|
[Fact]
|
|
public async Task ConfigEditor_allows_Designer()
|
|
=> (await AllowedAsync(AuthenticatedWith("Designer"), AdminUiPolicies.ConfigEditor)).ShouldBeTrue();
|
|
|
|
[Fact]
|
|
public async Task ConfigEditor_allows_Administrator()
|
|
=> (await AllowedAsync(AuthenticatedWith("Administrator"), AdminUiPolicies.ConfigEditor)).ShouldBeTrue();
|
|
|
|
[Fact]
|
|
public async Task ConfigEditor_denies_unauthenticated()
|
|
=> (await AllowedAsync(Unauthenticated(), AdminUiPolicies.ConfigEditor)).ShouldBeFalse();
|
|
|
|
// ── AuthenticatedRead: any authenticated user ──────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task AuthenticatedRead_allows_roleless_authenticated()
|
|
=> (await AllowedAsync(AuthenticatedWith(), AdminUiPolicies.AuthenticatedRead)).ShouldBeTrue();
|
|
|
|
[Fact]
|
|
public async Task AuthenticatedRead_allows_Viewer()
|
|
=> (await AllowedAsync(AuthenticatedWith("Viewer"), AdminUiPolicies.AuthenticatedRead)).ShouldBeTrue();
|
|
|
|
[Fact]
|
|
public async Task AuthenticatedRead_denies_unauthenticated()
|
|
=> (await AllowedAsync(Unauthenticated(), AdminUiPolicies.AuthenticatedRead)).ShouldBeFalse();
|
|
|
|
// ── FleetAdmin: Administrator only (pre-existing policy, pinned here) ───────────
|
|
|
|
[Fact]
|
|
public async Task FleetAdmin_allows_Administrator()
|
|
=> (await AllowedAsync(AuthenticatedWith("Administrator"), AdminUiPolicies.FleetAdmin)).ShouldBeTrue();
|
|
|
|
[Fact]
|
|
public async Task FleetAdmin_denies_Designer()
|
|
=> (await AllowedAsync(AuthenticatedWith("Designer"), AdminUiPolicies.FleetAdmin)).ShouldBeFalse();
|
|
|
|
[Fact]
|
|
public async Task FleetAdmin_denies_Operator()
|
|
=> (await AllowedAsync(AuthenticatedWith(DevAuthRoles.Operator), AdminUiPolicies.FleetAdmin)).ShouldBeFalse();
|
|
|
|
// ── DriverOperator: Operator + Administrator only (pre-existing policy, pinned) ─
|
|
|
|
[Fact]
|
|
public async Task DriverOperator_allows_Operator()
|
|
=> (await AllowedAsync(AuthenticatedWith(DevAuthRoles.Operator), AdminUiPolicies.DriverOperator)).ShouldBeTrue();
|
|
|
|
[Fact]
|
|
public async Task DriverOperator_allows_Administrator()
|
|
=> (await AllowedAsync(AuthenticatedWith("Administrator"), AdminUiPolicies.DriverOperator)).ShouldBeTrue();
|
|
|
|
[Fact]
|
|
public async Task DriverOperator_denies_Designer()
|
|
=> (await AllowedAsync(AuthenticatedWith("Designer"), AdminUiPolicies.DriverOperator)).ShouldBeFalse();
|
|
|
|
[Fact]
|
|
public async Task DriverOperator_denies_Viewer()
|
|
=> (await AllowedAsync(AuthenticatedWith("Viewer"), AdminUiPolicies.DriverOperator)).ShouldBeFalse();
|
|
|
|
// ── Dev-rig protection: DevAuthRoles.All satisfies every policy ────────────────
|
|
|
|
[Theory]
|
|
[InlineData(AdminUiPolicies.FleetAdmin)]
|
|
[InlineData(AdminUiPolicies.DriverOperator)]
|
|
[InlineData(AdminUiPolicies.ConfigEditor)]
|
|
[InlineData(AdminUiPolicies.AuthenticatedRead)]
|
|
public async Task DevAuthRoles_All_satisfies_every_policy(string policy)
|
|
=> (await AllowedAsync(AuthenticatedWith(DevAuthRoles.All), policy)).ShouldBeTrue();
|
|
}
|