SEBWIN-320: Implemented configuration reset functionality for reset utility.

This commit is contained in:
dbuechel 2019-07-19 10:07:45 +02:00
parent 2754cdcc56
commit 68d487dd46
27 changed files with 390 additions and 17 deletions

View file

@ -45,6 +45,11 @@ namespace SafeExamBrowser.Contracts.Lockdown
/// </summary>
void Initialize();
/// <summary>
/// Resets the feature to its default configuration. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool Reset();
/// <summary>
/// Restores the feature to its previous configuration (i.e. before it was enabled or disabled). Returns <c>true</c> if successful,
/// otherwise <c>false</c>.

View file

@ -7,6 +7,7 @@
*/
using System;
using System.Collections.Generic;
namespace SafeExamBrowser.Contracts.Lockdown
{
@ -15,6 +16,11 @@ namespace SafeExamBrowser.Contracts.Lockdown
/// </summary>
public interface IFeatureConfigurationFactory
{
/// <summary>
/// Creates all feature configurations.
/// </summary>
IList<IFeatureConfiguration> CreateAll(Guid groupId, string sid, string userName);
/// <summary>
/// Creates an <see cref="IFeatureConfiguration"/> to control the option to change the password of a user account via the security screen.
/// </summary>

View file

@ -22,5 +22,10 @@ namespace SafeExamBrowser.Contracts.SystemComponents
/// Retrieves the security identifier of the currently logged in user.
/// </summary>
string GetUserSid();
/// <summary>
/// Tries to retrieve the security identifier for the specified user name. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool TryGetSidForUser(string userName, out string sid);
}
}

View file

@ -42,6 +42,11 @@ namespace SafeExamBrowser.Lockdown.UnitTests
throw new NotImplementedException();
}
public bool Reset()
{
throw new NotImplementedException();
}
public bool Restore()
{
throw new NotImplementedException();

View file

@ -7,6 +7,7 @@
*/
using System;
using System.Collections.Generic;
using SafeExamBrowser.Contracts.Lockdown;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.MachineHive;
@ -24,6 +25,25 @@ namespace SafeExamBrowser.Lockdown
this.logger = logger;
}
public IList<IFeatureConfiguration> CreateAll(Guid groupId, string sid, string userName)
{
return new List<IFeatureConfiguration>
{
CreateChangePasswordConfiguration(groupId, sid, userName),
CreateChromeNotificationConfiguration(groupId, sid, userName),
CreateEaseOfAccessConfiguration(groupId),
CreateLockWorkstationConfiguration(groupId, sid, userName),
CreateNetworkOptionsConfiguration(groupId),
CreatePowerOptionsConfiguration(groupId),
CreateRemoteConnectionConfiguration(groupId),
CreateSignoutConfiguration(groupId, sid, userName),
CreateSwitchUserConfiguration(groupId),
CreateTaskManagerConfiguration(groupId, sid, userName),
CreateVmwareOverlayConfiguration(groupId, sid, userName),
CreateWindowsUpdateConfiguration(groupId)
};
}
public IFeatureConfiguration CreateChangePasswordConfiguration(Guid groupId, string sid, string userName)
{
return new ChangePasswordConfiguration(groupId, logger.CloneFor(nameof(ChangePasswordConfiguration)), sid, userName);

View file

@ -33,6 +33,7 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
public abstract bool EnableFeature();
public abstract FeatureConfigurationStatus GetStatus();
public abstract void Initialize();
public abstract bool Reset();
public abstract bool Restore();
public override string ToString()

View file

@ -23,5 +23,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public EaseOfAccessConfiguration(Guid groupId, ILogger logger) : base(groupId, logger)
{
}
public override bool Reset()
{
return DeleteConfiguration();
}
}
}

View file

@ -23,5 +23,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public NetworkOptionsConfiguration(Guid groupId, ILogger logger) : base(groupId, logger)
{
}
public override bool Reset()
{
return DeleteConfiguration();
}
}
}

View file

@ -23,5 +23,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public PowerOptionsConfiguration(Guid groupId, ILogger logger) : base(groupId, logger)
{
}
public override bool Reset()
{
return DeleteConfiguration();
}
}
}

View file

@ -12,6 +12,13 @@ using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.MachineHive
{
/// <summary>
/// Specifies whether Remote Desktop connections are enabled.
///
/// See https://docs.microsoft.com/en-us/windows-hardware/customize/desktop/unattend/microsoft-windows-terminalservices-localsessionmanager-fdenytsconnections:
/// • 0 = Specifies that remote desktop connections are enabled.
/// • 1 = Specifies that remote desktop connections are denied. This is the default value.
/// </summary>
[Serializable]
internal class RemoteConnectionConfiguration : MachineHiveConfiguration
{
@ -23,5 +30,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public RemoteConnectionConfiguration(Guid groupId, ILogger logger) : base(groupId, logger)
{
}
public override bool Reset()
{
return DisableFeature();
}
}
}

