Skip to content

Commit 02e3b20

Browse files
committed
Add policy command source
1 parent a93bda1 commit 02e3b20

1 file changed

Lines changed: 128 additions & 0 deletions

File tree

BrowserGuard/PolicyCommand.cs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using System;
2+
using System.IO;
3+
using System.Text.Json.Nodes;
4+
using Microsoft.Win32;
5+
6+
namespace BrowserGuard
7+
{
8+
// Maintains this extension's entry in the Edge ExtensionSettings policy,
9+
// invoked by the installer as a subcommand.
10+
//
11+
// ExtensionInstallForcelist would only supply the update URL for the first
12+
// install; afterwards the browser looks at update_url inside the extension's
13+
// own manifest, which a self hosted build does not have. ExtensionSettings
14+
// with override_update_url keeps the URL in use for update checks too, which
15+
// is why it is used on its own.
16+
//
17+
// The policy value is one JSON object covering every extension, so it is
18+
// parsed and only this extension's member is touched; whatever another
19+
// product put there is preserved.
20+
internal static class PolicyCommand
21+
{
22+
internal const string CommandName = "policy";
23+
24+
// SOFTWARE\Policies is shared between the 32 and 64 bit registry views.
25+
private const string PolicyKey = @"SOFTWARE\Policies\Microsoft\Edge";
26+
private const string SettingsValue = "ExtensionSettings";
27+
28+
internal static int Run(string[] args)
29+
{
30+
try
31+
{
32+
if (args.Length < 3)
33+
{
34+
return Usage();
35+
}
36+
37+
var extensionId = args[2];
38+
switch (args[1].ToLowerInvariant())
39+
{
40+
case "register":
41+
if (args.Length < 4)
42+
{
43+
return Usage();
44+
}
45+
Register(extensionId, args[3]);
46+
return 0;
47+
48+
case "unregister":
49+
Unregister(extensionId);
50+
return 0;
51+
52+
default:
53+
return Usage();
54+
}
55+
}
56+
catch (Exception ex)
57+
{
58+
Console.Error.WriteLine(ex.Message);
59+
return 1;
60+
}
61+
}
62+
63+
private static int Usage()
64+
{
65+
Console.Error.WriteLine(
66+
$"usage: BrowserGuard.exe {CommandName} register <extension-id> <update-url>\n" +
67+
$" BrowserGuard.exe {CommandName} unregister <extension-id>");
68+
return 1;
69+
}
70+
71+
// Separated from the registry access so that it can be exercised by tests.
72+
internal static void SetEntry(JsonObject settings, string extensionId, string updateUrl)
73+
{
74+
settings[extensionId] = new JsonObject
75+
{
76+
["installation_mode"] = "force_installed",
77+
["update_url"] = updateUrl,
78+
["override_update_url"] = true,
79+
};
80+
}
81+
82+
private static void Register(string extensionId, string updateUrl)
83+
{
84+
var settings = ReadSettings();
85+
SetEntry(settings, extensionId, updateUrl);
86+
WriteSettings(settings);
87+
}
88+
89+
private static void Unregister(string extensionId)
90+
{
91+
var settings = ReadSettings();
92+
if (settings.Remove(extensionId))
93+
{
94+
WriteSettings(settings);
95+
}
96+
}
97+
98+
private static JsonObject ReadSettings()
99+
{
100+
using var key = Registry.LocalMachine.OpenSubKey(PolicyKey);
101+
if (key?.GetValue(SettingsValue) is not string raw || string.IsNullOrWhiteSpace(raw))
102+
{
103+
return new JsonObject();
104+
}
105+
106+
// A value that is not a JSON object cannot be merged into safely.
107+
return JsonNode.Parse(raw) as JsonObject
108+
?? throw new InvalidDataException($"{SettingsValue} is not a JSON object.");
109+
}
110+
111+
private static void WriteSettings(JsonObject settings)
112+
{
113+
using var key = Registry.LocalMachine.CreateSubKey(PolicyKey, true)
114+
?? throw new InvalidOperationException($"Cannot open HKLM\\{PolicyKey} for writing.");
115+
116+
if (settings.Count == 0)
117+
{
118+
if (key.GetValue(SettingsValue) is not null)
119+
{
120+
key.DeleteValue(SettingsValue);
121+
}
122+
return;
123+
}
124+
125+
key.SetValue(SettingsValue, settings.ToJsonString(), RegistryValueKind.String);
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)