163 lines
6.7 KiB
C#
163 lines
6.7 KiB
C#
using System.Security.Claims;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.Auth.Abstractions.Ldap;
|
|
using ZB.MOM.WW.Auth.Ldap;
|
|
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
|
using ZB.MOM.WW.MxGateway.Server.Dashboard;
|
|
using LibraryLdapOptions = ZB.MOM.WW.Auth.Abstractions.Ldap.LdapOptions;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.IntegrationTests;
|
|
|
|
[Collection(LiveResourcesCollection.Name)]
|
|
[Trait("Category", "LiveLdap")]
|
|
public sealed class DashboardLdapLiveTests
|
|
{
|
|
/// <summary>Verifies that an admin user in the GwAdmin group authenticates successfully.</summary>
|
|
[LiveLdapFact]
|
|
public async Task AuthenticateAsync_AdminInGwAdminGroup_Succeeds()
|
|
{
|
|
DashboardAuthenticator authenticator = CreateAuthenticator();
|
|
|
|
DashboardAuthenticationResult result = await authenticator.AuthenticateAsync(
|
|
"admin",
|
|
"admin123",
|
|
CancellationToken.None);
|
|
|
|
Assert.True(result.Succeeded);
|
|
Assert.NotNull(result.Principal);
|
|
Assert.Equal("admin", result.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value);
|
|
Assert.Contains(result.Principal.Claims, claim =>
|
|
claim.Type == DashboardAuthenticationDefaults.LdapGroupClaimType
|
|
&& claim.Value.Contains("GwAdmin", StringComparison.OrdinalIgnoreCase));
|
|
|
|
// IntegrationTests-023: DashboardAuthenticator builds the principal with a
|
|
// ClaimTypes.Role claim resolved from the LDAP groups via the
|
|
// DashboardGroupRoleMapper. The seeded GroupToRole map (GwAdmin -> Admin)
|
|
// means the admin principal must carry Role=Admin alongside the raw LDAP-group
|
|
// claim. A regression in the group→role mapping would fail this assertion.
|
|
Assert.Contains(result.Principal.Claims, claim =>
|
|
claim.Type == ClaimTypes.Role
|
|
&& claim.Value == DashboardRoles.Admin);
|
|
}
|
|
|
|
/// <summary>Verifies that a readonly user without GwAdmin group fails to authenticate.</summary>
|
|
[LiveLdapFact]
|
|
public async Task AuthenticateAsync_ReadOnlyUserMissingGwAdminGroup_Fails()
|
|
{
|
|
DashboardAuthenticator authenticator = CreateAuthenticator();
|
|
|
|
DashboardAuthenticationResult result = await authenticator.AuthenticateAsync(
|
|
"readonly",
|
|
"readonly123",
|
|
CancellationToken.None);
|
|
|
|
Assert.False(result.Succeeded);
|
|
Assert.Null(result.Principal);
|
|
Assert.DoesNotContain("readonly123", result.FailureMessage, StringComparison.Ordinal);
|
|
}
|
|
|
|
/// <summary>Verifies that authentication with wrong password fails without leaking the password.</summary>
|
|
[LiveLdapFact]
|
|
public async Task AuthenticateAsync_AdminWithWrongPassword_FailsWithoutLeakingPassword()
|
|
{
|
|
// Exercises the user-bind-failure branch: the user exists and the service
|
|
// account search succeeds, but the candidate bind is rejected.
|
|
const string wrongPassword = "definitely-not-the-admin-password";
|
|
DashboardAuthenticator authenticator = CreateAuthenticator();
|
|
|
|
DashboardAuthenticationResult result = await authenticator.AuthenticateAsync(
|
|
"admin",
|
|
wrongPassword,
|
|
CancellationToken.None);
|
|
|
|
Assert.False(result.Succeeded);
|
|
Assert.Null(result.Principal);
|
|
Assert.DoesNotContain(wrongPassword, result.FailureMessage, StringComparison.Ordinal);
|
|
}
|
|
|
|
/// <summary>Verifies that authentication with unknown username fails.</summary>
|
|
[LiveLdapFact]
|
|
public async Task AuthenticateAsync_UnknownUsername_Fails()
|
|
{
|
|
// Exercises the user-not-found branch: the service-account search returns no
|
|
// entry, so no candidate bind is attempted.
|
|
DashboardAuthenticator authenticator = CreateAuthenticator();
|
|
|
|
DashboardAuthenticationResult result = await authenticator.AuthenticateAsync(
|
|
"no-such-user-9f3c1",
|
|
"irrelevant-password",
|
|
CancellationToken.None);
|
|
|
|
Assert.False(result.Succeeded);
|
|
Assert.Null(result.Principal);
|
|
}
|
|
|
|
/// <summary>Verifies that authentication fails gracefully when the server is unreachable.</summary>
|
|
[LiveLdapFact]
|
|
public async Task AuthenticateAsync_ServerUnreachable_FailsWithoutThrowing()
|
|
{
|
|
// Exercises the connect-failure path: a closed loopback port produces a
|
|
// connection error that the shared LdapAuthService must absorb into a Fail
|
|
// result rather than propagating an exception to the dashboard.
|
|
DashboardAuthenticator authenticator = CreateAuthenticator(LibraryOptions() with
|
|
{
|
|
// 1 is a reserved port number that no LDAP server listens on.
|
|
Port = 1,
|
|
});
|
|
|
|
DashboardAuthenticationResult result = await authenticator.AuthenticateAsync(
|
|
"admin",
|
|
"admin123",
|
|
CancellationToken.None);
|
|
|
|
Assert.False(result.Succeeded);
|
|
Assert.Null(result.Principal);
|
|
}
|
|
|
|
private static DashboardAuthenticator CreateAuthenticator() => CreateAuthenticator(LibraryOptions());
|
|
|
|
private static DashboardAuthenticator CreateAuthenticator(LibraryLdapOptions ldapOptions)
|
|
{
|
|
GatewayOptions gatewayOptions = new()
|
|
{
|
|
Dashboard = new DashboardOptions
|
|
{
|
|
GroupToRole = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["GwAdmin"] = DashboardRoles.Admin,
|
|
},
|
|
},
|
|
};
|
|
|
|
return new DashboardAuthenticator(
|
|
new LdapAuthService(ldapOptions),
|
|
new DashboardGroupRoleMapper(Options.Create(gatewayOptions)),
|
|
NullLogger<DashboardAuthenticator>.Instance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds the shared library <see cref="LibraryLdapOptions"/> from the gateway's
|
|
/// default LDAP settings so the live tests exercise the same seeded directory the
|
|
/// gateway connects to (localhost:3893, plaintext, with AllowInsecure for dev).
|
|
/// </summary>
|
|
private static LibraryLdapOptions LibraryOptions()
|
|
{
|
|
ZB.MOM.WW.MxGateway.Server.Configuration.LdapOptions gateway = new();
|
|
return new LibraryLdapOptions
|
|
{
|
|
Enabled = gateway.Enabled,
|
|
Server = gateway.Server,
|
|
Port = gateway.Port,
|
|
Transport = gateway.Transport,
|
|
AllowInsecure = gateway.AllowInsecure,
|
|
SearchBase = gateway.SearchBase,
|
|
ServiceAccountDn = gateway.ServiceAccountDn,
|
|
ServiceAccountPassword = gateway.ServiceAccountPassword,
|
|
UserNameAttribute = gateway.UserNameAttribute,
|
|
DisplayNameAttribute = gateway.DisplayNameAttribute,
|
|
GroupAttribute = gateway.GroupAttribute,
|
|
};
|
|
}
|
|
}
|