View file

@ -23,5 +23,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public SwitchUserConfiguration(Guid groupId, ILogger logger) : base(groupId, logger)
{
}
public override bool Reset()
{
return DeleteConfiguration();
}
}
}

View file

@ -30,6 +30,8 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
itemsToRestore = new List<RegistryDataItem>();
}
protected abstract bool IsHiveAvailable(RegistryDataItem item);
public override bool DisableFeature()
{
var success = true;
@ -66,7 +68,7 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
}
else
{
logger.Warn("Failed to enabled feature!");
logger.Warn("Failed to enable feature!");
}
return success;
@ -148,7 +150,26 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
return success;
}
protected abstract bool IsHiveAvailable(RegistryDataItem item);
protected bool DeleteConfiguration()
{
var success = true;
foreach (var item in Items)
{
success &= TryDelete(new RegistryDataItem { Key = item.Key, Value = item.Value });
}
if (success)
{
logger.Info("Successfully deleted feature configuration.");
}
else
{
logger.Warn("Failed to delete feature configuration!");
}
return success;
}
private RegistryDataItem ReadItem(string key, string value)
{

View file

@ -23,5 +23,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public ChangePasswordConfiguration(Guid groupId, ILogger logger, string sid, string userName) : base(groupId, logger, sid, userName)
{
}
public override bool Reset()
{
return DeleteConfiguration();
}
}
}

View file

@ -16,9 +16,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
/// 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
/// • 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
@ -31,5 +31,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public ChromeNotificationConfiguration(Guid groupId, ILogger logger, string sid, string userName) : base(groupId, logger, sid, userName)
{
}
public override bool Reset()
{
return DeleteConfiguration();
}
}
}

View file

@ -23,5 +23,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public LockWorkstationConfiguration(Guid groupId, ILogger logger, string sid, string userName) : base(groupId, logger, sid, userName)
{
}
public override bool Reset()
{
return DeleteConfiguration();
}
}
}

View file

@ -23,5 +23,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public SignoutConfiguration(Guid groupId, ILogger logger, string sid, string userName) : base(groupId, logger, sid, userName)
{
}
public override bool Reset()
{
return DeleteConfiguration();
}
}
}

View file

@ -23,5 +23,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public TaskManagerConfiguration(Guid groupId, ILogger logger, string sid, string userName) : base(groupId, logger, sid, userName)
{
}
public override bool Reset()
{
return DeleteConfiguration();
}
}
}

View file

@ -24,5 +24,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations.
public VmwareOverlayConfiguration(Guid groupId, ILogger logger, string sid, string userName) : base(groupId, logger, sid, userName)
{
}
public override bool Reset()
{
return EnableFeature();
}
}
}

View file

@ -23,5 +23,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.ServiceConfigurations
public WindowsUpdateConfiguration(Guid groupId, ILogger logger) : base(groupId, logger)
{
}
public override bool Reset()
{
return EnableFeature();
}
}
}

View file

