test(ldap): align DashboardLdapLiveTests fixtures with the shared directory (NEXT-06)
The suite's fixtures had drifted from the shared GLAuth config, so a green run proved nothing about the service-account bind: the only success-path test used admin/admin123, but the directory's admin carries the standard dev password, and the "not an admin" test used a readonly user that does not exist there at all -- it passed via the user-not-found branch rather than the group-missing branch it names. Realign to real users from scadaproj/infra/glauth/config.toml: admin/password (othergroups include GwAdmin, gid 5610) for the success path, and gw-viewer/password (GwReader only, gid 5611) for the bind-succeeds-but-no-role path. Both are published dev credentials documented in glauth.md, not secrets. The gw-viewer test drops its old no-leak assertion on the credential literal: the real password is the word "password", which legitimately occurs in the generic denial text, so the check would fail for the wrong reason. The no-leak property is still covered with a distinctive literal by the wrong-password test. In its place the test now asserts the property this fixture is uniquely able to prove -- an authorization failure must be reported with the same message as an authentication failure, so it cannot be used to enumerate valid accounts. appsettings ships Server=localhost, so document the MxGateway__Ldap__Server override the suite needs to reach the shared GLAuth alongside the existing MXGATEWAY_RUN_LIVE_LDAP_TESTS and ServiceAccountPassword variables. Verified live: Failed: 0, Passed: 5 against 10.100.0.35:3893.
This commit is contained in:
@@ -14,7 +14,19 @@ namespace ZB.MOM.WW.MxGateway.IntegrationTests;
|
||||
[Trait("Category", "LiveLdap")]
|
||||
public sealed class DashboardLdapLiveTests
|
||||
{
|
||||
/// <summary>Verifies that an admin user in the GwAdmin group authenticates successfully.</summary>
|
||||
/// <summary>
|
||||
/// The shared dev/test directory issues every human tester the same well-known password, so
|
||||
/// the fixtures name it once rather than repeating a literal that drifts per test. This is a
|
||||
/// published dev credential (see <c>glauth.md</c> and <c>scadaproj/infra/glauth/config.toml</c>),
|
||||
/// not a secret — unlike the service-account bind password, which is never in source and must
|
||||
/// arrive via <c>MxGateway__Ldap__ServiceAccountPassword</c>.
|
||||
/// </summary>
|
||||
private const string SharedDirectoryPassword = "password";
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that <c>admin</c> — a shared-directory user whose <c>othergroups</c> include
|
||||
/// GwAdmin (gid 5610) — authenticates successfully and is granted the Admin dashboard role.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[LiveLdapFact]
|
||||
public async Task AuthenticateAsync_AdminInGwAdminGroup_Succeeds()
|
||||
@@ -23,7 +35,7 @@ public sealed class DashboardLdapLiveTests
|
||||
|
||||
DashboardAuthenticationResult result = await authenticator.AuthenticateAsync(
|
||||
"admin",
|
||||
"admin123",
|
||||
SharedDirectoryPassword,
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.True(result.Succeeded);
|
||||
@@ -38,21 +50,43 @@ public sealed class DashboardLdapLiveTests
|
||||
&& claim.Value == DashboardRoles.Admin);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a readonly user without GwAdmin group fails to authenticate.</summary>
|
||||
/// <summary>
|
||||
/// Verifies that <c>gw-viewer</c> — a shared-directory user whose only group is GwReader
|
||||
/// (gid 5611), which this suite's GroupToRole map deliberately leaves unmapped — is denied
|
||||
/// even though its bind succeeds, and that the denial is indistinguishable from the
|
||||
/// unknown-user denial.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[LiveLdapFact]
|
||||
public async Task AuthenticateAsync_ReadOnlyUserMissingGwAdminGroup_Fails()
|
||||
public async Task AuthenticateAsync_ViewerMissingGwAdminGroup_FailsIndistinguishably()
|
||||
{
|
||||
DashboardAuthenticator authenticator = CreateAuthenticator();
|
||||
|
||||
DashboardAuthenticationResult result = await authenticator.AuthenticateAsync(
|
||||
"readonly",
|
||||
"readonly123",
|
||||
"gw-viewer",
|
||||
SharedDirectoryPassword,
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.False(result.Succeeded);
|
||||
Assert.Null(result.Principal);
|
||||
Assert.DoesNotContain("readonly123", result.FailureMessage, StringComparison.Ordinal);
|
||||
|
||||
// This test used to assert the failure message did not echo the credential literal.
|
||||
// That check cannot survive the move to the shared directory: the real password is the
|
||||
// word "password", which legitimately occurs in the generic denial text ("The username
|
||||
// or password is invalid, ..."), so the assertion would fail for the wrong reason. The
|
||||
// no-leak property is still covered — with a distinctive literal — by
|
||||
// AuthenticateAsync_AdminWithWrongPassword_FailsWithoutLeakingPassword below. What is
|
||||
// asserted here instead is the property this fixture is actually uniquely able to prove:
|
||||
// an authorization failure (valid credentials, no mapped role) must be reported with the
|
||||
// same message as an authentication failure, so the response cannot be used to enumerate
|
||||
// valid accounts.
|
||||
DashboardAuthenticationResult unknownUserResult = await authenticator.AuthenticateAsync(
|
||||
"no-such-user-9f3c1",
|
||||
"irrelevant-password",
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.False(string.IsNullOrWhiteSpace(result.FailureMessage));
|
||||
Assert.Equal(unknownUserResult.FailureMessage, result.FailureMessage);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that authentication with wrong password fails without leaking the password.</summary>
|
||||
@@ -98,9 +132,11 @@ public sealed class DashboardLdapLiveTests
|
||||
[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.
|
||||
// Exercises the connect-failure path: overriding only the port keeps whatever host
|
||||
// the run targets (localhost by default, the shared GLAuth under the
|
||||
// MxGateway__Ldap__Server override) while pointing at a port nothing listens on, so
|
||||
// the connection error the shared LdapAuthService must absorb into a Fail result —
|
||||
// rather than propagate as an exception to the dashboard — is reproduced either way.
|
||||
DashboardAuthenticator authenticator = CreateAuthenticator(LibraryOptions() with
|
||||
{
|
||||
// 1 is a reserved port number that no LDAP server listens on.
|
||||
@@ -109,7 +145,7 @@ public sealed class DashboardLdapLiveTests
|
||||
|
||||
DashboardAuthenticationResult result = await authenticator.AuthenticateAsync(
|
||||
"admin",
|
||||
"admin123",
|
||||
SharedDirectoryPassword,
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.False(result.Succeeded);
|
||||
@@ -147,8 +183,10 @@ public sealed class DashboardLdapLiveTests
|
||||
/// <see cref="LibraryLdapOptions.ConnectionTimeoutMs"/>, which governs the
|
||||
/// unreachable-server test's timing) at whatever value the operator configured, and
|
||||
/// cannot silently drop a field added to the shared type. The gateway's
|
||||
/// <c>appsettings.json</c> seeds the dev directory connection (localhost:3893,
|
||||
/// plaintext, AllowInsecure).
|
||||
/// <c>appsettings.json</c> seeds the dev directory connection (port 3893, plaintext,
|
||||
/// AllowInsecure) but ships <c>Server=localhost</c>, so a run against the shared GLAuth
|
||||
/// needs the <c>MxGateway__Ldap__Server=10.100.0.35</c> environment override that the
|
||||
/// <c>AddEnvironmentVariables()</c> layer below applies.
|
||||
/// </summary>
|
||||
private static LibraryLdapOptions LibraryOptions()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user