From 14c0267428c2f84800b4da833d7420235665c671 Mon Sep 17 00:00:00 2001 From: Randomlyclueless <144950366+Randomlyclueless@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:53:28 +0530 Subject: [PATCH 1/2] fix(#390): Add admin elevation check to fail fast with clear error message --- src/Infrastructure/AppRunner.cs | 9 ++++++ src/Infrastructure/Helpers/AdminGuard.cs | 39 ++++++++++++++++++++++++ tests/WinHome.Tests/AdminGuardTests.cs | 32 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 src/Infrastructure/Helpers/AdminGuard.cs create mode 100644 tests/WinHome.Tests/AdminGuardTests.cs diff --git a/src/Infrastructure/AppRunner.cs b/src/Infrastructure/AppRunner.cs index 8cdd2415..bef5cdd2 100644 --- a/src/Infrastructure/AppRunner.cs +++ b/src/Infrastructure/AppRunner.cs @@ -4,6 +4,13 @@ using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; +using WinHome.Infrastructure.Helpers; // <-- add this line +using WinHome.Interfaces; +using WinHome.Models; +using WinHome.Services.Logging; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + namespace WinHome.Infrastructure; /// Orchestrates configuration loading, validation, secret resolution, and engine execution. @@ -36,7 +43,9 @@ public AppRunner(IEngine engine, IConfigValidator validator, ISecretResolver sec public async Task RunAsync(FileInfo configFile, bool dryRun, string? profile, bool debug, bool diff, bool json, bool force = false, bool continueOnError = false) { try + { + AdminGuard.EnsureAdministrator(); if (!configFile.Exists) { _logger.LogError($"[Error] Configuration file not found: {configFile.FullName}"); diff --git a/src/Infrastructure/Helpers/AdminGuard.cs b/src/Infrastructure/Helpers/AdminGuard.cs new file mode 100644 index 00000000..0bb2a062 --- /dev/null +++ b/src/Infrastructure/Helpers/AdminGuard.cs @@ -0,0 +1,39 @@ +using System.Security.Principal; +using System.Runtime.Versioning; + +namespace WinHome.Infrastructure.Helpers; + +/// Validates that the current process has administrative privileges. +[SupportedOSPlatform("windows")] +public static class AdminGuard +{ + internal static Func IsAdministrator = () => + { + using var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + }; + + /// Throws if the current process is not running as Administrator. + /// Thrown when not running with admin privileges. + public static void EnsureAdministrator() + { + if (!IsAdministrator()) + { + throw new UnauthorizedAccessException( + "Error: WinHome requires Administrative Privileges to manage system configurations. " + + "Please re-run this command from an elevated (Administrator) terminal."); + } + } + + /// Resets the administrator check delegate to its default implementation (used for testing). + internal static void ResetAdminCheck() + { + IsAdministrator = () => + { + using var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + }; + } +} \ No newline at end of file diff --git a/tests/WinHome.Tests/AdminGuardTests.cs b/tests/WinHome.Tests/AdminGuardTests.cs new file mode 100644 index 00000000..16793c3f --- /dev/null +++ b/tests/WinHome.Tests/AdminGuardTests.cs @@ -0,0 +1,32 @@ +using WinHome.Infrastructure.Helpers; +using Xunit; + +namespace WinHome.Tests; + +[Collection("SequentialTests")] +public class AdminGuardTests +{ + [Fact] + public void EnsureAdministrator_DoesNotThrow_WhenAdmin() + { + AdminGuard.IsAdministrator = () => true; + var ex = Record.Exception(() => AdminGuard.EnsureAdministrator()); + Assert.Null(ex); + } + + [Fact] + public void EnsureAdministrator_ThrowsUnauthorizedAccessException_WhenNotAdmin() + { + AdminGuard.IsAdministrator = () => false; + var ex = Assert.Throws(() => AdminGuard.EnsureAdministrator()); + Assert.Contains("Administrative Privileges", ex.Message); + } + + [Fact] + public void ResetAdminCheck_RestoresDefaultDelegate() + { + AdminGuard.IsAdministrator = () => false; + AdminGuard.ResetAdminCheck(); + Assert.NotNull(AdminGuard.IsAdministrator); + } +} \ No newline at end of file From 481119cd145a67826ad991a46031cb47419fc32f Mon Sep 17 00:00:00 2001 From: WinHome Test Date: Wed, 24 Jun 2026 00:36:30 +0530 Subject: [PATCH 2/2] fix: remove duplicate using statements in AppRunner.cs --- src/Infrastructure/AppRunner.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Infrastructure/AppRunner.cs b/src/Infrastructure/AppRunner.cs index bef5cdd2..74d5b0ca 100644 --- a/src/Infrastructure/AppRunner.cs +++ b/src/Infrastructure/AppRunner.cs @@ -5,11 +5,7 @@ using YamlDotNet.Serialization.NamingConventions; using WinHome.Infrastructure.Helpers; // <-- add this line -using WinHome.Interfaces; -using WinHome.Models; -using WinHome.Services.Logging; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; + namespace WinHome.Infrastructure;