@ -14,11 +14,13 @@ using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Lockdown;
using SafeExamBrowser.Logging;
using SafeExamBrowser.ResetUtility.Procedure;
using SafeExamBrowser.SystemComponents;
namespace SafeExamBrowser.ResetUtility
{
internal class CompositionRoot
{
private ProcedureContext context;
private ILogger logger;
internal ProcedureStep InitialStep { get; private set; }
@ -26,14 +28,8 @@ namespace SafeExamBrowser.ResetUtility
internal void BuildObjectGraph()
{
var context = new ProcedureContext();
InitializeLogging();
context.CreateBackup = CreateBackup;
context.Logger = logger;
context.MainMenu = BuildMainMenu(context);
context.Update = new SystemConfigurationUpdate(new ModuleLogger(logger, nameof(SystemConfigurationUpdate)));
InitializeContext();
InitialStep = new Initialization(context);
NativeMethods = new NativeMethods();
@ -68,6 +64,17 @@ namespace SafeExamBrowser.ResetUtility
return new FeatureConfigurationBackup(filePath, new ModuleLogger(logger, nameof(FeatureConfigurationBackup)));
}
private void InitializeContext()
{
context = new ProcedureContext();
context.ConfigurationFactory = new FeatureConfigurationFactory(new ModuleLogger(logger, nameof(FeatureConfigurationFactory)));
context.CreateBackup = CreateBackup;
context.Logger = logger;
context.MainMenu = BuildMainMenu(context);
context.Update = new SystemConfigurationUpdate(new ModuleLogger(logger, nameof(SystemConfigurationUpdate)));
context.UserInfo = new UserInfo(new ModuleLogger(logger, nameof(UserInfo)));
}
private void InitializeLogging()
{
var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), nameof(SafeExamBrowser));

View file

@ -10,14 +10,17 @@ using System;
using System.Collections.Generic;
using SafeExamBrowser.Contracts.Lockdown;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.SystemComponents;
namespace SafeExamBrowser.ResetUtility.Procedure
{
internal class ProcedureContext
{
internal IFeatureConfigurationFactory ConfigurationFactory { get; set; }
internal Func<string, IFeatureConfigurationBackup> CreateBackup { get; set; }
internal ILogger Logger { get; set; }
internal IList<MainMenuOption> MainMenu { get; set; }
internal ISystemConfigurationUpdate Update { get; set; }
internal IUserInfo UserInfo { get; set; }
}
}

View file

@ -57,6 +57,25 @@ namespace SafeExamBrowser.ResetUtility.Procedure
Console.CursorVisible = false;
}
protected void ClearLine(int top)
{
Console.SetCursorPosition(0, top);
Console.WriteLine(new String(' ', Console.BufferWidth));
Console.SetCursorPosition(0, top);
}
protected string ReadLine()
{
Console.Write("> ");
Console.CursorVisible = true;
var input = Console.ReadLine();
Console.CursorVisible = false;
return input;
}
protected void ShowError(string message)
{
Console.ForegroundColor = ConsoleColor.Red;

View file

@ -6,6 +6,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using SafeExamBrowser.Contracts.Lockdown;
namespace SafeExamBrowser.ResetUtility.Procedure
{
internal class Reset : ProcedureStep
@ -16,6 +21,15 @@ namespace SafeExamBrowser.ResetUtility.Procedure
internal override ProcedureStepResult Execute()
{
InitializeConsole();
var success = TryGetUserInfo(out var userName, out var sid);
if (success)
{
ResetAll(userName, sid);
}
return ProcedureStepResult.Continue;
}
@ -23,5 +37,120 @@ namespace SafeExamBrowser.ResetUtility.Procedure
{
return new MainMenu(Context);
}
private bool TryGetUserInfo(out string userName, out string sid)
{
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine("IMPORTANT: Some configuration values are user specific. In order to reset these values, the user specified below needs to be logged in!");
Console.ForegroundColor = ForegroundColor;
Console.WriteLine();
Console.WriteLine("Please enter the name of the user for which to reset all configuration values:");
userName = ReadLine();
StartProgressAnimation();
var success = Context.UserInfo.TryGetSidForUser(userName, out sid);
StopProgressAnimation();
while (!success)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Could not find user '{userName}'!");
Console.ForegroundColor = ForegroundColor;
var tryAgain = new MenuOption { IsSelected = true, Text = "Try again" };
var mainMenu = new MenuOption { Text = "Return to main menu" };
ShowMenu(new List<MenuOption> { tryAgain, mainMenu });
if (mainMenu.IsSelected)
{
break;
}
ClearLine(Console.CursorTop - 1);
ClearLine(Console.CursorTop - 1);
ClearLine(Console.CursorTop - 1);
ClearLine(Console.CursorTop - 1);
userName = ReadLine();
success = Context.UserInfo.TryGetSidForUser(userName, out sid);
}
return success;
}
private void ResetAll(string userName, string sid)
{
var configurations = Context.ConfigurationFactory.CreateAll(Guid.NewGuid(), sid, userName);
var failed = new List<IFeatureConfiguration>();
Logger.Info($"Attempting to reset all configuration values for user '{userName}' with SID '{sid}'...");
Console.WriteLine();
Console.WriteLine("Initiating reset procedure...");
foreach (var configuration in configurations)
{
var success = configuration.Reset();
if (!success)
{
failed.Add(configuration);
}
ShowProgress(configurations.IndexOf(configuration) + 1, configurations.Count);
}
PerformUpdate();
if (failed.Any())
{
HandleFailure(failed);
}
else
{
HandleSuccess();
}
Console.WriteLine();
Console.WriteLine("Press any key to return to the main menu.");
Console.ReadKey();
}
private void PerformUpdate()
{
Console.WriteLine();
Console.WriteLine("Performing system configuration update, please wait...");
StartProgressAnimation();
Context.Update.Execute();
StopProgressAnimation();
Console.WriteLine("Update completed.");
}
private void HandleFailure(IList<IFeatureConfiguration> configurations)
{
Logger.Warn($"Failed to reset {configurations.Count} items!");
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Failed to reset {configurations.Count} items!");
foreach (var configuration in configurations)
{
Console.WriteLine($" - {configuration.GetType().Name}");
}
Console.ForegroundColor = ForegroundColor;
Console.WriteLine();
Console.WriteLine("Please consult the application log for more information.");
}
private void HandleSuccess()
{
Logger.Info("Successfully reset all changes!");
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.DarkGreen;
Console.WriteLine("Successfully reset all changes!");
Console.ForegroundColor = ForegroundColor;
}
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using System.IO;
@ -104,13 +103,11 @@ namespace SafeExamBrowser.ResetUtility.Procedure
private void PerformUpdate()
{
Logger.Info("Starting system configuration update...");
Console.WriteLine();
Console.WriteLine("Performing system configuration update, please wait...");
StartProgressAnimation();
Context.Update.Execute();
StopProgressAnimation();
Logger.Info("Update completed.");
Console.WriteLine("Update completed.");
}
@ -160,7 +157,7 @@ namespace SafeExamBrowser.ResetUtility.Procedure
next = new Reset(Context);
}
Logger.Info($"The user chose {(yes.IsSelected ? "" : "not ")}to perform a reset for now.");
Logger.Info($"The user chose {(yes.IsSelected ? "" : "not ")}to perform a reset.");
}
}
}

View file

@ -92,6 +92,10 @@
<Project>{e107026c-2011-4552-a7d8-3a0d37881df6}</Project>
<Name>SafeExamBrowser.Logging</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.SystemComponents\SafeExamBrowser.SystemComponents.csproj">
<Project>{acee2ef1-14d2-4b52-8994-5c053055bb51}</Project>
<Name>SafeExamBrowser.SystemComponents</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="SafeExamBrowser.ico" />

View file

@ -71,7 +71,7 @@ namespace SafeExamBrowser.Runtime
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
var sessionContext = new SessionContext();
var uiFactory = new UserInterfaceFactory(text);
var userInfo = new UserInfo();
var userInfo = new UserInfo(ModuleLogger(nameof(UserInfo)));
var bootstrapOperations = new Queue<IOperation>();
var sessionOperations = new Queue<IRepeatableOperation>();

View file

@ -7,13 +7,25 @@
*/
using System;
using System.Diagnostics;
using System.Security.Principal;
using System.Text.RegularExpressions;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.SystemComponents;
namespace SafeExamBrowser.SystemComponents
{
public class UserInfo : IUserInfo
{
private const string SID_REGEX_PATTERN = @"S-\d(-\d+)+";
private ILogger logger;
public UserInfo(ILogger logger)
{
this.logger = logger;
}
public string GetUserName()
{
return Environment.UserName;
@ -23,5 +35,77 @@ namespace SafeExamBrowser.SystemComponents
{
return WindowsIdentity.GetCurrent().User.Value;
}
public bool TryGetSidForUser(string userName, out string sid)
{
var strategies = new Func<string, string>[] { NtAccount, Wmi };
var success = false;
sid = default(string);
foreach (var strategy in strategies)
{
try
{
sid = strategy.Invoke(userName);
if (IsValid(sid))
{
logger.Info($"Found SID '{sid}' via '{strategy.Method.Name}' for user name '{userName}'!");
success = true;
break;
}
logger.Warn($"Retrieved invalid SID '{sid}' via '{strategy.Method.Name}' for user name '{userName}'!");
}
catch (Exception e)
{
logger.Error($"Failed to get SID via '{strategy.Method.Name}' for user name '{userName}'!", e);
}
}
if (!success)
{
logger.Error($"All attempts to retrieve SID for user name '{userName}' failed!");
}
return success;
}
private string NtAccount(string userName)
{
var account = new NTAccount(userName);
if (account.IsValidTargetType(typeof(SecurityIdentifier)))
{
return account.Translate(typeof(SecurityIdentifier)).Value;
}
return null;
}
private string Wmi(string userName)
{
var process = new Process();
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.Arguments = string.Format("/c \"wmic useraccount where name='{0}' get sid\"", userName);
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForExit(5000);
var output = process.StandardOutput.ReadToEnd();
var match = Regex.Match(output, SID_REGEX_PATTERN);
return match.Success ? match.Value : null;
}
private bool IsValid(string sid)
{
return !String.IsNullOrWhiteSpace(sid) && Regex.IsMatch(sid, $"^{SID_REGEX_PATTERN}$");
}
}
}