mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 09:14:25 +08:00
Harden OOBE, launch-source and elevation flow
Introduce a per-user OOBE state model and hardened launch/elevation handling. Adds OobeStateFile/OobeLaunchDecision models, OobeStateService (persisting %LOCALAPPDATA%/.launcher/state/oobe-state.json), and LauncherExecutionContext to capture elevation and user SID. CommandContext now normalizes/infers launch-source values (normal, postinstall, apply-update, plugin-install, debug-preview) and exposes maintenance checks. LauncherFlowCoordinator propagates richer launcher context details for diagnostics and suppresses OOBE for elevated/maintenance contexts. PluginInstallerService avoids requesting elevation for user-scoped installs and returns a clear error when installation target is outside the current user's LocalAppData. LauncherClient maps and surfaces result codes, UpdateWorkflow and installer invocation now pass explicit --launch-source values, and WelcomeOobeStep persists OOBE completion via the new service. Adds unit tests (CommandContext, OobeStateService, PluginInstallerService), docs/specs/checklists for the contract, and makes internals visible to tests.
This commit is contained in:
@@ -30,6 +30,11 @@ internal sealed class PluginInstallerService
|
||||
throw new FileNotFoundException($"Plugin package '{fullSourcePath}' was not found.", fullSourcePath);
|
||||
}
|
||||
|
||||
if (TryBuildElevationRequiredResult(fullPluginsDirectory) is { } elevationRequiredResult)
|
||||
{
|
||||
return elevationRequiredResult;
|
||||
}
|
||||
|
||||
var manifest = ReadManifestFromPackage(fullSourcePath);
|
||||
Directory.CreateDirectory(fullPluginsDirectory);
|
||||
var destinationPath = Path.Combine(fullPluginsDirectory, BuildInstalledPackageFileName(manifest.Id));
|
||||
@@ -51,6 +56,46 @@ internal sealed class PluginInstallerService
|
||||
};
|
||||
}
|
||||
|
||||
private static LauncherResult? TryBuildElevationRequiredResult(string pluginsDirectory)
|
||||
{
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
if (string.IsNullOrWhiteSpace(localAppData))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var allowedRoot = EnsureTrailingSeparator(Path.Combine(Path.GetFullPath(localAppData), "LanMountainDesktop"));
|
||||
var normalizedPluginsDirectory = EnsureTrailingSeparator(Path.GetFullPath(pluginsDirectory));
|
||||
if (normalizedPluginsDirectory.StartsWith(allowedRoot, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Logger.Warn(
|
||||
$"Plugin installation requires explicit elevation. Reason='plugin_requires_elevation'; " +
|
||||
$"PluginsDirectory='{pluginsDirectory}'; AllowedRoot='{allowedRoot}'.");
|
||||
|
||||
return new LauncherResult
|
||||
{
|
||||
Success = false,
|
||||
Stage = "plugin.install",
|
||||
Code = "plugin_elevation_required",
|
||||
Message = "Plugin installation outside the current user's LanMountainDesktop data directory requires explicit elevation.",
|
||||
ErrorMessage = "Plugin installation target is outside the current user's LanMountainDesktop data directory.",
|
||||
Details = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["pluginsDirectory"] = pluginsDirectory,
|
||||
["allowedRoot"] = allowedRoot,
|
||||
["elevationReason"] = "outside_user_scope"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PluginManifest ReadManifestFromPackage(string packagePath)
|
||||
{
|
||||
using var archive = ZipFile.OpenRead(packagePath);
|
||||
|
||||
Reference in New Issue
Block a user