SEBWIN-319: Implemented infrastructure for registry configurations and corrected mistake in backup mechanism.

This commit is contained in:
dbuechel 2019-07-02 10:35:40 +02:00
parent ac5791ee13
commit ee683af63c
28 changed files with 474 additions and 57 deletions

View file

@ -35,6 +35,11 @@ namespace SafeExamBrowser.Contracts.Lockdown
/// </summary>
bool EnableFeature();
/// <summary>
/// Initializes the currently active configuration of the feature.
/// </summary>
void Initialize();
/// <summary>
/// Starts monitoring the feature to ensure that it remains as currently configured.
/// </summary>

View file

@ -18,7 +18,7 @@ namespace SafeExamBrowser.Contracts.Lockdown
/// <summary>
/// Creates an <see cref="IFeatureConfiguration"/> to control notifications of the Google Chrome browser.
/// </summary>
IFeatureConfiguration CreateChromeNotificationConfiguration(Guid groupId);
IFeatureConfiguration CreateChromeNotificationConfiguration(Guid groupId, string sid, string userName);
/// <summary>
/// Creates an <see cref="IFeatureConfiguration"/> to control the ease of access options on the security screen.

View file

@ -32,6 +32,11 @@ namespace SafeExamBrowser.Lockdown.UnitTests
throw new NotImplementedException();
}
public void Initialize()
{
throw new NotImplementedException();
}
public void Monitor()
{
throw new NotImplementedException();

View file

@ -10,6 +10,7 @@ using System;
using SafeExamBrowser.Contracts.Lockdown;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Lockdown.FeatureConfigurations;
using SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.UserHive;
namespace SafeExamBrowser.Lockdown
{
@ -22,9 +23,9 @@ namespace SafeExamBrowser.Lockdown
this.logger = logger;
}
public IFeatureConfiguration CreateChromeNotificationConfiguration(Guid groupId)
public IFeatureConfiguration CreateChromeNotificationConfiguration(Guid groupId, string sid, string userName)
{
return new ChromeNotificationConfiguration(groupId, logger.CloneFor(nameof(ChromeNotificationConfiguration)));
return new ChromeNotificationConfiguration(groupId, logger.CloneFor(nameof(ChromeNotificationConfiguration)), sid, userName);
}
public IFeatureConfiguration CreateEaseOfAccessConfiguration(Guid groupId)

View file

@ -1,47 +0,0 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Lockdown.FeatureConfigurations
{
[Serializable]
internal class ChromeNotificationConfiguration : FeatureConfiguration
{
public ChromeNotificationConfiguration(Guid groupId, ILogger logger) : base(groupId, logger)
{
}
public override bool DisableFeature()
{
logger.Info("Disabling...");
return true;
}
public override bool EnableFeature()
{
logger.Info("Enabling...");
return true;
}
public override void Monitor()
{
logger.Info("Monitoring...");
}
public override bool Restore()
{
logger.Info("Restoring...");
return true;
}
}
}

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -31,6 +31,7 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
public abstract bool DisableFeature();
public abstract bool EnableFeature();
public abstract void Initialize();
public abstract void Monitor();
public abstract bool Restore();

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.Win32;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
{
[Serializable]
internal abstract class MachineHiveConfiguration : RegistryConfiguration
{
protected override RegistryKey RootKey => Registry.LocalMachine;
public MachineHiveConfiguration(Guid groupId, ILogger logger) : base(groupId, logger)
{
}
protected override bool IsHiveAvailable(RegistryDataItem item)
{
return true;
}
}
}

View file

@ -0,0 +1,201 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
{
[Serializable]
internal abstract class RegistryConfiguration : FeatureConfiguration
{
private IList<RegistryDataItem> itemsToDelete;
private IList<RegistryDataItem> itemsToRestore;
protected abstract IEnumerable<RegistryConfigurationItem> Items { get; }
protected abstract RegistryKey RootKey { get; }
public RegistryConfiguration(Guid groupId, ILogger logger) : base(groupId, logger)
{
itemsToDelete = new List<RegistryDataItem>();
itemsToRestore = new List<RegistryDataItem>();
}
public override bool DisableFeature()
{
var success = true;
foreach (var item in Items)
{
success &= TrySet(new RegistryDataItem { Key = item.Key, Value = item.Value, Data = item.Disabled });
}
if (success)
{
logger.Info("Successfully disabled feature.");
}
else
{
logger.Warn("Failed to disable feature!");
}
return success;
}
public override bool EnableFeature()
{
var success = true;
foreach (var item in Items)
{
success &= TrySet(new RegistryDataItem { Key = item.Key, Value = item.Value, Data = item.Enabled });
}
if (success)
{
logger.Info("Successfully enabled feature.");
}
else
{
logger.Warn("Failed to enabled feature!");
}
return success;
}
public override void Initialize()
{
foreach (var item in Items)
{
var original = ReadItem(item.Key, item.Value);
if (original.Data == null)
{
itemsToDelete.Add(original);
}
else
{
itemsToRestore.Add(original);
}
}
}
public override void Monitor()
{
// TODO!
}
public override bool Restore()
{
foreach (var item in new List<RegistryDataItem>(itemsToDelete))
{
if (TryDelete(item))
{
itemsToDelete.Remove(item);
}
}
foreach (var item in new List<RegistryDataItem>(itemsToRestore))
{
if (TrySet(item))
{
itemsToRestore.Remove(item);
}
}
var success = !itemsToDelete.Any() && !itemsToRestore.Any();
if (success)
{
logger.Info("Successfully restored feature.");
}
else
{
logger.Warn("Failed to restore feature!");
}
return success;
}
protected abstract bool IsHiveAvailable(RegistryDataItem item);
private RegistryDataItem ReadItem(string key, string value)
{
var data = Registry.GetValue(key, value, null);
var original = new RegistryDataItem { Key = key, Value = value, Data = data };
return original;
}
private bool TryDelete(RegistryDataItem item)
{
var success = false;
try
{
if (IsHiveAvailable(item))
{
var keyWithoutRoot = item.Key.Substring(item.Key.IndexOf('\\') + 1);
using (var key = RootKey.OpenSubKey(keyWithoutRoot, true))
{
if (key.GetValue(item.Value) != null)
{
key.DeleteValue(item.Value);
logger.Debug($"Successfully deleted registry item {item}.");
}
else
{
logger.Debug($"No need to delete registry item {item} as it does not exist.");
}
success = true;
}
}
else
{
logger.Warn($"Cannot delete item {item} as its registry hive is not available!");
}
}
catch (Exception e)
{
logger.Error($"Failed to delete registry item {item}!", e);
}
return success;
}
private bool TrySet(RegistryDataItem item)
{
var success = false;
try
{
if (IsHiveAvailable(item))
{
Registry.SetValue(item.Key, item.Value, item.Data);
logger.Debug($"Successfully set registry item {item}.");
success = true;
}
else
{
logger.Warn($"Cannot set item {item} as its registry hive is not available!");
}
}
catch (Exception e)
{
logger.Error($"Failed to set registry item {item}!", e);
}
return success;
}
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
{
internal class RegistryConfigurationItem
{
internal object Disabled { get; }
internal object Enabled { get; }
internal string Key { get; }
internal string Value { get; }
internal RegistryConfigurationItem(string key, string value, object disabled, object enabled)
{
Key = key;
Value = value;
Disabled = disabled;
Enabled = enabled;
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
{
[Serializable]
internal class RegistryDataItem
{
internal object Data { get; set; }
internal string Key { get; set; }
internal string Value { get; set; }
public override string ToString()
{
return $@"'{Key}\{Value}'{(Data != null ? $" => '{Data}'" : "")}";
}
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.UserHive
{
/// <summary>
/// IMPORTANT: This registry configuration only has an effect after Chrome is restarted!
///
/// See https://www.chromium.org/administrators/policy-list-3#DefaultNotificationsSetting:
/// • 1 = Allow sites to show desktop notifications
/// • 2 = Do not allow any site to show desktop notifications
/// • 3 = Ask every time a site wants to show desktop notifications
/// </summary>
[Serializable]
internal class ChromeNotificationConfiguration : UserHiveConfiguration
{
protected override IEnumerable<RegistryConfigurationItem> Items => new []
{
new RegistryConfigurationItem($@"HKEY_USERS\{SID}\Software\Policies\Google\Chrome", "DefaultNotificationsSetting", 2, 1)
};
public ChromeNotificationConfiguration(Guid groupId, ILogger logger, string sid, string userName) : base(groupId, logger, sid, userName)
{
}
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using Microsoft.Win32;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
{
[Serializable]
internal abstract class UserHiveConfiguration : RegistryConfiguration
{
protected string SID { get; }
protected string UserName { get; }
protected override RegistryKey RootKey => Registry.Users;
public UserHiveConfiguration(Guid groupId, ILogger logger, string sid, string userName) : base(groupId, logger)
{
SID = sid ?? throw new ArgumentNullException(nameof(sid));
UserName = userName ?? throw new ArgumentNullException(nameof(userName));
}
public override string ToString()
{
return $"{GetType().Name} ({Id}) for user '{UserName}'";
}
protected override bool IsHiveAvailable(RegistryDataItem item)
{
var isAvailable = false;
try
{
isAvailable = Registry.Users.OpenSubKey(SID) != null;
}
catch (Exception e)
{
logger.Error($"Failed to check availability of registry hive for item {item}!", e);
}
return isAvailable;
}
}
}

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -28,6 +28,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
public override void Initialize()
{
}
public override void Monitor()
{

View file

@ -56,8 +56,13 @@
<Compile Include="AutoRestoreMechanism.cs" />
<Compile Include="FeatureConfigurationFactory.cs" />
<Compile Include="FeatureConfigurationBackup.cs" />
<Compile Include="FeatureConfigurations\ChromeNotificationConfiguration.cs" />
<Compile Include="FeatureConfigurations\RegistryConfigurations\RegistryDataItem.cs" />
<Compile Include="FeatureConfigurations\RegistryConfigurations\RegistryConfigurationItem.cs" />
<Compile Include="FeatureConfigurations\RegistryConfigurations\UserHive\ChromeNotificationConfiguration.cs" />
<Compile Include="FeatureConfigurations\FeatureConfiguration.cs" />
<Compile Include="FeatureConfigurations\RegistryConfigurations\MachineHiveConfiguration.cs" />
<Compile Include="FeatureConfigurations\RegistryConfigurations\RegistryConfiguration.cs" />
<Compile Include="FeatureConfigurations\RegistryConfigurations\UserHiveConfiguration.cs" />
<Compile Include="FeatureConfigurations\TaskManagerConfiguration.cs" />
<Compile Include="FeatureConfigurations\EaseOfAccessConfiguration.cs" />
<Compile Include="FeatureConfigurations\NetworkOptionsConfiguration.cs" />
@ -77,6 +82,8 @@
<Name>SafeExamBrowser.Contracts</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="FeatureConfigurations\RegistryConfigurations\MachineHive\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -37,7 +37,10 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
factory = new Mock<IFeatureConfigurationFactory>();
logger = new Mock<ILogger>();
settings = new Settings();
sessionContext = new SessionContext { Configuration = new ServiceConfiguration { Settings = settings } };
sessionContext = new SessionContext
{
Configuration = new ServiceConfiguration { Settings = settings, UserName = "TestName", UserSid = "S-1-234-TEST" }
};
sut = new LockdownOperation(backup.Object, factory.Object, logger.Object, sessionContext);
}
@ -57,6 +60,7 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
var result = sut.Perform();
backup.Verify(b => b.Save(It.Is<IFeatureConfiguration>(c => c == configuration.Object)), Times.Exactly(count));
configuration.Verify(c => c.Initialize(), Times.Exactly(count));
configuration.Verify(c => c.DisableFeature(), Times.Exactly(3));
configuration.Verify(c => c.EnableFeature(), Times.Exactly(count - 3));
configuration.Verify(c => c.Monitor(), Times.Exactly(count));
@ -71,12 +75,15 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
var groupId = default(Guid);
configuration.SetReturnsDefault(true);
factory.Setup(f => f.CreateChromeNotificationConfiguration(It.IsAny<Guid>())).Returns(configuration.Object).Callback<Guid>(id => groupId = id);
factory
.Setup(f => f.CreateChromeNotificationConfiguration(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<string>()))
.Returns(configuration.Object)
.Callback<Guid, string, string>((id, name, sid) => groupId = id);
factory.SetReturnsDefault(configuration.Object);
sut.Perform();
factory.Verify(f => f.CreateChromeNotificationConfiguration(It.Is<Guid>(id => id == groupId)), Times.Once);
factory.Verify(f => f.CreateChromeNotificationConfiguration(It.Is<Guid>(id => id == groupId), It.IsAny<string>(), It.IsAny<string>()), Times.Once);
factory.Verify(f => f.CreateEaseOfAccessConfiguration(It.Is<Guid>(id => id == groupId)), Times.Once);
factory.Verify(f => f.CreateNetworkOptionsConfiguration(It.Is<Guid>(id => id == groupId)), Times.Once);
factory.Verify(f => f.CreatePasswordChangeConfiguration(It.Is<Guid>(id => id == groupId)), Times.Once);
@ -104,6 +111,7 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
var result = sut.Perform();
backup.Verify(b => b.Save(It.Is<IFeatureConfiguration>(c => c == configuration.Object)), Times.Exactly(count - offset));
configuration.Verify(c => c.Initialize(), Times.Exactly(count - offset));
configuration.Verify(c => c.DisableFeature(), Times.Never);
configuration.Verify(c => c.EnableFeature(), Times.Exactly(count - offset));
configuration.Verify(c => c.Monitor(), Times.Exactly(count - offset - 1));
@ -141,18 +149,22 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
configuration1.Verify(c => c.DisableFeature(), Times.Never);
configuration1.Verify(c => c.EnableFeature(), Times.Never);
configuration1.Verify(c => c.Initialize(), Times.Never);
configuration1.Verify(c => c.Restore(), Times.Once);
configuration2.Verify(c => c.DisableFeature(), Times.Never);
configuration2.Verify(c => c.EnableFeature(), Times.Never);
configuration2.Verify(c => c.Initialize(), Times.Never);
configuration2.Verify(c => c.Restore(), Times.Once);
configuration3.Verify(c => c.DisableFeature(), Times.Never);
configuration3.Verify(c => c.EnableFeature(), Times.Never);
configuration3.Verify(c => c.Initialize(), Times.Never);
configuration3.Verify(c => c.Restore(), Times.Once);
configuration4.Verify(c => c.DisableFeature(), Times.Never);
configuration4.Verify(c => c.EnableFeature(), Times.Never);
configuration4.Verify(c => c.Initialize(), Times.Never);
configuration4.Verify(c => c.Restore(), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
@ -188,18 +200,22 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
configuration1.Verify(c => c.DisableFeature(), Times.Never);
configuration1.Verify(c => c.EnableFeature(), Times.Never);
configuration1.Verify(c => c.Initialize(), Times.Never);
configuration1.Verify(c => c.Restore(), Times.Once);
configuration2.Verify(c => c.DisableFeature(), Times.Never);
configuration2.Verify(c => c.EnableFeature(), Times.Never);
configuration2.Verify(c => c.Initialize(), Times.Never);
configuration2.Verify(c => c.Restore(), Times.Once);
configuration3.Verify(c => c.DisableFeature(), Times.Never);
configuration3.Verify(c => c.EnableFeature(), Times.Never);
configuration3.Verify(c => c.Initialize(), Times.Never);
configuration3.Verify(c => c.Restore(), Times.Once);
configuration4.Verify(c => c.DisableFeature(), Times.Never);
configuration4.Verify(c => c.EnableFeature(), Times.Never);
configuration4.Verify(c => c.Initialize(), Times.Never);
configuration4.Verify(c => c.Restore(), Times.Once);
Assert.AreEqual(OperationResult.Failed, result);

View file

@ -36,9 +36,11 @@ namespace SafeExamBrowser.Service.Operations
groupId = Guid.NewGuid();
var success = true;
var sid = Context.Configuration.UserSid;
var userName = Context.Configuration.UserName;
var configurations = new []
{
(factory.CreateChromeNotificationConfiguration(groupId), Context.Configuration.Settings.Service.DisableChromeNotifications),
(factory.CreateChromeNotificationConfiguration(groupId, sid, userName), Context.Configuration.Settings.Service.DisableChromeNotifications),
(factory.CreateEaseOfAccessConfiguration(groupId), Context.Configuration.Settings.Service.DisableEaseOfAccessOptions),
(factory.CreateNetworkOptionsConfiguration(groupId), Context.Configuration.Settings.Service.DisableNetworkOptions),
(factory.CreatePasswordChangeConfiguration(groupId), Context.Configuration.Settings.Service.DisablePasswordChange),
@ -114,6 +116,7 @@ namespace SafeExamBrowser.Service.Operations
{
var success = false;
configuration.Initialize();
backup.Save(configuration);
if (disable)

View file

@ -52,6 +52,9 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>SafeExamBrowser.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
@ -102,6 +105,8 @@
<Name>SafeExamBrowser.Logging</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Content Include="SafeExamBrowser.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB