Review 1.5: a check sent back from API v1 with [udeladt] login or URL query is refused, not saved

GET returns the SQL login and the URL query redacted; a client that PUTs it back unchanged
would otherwise replace the real values.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Søren Eggert Lundsteen Olsen 2026-10-02 15:54:38 +02:00
parent 18cd879dbb
commit 37d3d4b5ec
2 changed files with 22 additions and 0 deletions

View file

@ -162,6 +162,9 @@ public static class SyntheticCheckLimits
public static class SyntheticCheckValidation public static class SyntheticCheckValidation
{ {
/// <summary>What API v1 shows instead of a login or a URL query (a value sent back unchanged is refused).</summary>
public const string RedactedValue = "[udeladt]";
/// <summary>The longest HTTP address a check may have.</summary> /// <summary>The longest HTTP address a check may have.</summary>
public const int MaxUrlLength = 2000; public const int MaxUrlLength = 2000;
@ -253,6 +256,12 @@ public static class SyntheticCheckValidation
return $"Adressen må højst være {MaxUrlLength} tegn."; return $"Adressen må højst være {MaxUrlLength} tegn.";
} }
// API v1 shows the query as [udeladt]: a GET sent back as it is must not replace the real address.
if (Uri.UnescapeDataString(url.Query).Contains(RedactedValue, StringComparison.Ordinal))
{
return "Adressen indeholder [udeladt] fra API'et. Angiv hele adressen igen.";
}
if (!string.IsNullOrEmpty(url.UserInfo)) if (!string.IsNullOrEmpty(url.UserInfo))
{ {
return "Adressen må ikke indeholde brugernavn eller adgangskode."; return "Adressen må ikke indeholde brugernavn eller adgangskode.";
@ -283,6 +292,11 @@ public static class SyntheticCheckValidation
return "Angiv SQL-login'et (højst 128 tegn)."; return "Angiv SQL-login'et (højst 128 tegn).";
} }
if (settings.UserName == RedactedValue)
{
return "Login'et er [udeladt] fra API'et. Angiv det rigtige login igen.";
}
if (settings.Encryption is not ("Mandatory" or "Optional")) if (settings.Encryption is not ("Mandatory" or "Optional"))
{ {
return "Kryptering skal være Mandatory eller Optional."; return "Kryptering skal være Mandatory eller Optional.";

View file

@ -68,6 +68,14 @@ public class SyntheticCheckTests
Assert.Equal(@"sql01\AX / AXDB", new SyntheticCheckSettings { Server = @"sql01\AX", Database = "AXDB" }.Target(SyntheticCheckKind.Sql)); Assert.Equal(@"sql01\AX / AXDB", new SyntheticCheckSettings { Server = @"sql01\AX", Database = "AXDB" }.Target(SyntheticCheckKind.Sql));
} }
[Fact]
public void Redacted_values_from_the_api_are_not_saved_back()
{
Assert.Contains("[udeladt]", Validate(SyntheticCheckKind.Sql, new() { Server = "sql01", Authentication = "SqlLogin", UserName = "[udeladt]" }), StringComparison.Ordinal);
Assert.Contains("[udeladt]", Validate(SyntheticCheckKind.Http, new() { Url = new Uri("https://ep/side?[udeladt]") }), StringComparison.Ordinal);
Assert.Null(Validate(SyntheticCheckKind.Http, new() { Url = new Uri("https://ep/side?a=1") }));
}
[Fact] [Fact]
public void A_sql_password_is_bound_to_the_server_and_login() public void A_sql_password_is_bound_to_the_server_and_login()
{ {