fca978de07
Sweep of 203 source files resolving CommentChecker findings: add <summary>/<param>/<returns>/<inheritdoc> where missing, and remove resolved task/issue tracking markers (Tests-NNN, Worker-NNN, Server-NNN, Task N) from code comments. Comment/doc-only — no logic changes. Server+Tests build clean under TreatWarningsAsErrors.
145 lines
5.8 KiB
C#
145 lines
5.8 KiB
C#
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.Auth.Abstractions.Roles;
|
|
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
|
using ZB.MOM.WW.MxGateway.Server.Dashboard;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
|
|
|
/// <summary>
|
|
/// Tests for <see cref="DashboardGroupRoleMapper"/>, the shared-Auth
|
|
/// <see cref="IGroupRoleMapper{TRole}"/> seam over the dashboard's
|
|
/// LDAP-group → role mapping. Behaviour must match the existing
|
|
/// <c>DashboardAuthenticator.MapGroupsToRoles</c> precedence/case rules.
|
|
/// </summary>
|
|
public sealed class DashboardGroupRoleMapperTests
|
|
{
|
|
private static DashboardGroupRoleMapper CreateMapper(Dictionary<string, string> mapping)
|
|
{
|
|
GatewayOptions options = new()
|
|
{
|
|
Dashboard = new DashboardOptions
|
|
{
|
|
GroupToRole = mapping,
|
|
},
|
|
};
|
|
|
|
return new DashboardGroupRoleMapper(Options.Create(options));
|
|
}
|
|
|
|
private static Dictionary<string, string> StandardMapping() => new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["GwAdmin"] = DashboardRoles.Admin,
|
|
["GwReader"] = DashboardRoles.Viewer,
|
|
};
|
|
|
|
/// <summary>Verifies full-DN match, leading-RDN fallback, case-insensitivity, and unmapped → empty.</summary>
|
|
/// <param name="ldapGroup">The LDAP group name or distinguished name.</param>
|
|
/// <param name="expectedRole">The expected role or null if no match.</param>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Theory]
|
|
[InlineData("GwAdmin", DashboardRoles.Admin)]
|
|
[InlineData("gwadmin", DashboardRoles.Admin)]
|
|
[InlineData("ou=GwAdmin,ou=groups,dc=zb,dc=local", DashboardRoles.Admin)]
|
|
[InlineData("OtherGroup", null)]
|
|
public async Task MapAsync_ResolvesByShortNameAndDistinguishedName(
|
|
string ldapGroup,
|
|
string? expectedRole)
|
|
{
|
|
DashboardGroupRoleMapper mapper = CreateMapper(StandardMapping());
|
|
|
|
GroupRoleMapping<string> result = await mapper.MapAsync([ldapGroup], CancellationToken.None);
|
|
|
|
if (expectedRole is null)
|
|
{
|
|
Assert.Empty(result.Roles);
|
|
}
|
|
else
|
|
{
|
|
Assert.Equal(expectedRole, Assert.Single(result.Roles));
|
|
}
|
|
|
|
Assert.Null(result.Scope);
|
|
}
|
|
|
|
/// <summary>Verifies that admin and viewer roles are both emitted when both groups are present.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task MapAsync_AdminPlusViewer_BothRolesEmitted()
|
|
{
|
|
DashboardGroupRoleMapper mapper = CreateMapper(StandardMapping());
|
|
|
|
GroupRoleMapping<string> result = await mapper.MapAsync(
|
|
["GwAdmin", "GwReader"],
|
|
CancellationToken.None);
|
|
|
|
Assert.Contains(DashboardRoles.Admin, result.Roles);
|
|
Assert.Contains(DashboardRoles.Viewer, result.Roles);
|
|
}
|
|
|
|
/// <summary>Verifies that an empty GroupToRole map yields no roles.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task MapAsync_EmptyMapping_ReturnsNoRoles()
|
|
{
|
|
DashboardGroupRoleMapper mapper = CreateMapper(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase));
|
|
|
|
GroupRoleMapping<string> result = await mapper.MapAsync(["GwAdmin"], CancellationToken.None);
|
|
|
|
Assert.Empty(result.Roles);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Canonical roles: an LDAP user in the admin group must resolve
|
|
/// to the canonical role value <c>"Administrator"</c> (not the legacy
|
|
/// <c>"Admin"</c>), and the reader group to <c>"Viewer"</c>. Asserted with
|
|
/// string LITERALS — independent of <see cref="DashboardRoles"/> — so a
|
|
/// regression on the constant's value is caught here.
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task MapAsync_AdminGroup_ResolvesToCanonicalAdministratorValue()
|
|
{
|
|
DashboardGroupRoleMapper mapper = CreateMapper(StandardMapping());
|
|
|
|
GroupRoleMapping<string> adminResult = await mapper.MapAsync(["GwAdmin"], CancellationToken.None);
|
|
GroupRoleMapping<string> readerResult = await mapper.MapAsync(["GwReader"], CancellationToken.None);
|
|
|
|
Assert.Equal("Administrator", Assert.Single(adminResult.Roles));
|
|
Assert.Equal("Viewer", Assert.Single(readerResult.Roles));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The canonical admin value (<c>"Administrator"</c>) passes the
|
|
/// admin-only gate while a <c>"Viewer"</c> fails it — asserted with literals,
|
|
/// proving enforcement is bound to the new value and the legacy <c>"Admin"</c>
|
|
/// string is no longer what authorizes admin actions.
|
|
/// </summary>
|
|
[Fact]
|
|
public void AdminOnly_AuthorizesCanonicalAdministratorButNotViewer()
|
|
{
|
|
IReadOnlyList<string> adminGate = DashboardAuthorizationRequirement.AdminOnly.RequiredRoles;
|
|
|
|
Assert.Contains("Administrator", adminGate);
|
|
Assert.DoesNotContain("Admin", adminGate);
|
|
Assert.DoesNotContain("Viewer", adminGate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies the extracted shared helper is the single source of truth: it
|
|
/// produces the same roles the mapper does for the same inputs.
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task SharedHelper_MatchesMapperOutput()
|
|
{
|
|
Dictionary<string, string> mapping = StandardMapping();
|
|
DashboardGroupRoleMapper mapper = CreateMapper(mapping);
|
|
string[] groups = ["ou=GwAdmin,ou=groups,dc=zb,dc=local", "gwreader"];
|
|
|
|
IReadOnlyList<string> helperRoles = DashboardGroupRoleMapping.MapGroupsToRoles(groups, mapping);
|
|
GroupRoleMapping<string> mapperResult = await mapper.MapAsync(groups, CancellationToken.None);
|
|
|
|
Assert.Equal([.. helperRoles.OrderBy(r => r)], [.. mapperResult.Roles.OrderBy(r => r)]);
|
|
}
|
|
}
|