REPOSITORY_CONNECTION OR OLDVERSIONFOUND
@@ -213,6 +224,7 @@
+
diff --git a/installer/test-msi-wine.sh b/installer/test-msi-wine.sh
index 1371d88..7d3835e 100755
--- a/installer/test-msi-wine.sh
+++ b/installer/test-msi-wine.sh
@@ -28,10 +28,11 @@ sed -i 's/"MaxParallelInstances": 4/"MaxParallelInstances": 7/' "$P/Agent/appset
printf '[SQLInsight:Instances:1]\nName=manuel\nServer=sql02\n' >> "$P/Agent/sqlinsight.ini"
files_before=$(find "$P" -type f | wc -l)
-msi /i "$NEW" /qn
+msi /i "$NEW" /qn ADMIN_GROUPS="FIRMA\SQL-Admins;FIRMA\DBA"
check "opgradering" "[ $? = 0 ]"
check "ændret appsettings.json er bevaret" "grep -q '\"MaxParallelInstances\": 7' '$P/Agent/appsettings.json'"
check "ændret sqlinsight.ini er bevaret" "grep -q 'Server=sql02' '$P/Agent/sqlinsight.ini'"
+check "ADMIN_GROUPS i webbens sqlinsight.ini" "grep -q 'SQL-Admins;FIRMA' '$P/Web/sqlinsight.ini'"
entries=$(reg 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' /s | grep -c 'REG_SZ SQLInsight Pro')
check "én post under Programmer ($entries)" "[ $entries = 1 ]"
check "alle filer er der stadig" "[ \$(find '$P' -type f | wc -l) -ge $files_before ]"
diff --git a/src/SQLInsight.Web/Api/ConfigurationApiEndpoints.cs b/src/SQLInsight.Web/Api/ConfigurationApiEndpoints.cs
index fd7b9e4..159e7c0 100644
--- a/src/SQLInsight.Web/Api/ConfigurationApiEndpoints.cs
+++ b/src/SQLInsight.Web/Api/ConfigurationApiEndpoints.cs
@@ -31,8 +31,8 @@ public sealed record InstanceDiscoveryBody(string? Host);
///
/// Which instances and databases are monitored, through the API: everything the page Servere og databaser does.
-/// Reading needs the scope read; changing needs config:write for tokens, or membership of SQLInsight:Web:AdminGroups
-/// for Windows users. Connection tests and discovery are queued for the agent (202 Accepted with a request id to
+/// Reading needs the scope read; changing needs config:write for tokens, or membership of SQLInsight:Web:AdminGroups (the
+/// web server's local Administrators while that is empty) for Windows users. Connection tests and discovery are queued for the agent (202 Accepted with a request id to
/// follow at /api/v1/agent-requests/{id}). Passwords are write-only and never returned.
///
public static class ConfigurationApiEndpoints
@@ -41,7 +41,7 @@ public static class ConfigurationApiEndpoints
{
ArgumentNullException.ThrowIfNull(app);
var api = app.MapGroup("/api/v1").DisableAntiforgery().WithTags("SQLInsight Pro API v1").RequireRead();
- const string write = " Needs scope config:write for tokens, or SQLInsight:Web:AdminGroups for Windows users.";
+ const string write = " Needs scope config:write for tokens, or for Windows users SQLInsight:Web:AdminGroups (empty: the web server's local Administrators).";
api.MapGet("/monitored-instances", ListAsync).WithSummary("Overvågede instanser / Monitored instances (configuration)");
api.MapPost("/monitored-instances", AddAsync)
@@ -77,7 +77,7 @@ public static class ConfigurationApiEndpoints
}
private static IResult Forbidden() => Results.Problem(statusCode: StatusCodes.Status403Forbidden, title: "Ikke tilladt",
- detail: "Ændringer kræver scope \"config:write\" (API-token) eller medlemskab af en gruppe i SQLInsight:Web:AdminGroups.");
+ detail: "Ændringer kræver scope \"config:write\" (API-token) eller medlemskab af en gruppe i SQLInsight:Web:AdminGroups (er den tom: den lokale gruppe Administratorer på webserveren).");
private static IResult Invalid(string detail) => Results.Problem(statusCode: StatusCodes.Status422UnprocessableEntity, title: "Ugyldig", detail: detail);
diff --git a/src/SQLInsight.Web/Components/Pages/InstanceSetupPage.razor b/src/SQLInsight.Web/Components/Pages/InstanceSetupPage.razor
index f9c8fc9..0d42629 100644
--- a/src/SQLInsight.Web/Components/Pages/InstanceSetupPage.razor
+++ b/src/SQLInsight.Web/Components/Pages/InstanceSetupPage.razor
@@ -24,13 +24,11 @@
}
@if (!_mayChange)
{
-
- Du kan se opsætningen, men kun administratorer kan ændre den.
- @if (Options.Value.AdminGroups.Count == 0)
- {
- Der er ikke sat nogen administratorgruppe op endnu: tilføj en Windows-gruppe under SQLInsight:Web:AdminGroups i SQLInsight Webs appsettings.json.
- }
-
+ Du kan se opsætningen, men kun administratorer kan ændre den. @InstanceSetup.WhoMayChangeText(Options.Value)
+}
+else if (Options.Value.AdminGroupNames.Count == 0 && !string.Equals(Options.Value.Authentication, "None", StringComparison.OrdinalIgnoreCase))
+{
+ @InstanceSetup.WhoMayChangeText(Options.Value)
}
@if (_agentKey is null)
{
diff --git a/src/SQLInsight.Web/InstanceSetup.cs b/src/SQLInsight.Web/InstanceSetup.cs
index 9e5d143..9dbce72 100644
--- a/src/SQLInsight.Web/InstanceSetup.cs
+++ b/src/SQLInsight.Web/InstanceSetup.cs
@@ -27,7 +27,7 @@ public sealed record InstanceInput(
public static class InstanceSetup
{
public const string NotAdminText =
- "Kun administratorer kan ændre, hvilke servere og databaser der overvåges. Bed en administrator om at tilføje jeres gruppe under SQLInsight:Web:AdminGroups.";
+ "Kun administratorer kan ændre, hvilke servere og databaser der overvåges (SQLInsight:Web:AdminGroups, eller den lokale gruppe Administratorer på webserveren, når den er tom). Bed en administrator om at tilføje jeres gruppe.";
public const string NoAgentKeyText =
"Agenten har ikke meldt sig endnu, så adgangskoden kan ikke krypteres til den. Start tjenesten SQLInsight Agent (version 1.3 eller nyere), og prøv igen om lidt.";
@@ -39,8 +39,14 @@ public static class InstanceSetup
///
/// Whether the user may change the configuration: an API token needs the scope config:write; a Windows user must be
- /// in one of SQLInsight:Web:AdminGroups. With authentication off (development only) everyone may.
+ /// in one of SQLInsight:Web:AdminGroups or, while that is empty, in the web server's local Administrators group.
+ /// With authentication off (development only) everyone may.
///
+ ///
+ /// Administrators is recognised by its well-known SID among the user's group SIDs (ClaimTypes.GroupSid). A remote
+ /// Windows (Negotiate) login carries the full token, so a member has the SID as a group; a UAC-filtered local token
+ /// has it only as deny-only (ClaimTypes.DenyOnlySid), which does not count.
+ ///
public static bool MayChange(ClaimsPrincipal user, WebOptions options)
{
ArgumentNullException.ThrowIfNull(user);
@@ -55,7 +61,21 @@ public static class InstanceSetup
return true;
}
- return options.AdminGroups.Any(user.IsInRole);
+ var groups = options.AdminGroupNames;
+ return groups.Count > 0
+ ? groups.Any(user.IsInRole)
+ : user.Identity?.IsAuthenticated == true && user.HasClaim(ClaimTypes.GroupSid, WebOptions.BuiltinAdministratorsSid);
+ }
+
+ /// Who may change the configuration (Danish), for the page.
+ public static string WhoMayChangeText(WebOptions options)
+ {
+ ArgumentNullException.ThrowIfNull(options);
+ var groups = options.AdminGroupNames;
+ return groups.Count > 0
+ ? "Opsætningen kan ændres af medlemmer af " + string.Join(", ", groups) + " (SQLInsight:Web:AdminGroups)."
+ : "SQLInsight:Web:AdminGroups er tom, så opsætningen kan ændres af den lokale gruppe Administratorer på webserveren (BUILTIN\\Administrators). "
+ + "Sæt en Windows-gruppe i AdminGroups i SQLInsight Webs appsettings.json (eller ADMIN_GROUPS i installeren) for at give andre adgang.";
}
private static string? Validate(InstanceInput input)
diff --git a/src/SQLInsight.Web/WebOptions.cs b/src/SQLInsight.Web/WebOptions.cs
index fb84a20..f782013 100644
--- a/src/SQLInsight.Web/WebOptions.cs
+++ b/src/SQLInsight.Web/WebOptions.cs
@@ -11,11 +11,20 @@ public sealed class WebOptions
public IList AllowedGroups { get; } = [];
///
- /// Windows groups that may change which servers and databases are monitored (Konfiguration og sikkerhed → Servere
- /// og databaser), e.g. DOMAIN\SQL-Admins. Empty: nobody can change it in the UI; everyone with access can see it.
+ /// Windows groups that may change which servers and databases are monitored (Overvågning → Servere og databaser),
+ /// e.g. DOMAIN\SQL-Admins. An entry may hold several groups separated by ';' (the installer's ADMIN_GROUPS writes one
+ /// entry). Empty: the web server's local Administrators group (BUILTIN\Administrators) may change it. Everyone with
+ /// access to the UI can see it.
///
public IList AdminGroups { get; } = [];
+ /// The well-known SID of BUILTIN\Administrators: who may change the configuration while AdminGroups is empty.
+ public const string BuiltinAdministratorsSid = "S-1-5-32-544";
+
+ /// The groups in , with entries like "A;B" split.
+ public IReadOnlyList AdminGroupNames =>
+ AdminGroups.SelectMany(g => (g ?? string.Empty).Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).ToList();
+
/// Time zone for display, e.g. Europe/Copenhagen. Defaults to the server's local time zone.
public string? TimeZone { get; set; }
diff --git a/tests/SQLInsight.IntegrationTests/InstanceSetupWebTests.cs b/tests/SQLInsight.IntegrationTests/InstanceSetupWebTests.cs
index 226f91a..0c3eaf7 100644
--- a/tests/SQLInsight.IntegrationTests/InstanceSetupWebTests.cs
+++ b/tests/SQLInsight.IntegrationTests/InstanceSetupWebTests.cs
@@ -273,5 +273,27 @@ public sealed partial class InstanceSetupWebTests(SqlServerFixture sql) : IAsync
Assert.False(InstanceSetup.MayChange(User(new Claim(ApiTokenHandler.ClaimType, "grafana"), new Claim(ApiTokenHandler.ScopeClaimType, ApiToken.Read),
new Claim(ClaimTypes.Role, @"FIRMA\SQL-Admins")), windows));
}
+
+ [Fact]
+ public void Without_admin_groups_the_local_administrators_may_change_the_configuration()
+ {
+ static ClaimsPrincipal User(params Claim[] claims) => new(new ClaimsIdentity(claims, "Negotiate", ClaimTypes.Name, ClaimTypes.Role));
+ var empty = new WebOptions { Authentication = "Windows" };
+ var administrator = User(new Claim(ClaimTypes.GroupSid, WebOptions.BuiltinAdministratorsSid));
+
+ // A remote Windows login carries the full token: Administrators is a group SID.
+ Assert.True(InstanceSetup.MayChange(administrator, empty));
+ // A UAC-filtered token has it only as deny-only, and that does not count.
+ Assert.False(InstanceSetup.MayChange(User(new Claim(ClaimTypes.DenyOnlySid, WebOptions.BuiltinAdministratorsSid)), empty));
+ Assert.False(InstanceSetup.MayChange(User(new Claim(ClaimTypes.GroupSid, "S-1-5-32-545")), empty));
+ Assert.False(InstanceSetup.MayChange(new ClaimsPrincipal(new ClaimsIdentity([new Claim(ClaimTypes.GroupSid, WebOptions.BuiltinAdministratorsSid)])), empty));
+
+ // Once groups are set they decide alone; ADMIN_GROUPS from the installer may list several separated by ';'.
+ var groups = new WebOptions { Authentication = "Windows" };
+ groups.AdminGroups.Add(@"FIRMA\SQL-Admins; FIRMA\DBA");
+ Assert.False(InstanceSetup.MayChange(administrator, groups));
+ Assert.True(InstanceSetup.MayChange(User(new Claim(ClaimTypes.Role, @"FIRMA\DBA")), groups));
+ Assert.Equal([@"FIRMA\SQL-Admins", @"FIRMA\DBA"], groups.AdminGroupNames);
+ }
}