From dd78bc1fbc2adb6040df068c8259689dff63d228 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Thu, 27 Jun 2019 08:32:37 +0200 Subject: [PATCH] SEBWIN-301: Implemented auto-restore mechanism for service, added return status to feature configuration methods and introduced session flag for service. --- .../Lockdown/IFeatureConfiguration.cs | 13 +- .../AutoRestoreMechanismTests.cs | 130 ++++++++++++++++++ .../SafeExamBrowser.Lockdown.UnitTests.csproj | 28 +++- .../packages.config | 4 + .../AutoRestoreMechanism.cs | 88 +++++++++++- .../FeatureConfigurationBackup.cs | 48 ++++--- .../ChromeNotificationConfiguration.cs | 12 +- .../EaseOfAccessConfiguration.cs | 12 +- .../FeatureConfiguration.cs | 6 +- .../NetworkOptionsConfiguration.cs | 12 +- .../PasswordChangeConfiguration.cs | 12 +- .../PowerOptionsConfiguration.cs | 12 +- .../RemoteConnectionConfiguration.cs | 12 +- .../SignoutConfiguration.cs | 12 +- .../TaskManagerConfiguration.cs | 12 +- .../UserLockConfiguration.cs | 12 +- .../UserSwitchConfiguration.cs | 12 +- .../VmwareOverlayConfiguration.cs | 12 +- .../WindowsUpdateConfiguration.cs | 12 +- .../SessionActivationOperationTests.cs | 4 + .../SessionInitializationOperationTests.cs | 28 ++++ .../ServiceControllerTests.cs | 4 + SafeExamBrowser.Service/CompositionRoot.cs | 7 +- .../Operations/LockdownOperation.cs | 105 +++++++++----- .../Operations/SessionActivationOperation.cs | 2 + .../SessionInitializationOperation.cs | 5 +- SafeExamBrowser.Service/ServiceController.cs | 2 +- SafeExamBrowser.Service/SessionContext.cs | 5 + 28 files changed, 480 insertions(+), 143 deletions(-) create mode 100644 SafeExamBrowser.Lockdown.UnitTests/AutoRestoreMechanismTests.cs diff --git a/SafeExamBrowser.Contracts/Lockdown/IFeatureConfiguration.cs b/SafeExamBrowser.Contracts/Lockdown/IFeatureConfiguration.cs index 45d769f1..59d02052 100644 --- a/SafeExamBrowser.Contracts/Lockdown/IFeatureConfiguration.cs +++ b/SafeExamBrowser.Contracts/Lockdown/IFeatureConfiguration.cs @@ -26,14 +26,14 @@ namespace SafeExamBrowser.Contracts.Lockdown Guid GroupId { get; } /// - /// Disables the feature. + /// Disables the feature. Returns true if successful, otherwise false. /// - void DisableFeature(); + bool DisableFeature(); /// - /// Enables the feature. + /// Enables the feature. Returns true if successful, otherwise false. /// - void EnableFeature(); + bool EnableFeature(); /// /// Starts monitoring the feature to ensure that it remains as currently configured. @@ -41,8 +41,9 @@ namespace SafeExamBrowser.Contracts.Lockdown void Monitor(); /// - /// Restores the feature to its previous configuration (i.e. before it was enabled or disabled). + /// Restores the feature to its previous configuration (i.e. before it was enabled or disabled). Returns true if successful, + /// otherwise false. /// - void Restore(); + bool Restore(); } } diff --git a/SafeExamBrowser.Lockdown.UnitTests/AutoRestoreMechanismTests.cs b/SafeExamBrowser.Lockdown.UnitTests/AutoRestoreMechanismTests.cs new file mode 100644 index 00000000..2ca3be4d --- /dev/null +++ b/SafeExamBrowser.Lockdown.UnitTests/AutoRestoreMechanismTests.cs @@ -0,0 +1,130 @@ +/* + * 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.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using SafeExamBrowser.Contracts.Lockdown; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Lockdown.UnitTests +{ + [TestClass] + public class AutoRestoreMechanismTests + { + private Mock backup; + private Mock logger; + private AutoRestoreMechanism sut; + + [TestInitialize] + public void Initialize() + { + backup = new Mock(); + logger = new Mock(); + + sut = new AutoRestoreMechanism(backup.Object, logger.Object, 0); + } + + [TestMethod] + public void MustExecuteAsynchronously() + { + var sync = new AutoResetEvent(false); + var threadId = Thread.CurrentThread.ManagedThreadId; + + backup.Setup(b => b.GetAllConfigurations()).Callback(() => { threadId = Thread.CurrentThread.ManagedThreadId; sync.Set(); }); + + sut.Start(); + sync.WaitOne(); + sut.Stop(); + + Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId); + } + + [TestMethod] + public void MustNotTerminateUntilAllChangesReverted() + { + var configuration = new Mock(); + var counter = 0; + var limit = new Random().Next(5, 50); + var list = new List { configuration.Object }; + var sync = new AutoResetEvent(false); + + backup.Setup(b => b.GetAllConfigurations()).Returns(list).Callback(() => counter++); + backup.Setup(b => b.Delete(It.IsAny())).Callback(() => { list.Clear(); sync.Set(); }); + configuration.Setup(c => c.Restore()).Returns(() => counter == limit); + + sut.Start(); + sync.WaitOne(); + sut.Stop(); + + backup.Verify(b => b.GetAllConfigurations(), Times.Exactly(limit)); + backup.Verify(b => b.Delete(It.Is(c => c == configuration.Object)), Times.Once); + configuration.Verify(c => c.Restore(), Times.Exactly(limit)); + } + + [TestMethod] + public void MustRespectTimeout() + { + const int TIMEOUT = 50; + + var after = default(DateTime); + var before = default(DateTime); + var configuration = new Mock(); + var counter = 0; + var list = new List { configuration.Object }; + var sync = new AutoResetEvent(false); + + sut = new AutoRestoreMechanism(backup.Object, logger.Object, TIMEOUT); + + backup.Setup(b => b.GetAllConfigurations()).Returns(list).Callback(() => + { + switch (++counter) + { + case 1: + before = DateTime.Now; + break; + case 2: + after = DateTime.Now; + list.Clear(); + sync.Set(); + break; + } + }); + + sut.Start(); + sync.WaitOne(); + sut.Stop(); + + Assert.IsTrue(after - before >= new TimeSpan(0, 0, 0, 0, TIMEOUT)); + } + + [TestMethod] + public void MustStop() + { + var configuration = new Mock(); + var counter = 0; + var list = new List { configuration.Object }; + + backup.Setup(b => b.GetAllConfigurations()).Returns(list).Callback(() => counter++); + + sut.Start(); + Thread.Sleep(25); + sut.Stop(); + + backup.Verify(b => b.GetAllConfigurations(), Times.Between(counter, counter + 1, Range.Inclusive)); + } + + [TestMethod] + public void MustValidateTimeout() + { + Assert.ThrowsException(() => new AutoRestoreMechanism(backup.Object, logger.Object, new Random().Next(int.MinValue, -1))); + } + } +} diff --git a/SafeExamBrowser.Lockdown.UnitTests/SafeExamBrowser.Lockdown.UnitTests.csproj b/SafeExamBrowser.Lockdown.UnitTests/SafeExamBrowser.Lockdown.UnitTests.csproj index ceeaca29..ac6b6a7e 100644 --- a/SafeExamBrowser.Lockdown.UnitTests/SafeExamBrowser.Lockdown.UnitTests.csproj +++ b/SafeExamBrowser.Lockdown.UnitTests/SafeExamBrowser.Lockdown.UnitTests.csproj @@ -57,20 +57,46 @@ MinimumRecommendedRules.ruleset + + ..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll + ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll ..\packages\MSTest.TestFramework.1.3.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Moq.4.12.0\lib\net45\Moq.dll + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.1\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll + + - + + Designer + + + + + {47da5933-bef8-4729-94e6-abde2db12262} + SafeExamBrowser.Contracts + + + {386b6042-3e12-4753-9fc6-c88ea4f97030} + SafeExamBrowser.Lockdown + diff --git a/SafeExamBrowser.Lockdown.UnitTests/packages.config b/SafeExamBrowser.Lockdown.UnitTests/packages.config index 2f7c5a18..18056a53 100644 --- a/SafeExamBrowser.Lockdown.UnitTests/packages.config +++ b/SafeExamBrowser.Lockdown.UnitTests/packages.config @@ -1,5 +1,9 @@  + + + + \ No newline at end of file diff --git a/SafeExamBrowser.Lockdown/AutoRestoreMechanism.cs b/SafeExamBrowser.Lockdown/AutoRestoreMechanism.cs index 9d042abd..fc9d7b40 100644 --- a/SafeExamBrowser.Lockdown/AutoRestoreMechanism.cs +++ b/SafeExamBrowser.Lockdown/AutoRestoreMechanism.cs @@ -6,27 +6,107 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; +using System.Linq; +using System.Threading.Tasks; using SafeExamBrowser.Contracts.Lockdown; +using SafeExamBrowser.Contracts.Logging; namespace SafeExamBrowser.Lockdown { public class AutoRestoreMechanism : IAutoRestoreMechanism { - private IFeatureConfigurationBackup backup; + private readonly object @lock = new object(); - public AutoRestoreMechanism(IFeatureConfigurationBackup backup) + private IFeatureConfigurationBackup backup; + private ILogger logger; + private bool running; + private int timeout_ms; + + public AutoRestoreMechanism(IFeatureConfigurationBackup backup, ILogger logger, int timeout_ms) { + if (timeout_ms < 0) + { + throw new ArgumentException("Must be 0 or greater!", nameof(timeout_ms)); + } + this.backup = backup; + this.logger = logger; + this.timeout_ms = timeout_ms; } public void Start() { - + lock (@lock) + { + if (!running) + { + running = true; + Task.Run(new Action(RestoreAll)); + logger.Info("Started auto-restore mechanism."); + } + else + { + logger.Info("Auto-restore mechanism is already running."); + } + } } public void Stop() { - + lock (@lock) + { + if (running) + { + running = false; + logger.Info("Stopped auto-restore mechanism."); + } + else + { + logger.Info("Auto-restore mechanism is not running."); + } + } + } + + private void RestoreAll() + { + var configurations = backup.GetAllConfigurations(); + + if (!configurations.Any()) + { + running = false; + logger.Info("Nothing to restore, stopped auto-restore mechanism."); + + return; + } + + logger.Info($"Attempting to restore {configurations.Count} items..."); + + foreach (var configuration in configurations) + { + var success = configuration.Restore(); + + if (success) + { + backup.Delete(configuration); + } + else + { + logger.Warn($"Failed to restore {configuration}!"); + } + + lock (@lock) + { + if (!running) + { + logger.Info("Auto-restore mechanism was aborted."); + + return; + } + } + } + + Task.Delay(timeout_ms).ContinueWith((_) => RestoreAll()); } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurationBackup.cs b/SafeExamBrowser.Lockdown/FeatureConfigurationBackup.cs index cb87b87a..7c786d1d 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurationBackup.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurationBackup.cs @@ -45,7 +45,7 @@ namespace SafeExamBrowser.Lockdown } else { - logger.Warn($"Could not delete {configuration} as it does not exists in backup!"); + logger.Warn($"Could not delete {configuration} as it does not exist in backup!"); } } } @@ -78,25 +78,6 @@ namespace SafeExamBrowser.Lockdown } } - private void SaveToFile(List configurations) - { - try - { - logger.Debug($"Attempting to save backup data to '{filePath}'..."); - - using (var stream = File.Open(filePath, FileMode.Create)) - { - new BinaryFormatter().Serialize(stream, configurations); - } - - logger.Debug($"Successfully saved {configurations.Count} items."); - } - catch (Exception e) - { - logger.Error($"Failed to save backup data to '{filePath}'!", e); - } - } - private List LoadFromFile() { var configurations = new List(); @@ -128,5 +109,32 @@ namespace SafeExamBrowser.Lockdown return configurations; } + + private void SaveToFile(List configurations) + { + try + { + if (configurations.Any()) + { + logger.Debug($"Attempting to save backup data to '{filePath}'..."); + + using (var stream = File.Open(filePath, FileMode.Create)) + { + new BinaryFormatter().Serialize(stream, configurations); + } + + logger.Debug($"Successfully saved {configurations.Count} items."); + } + else + { + File.Delete(filePath); + logger.Debug("No backup data to save, deleted backup file."); + } + } + catch (Exception e) + { + logger.Error($"Failed to save backup data to '{filePath}'!", e); + } + } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/ChromeNotificationConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/ChromeNotificationConfiguration.cs index 490af30c..af50791e 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/ChromeNotificationConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/ChromeNotificationConfiguration.cs @@ -18,14 +18,18 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { logger.Info("Disabling..."); + + return true; } - public override void EnableFeature() + public override bool EnableFeature() { logger.Info("Enabling..."); + + return true; } public override void Monitor() @@ -33,9 +37,11 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations logger.Info("Monitoring..."); } - public override void Restore() + public override bool Restore() { logger.Info("Restoring..."); + + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/EaseOfAccessConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/EaseOfAccessConfiguration.cs index 3b876d95..bf3544b8 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/EaseOfAccessConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/EaseOfAccessConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/FeatureConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/FeatureConfiguration.cs index 63e00ddd..825ed412 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/FeatureConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/FeatureConfiguration.cs @@ -29,10 +29,10 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations this.logger = logger; } - public abstract void DisableFeature(); - public abstract void EnableFeature(); + public abstract bool DisableFeature(); + public abstract bool EnableFeature(); public abstract void Monitor(); - public abstract void Restore(); + public abstract bool Restore(); public override string ToString() { diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/NetworkOptionsConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/NetworkOptionsConfiguration.cs index eedb2258..da522f62 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/NetworkOptionsConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/NetworkOptionsConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/PasswordChangeConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/PasswordChangeConfiguration.cs index f8051df9..6f37e7a8 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/PasswordChangeConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/PasswordChangeConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/PowerOptionsConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/PowerOptionsConfiguration.cs index f7be1c5d..f100a31b 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/PowerOptionsConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/PowerOptionsConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/RemoteConnectionConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/RemoteConnectionConfiguration.cs index e1b15e0c..4a60ef07 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/RemoteConnectionConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/RemoteConnectionConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/SignoutConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/SignoutConfiguration.cs index 43d78ea0..d6db15ab 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/SignoutConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/SignoutConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/TaskManagerConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/TaskManagerConfiguration.cs index 960ef92f..8df5546a 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/TaskManagerConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/TaskManagerConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/UserLockConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/UserLockConfiguration.cs index 03dd2578..39437c7c 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/UserLockConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/UserLockConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/UserSwitchConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/UserSwitchConfiguration.cs index 602dbfbf..67330467 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/UserSwitchConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/UserSwitchConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/VmwareOverlayConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/VmwareOverlayConfiguration.cs index 55454767..c8b17e82 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/VmwareOverlayConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/VmwareOverlayConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/WindowsUpdateConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/WindowsUpdateConfiguration.cs index b89b4fbb..706e8a89 100644 --- a/SafeExamBrowser.Lockdown/FeatureConfigurations/WindowsUpdateConfiguration.cs +++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/WindowsUpdateConfiguration.cs @@ -18,14 +18,14 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations { } - public override void DisableFeature() + public override bool DisableFeature() { - + return true; } - public override void EnableFeature() + public override bool EnableFeature() { - + return true; } public override void Monitor() @@ -33,9 +33,9 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations } - public override void Restore() + public override bool Restore() { - + return true; } } } diff --git a/SafeExamBrowser.Service.UnitTests/Operations/SessionActivationOperationTests.cs b/SafeExamBrowser.Service.UnitTests/Operations/SessionActivationOperationTests.cs index d57ace81..001e3194 100644 --- a/SafeExamBrowser.Service.UnitTests/Operations/SessionActivationOperationTests.cs +++ b/SafeExamBrowser.Service.UnitTests/Operations/SessionActivationOperationTests.cs @@ -45,14 +45,18 @@ namespace SafeExamBrowser.Service.UnitTests.Operations Assert.AreEqual(OperationResult.Success, result); Assert.IsTrue(wasSet); + Assert.IsTrue(sessionContext.IsRunning); } [TestMethod] public void Revert_MustDoNothing() { + sessionContext.IsRunning = true; + var result = sut.Revert(); Assert.AreEqual(OperationResult.Success, result); + Assert.IsTrue(sessionContext.IsRunning); } } } diff --git a/SafeExamBrowser.Service.UnitTests/Operations/SessionInitializationOperationTests.cs b/SafeExamBrowser.Service.UnitTests/Operations/SessionInitializationOperationTests.cs index 56f881d7..abe19126 100644 --- a/SafeExamBrowser.Service.UnitTests/Operations/SessionInitializationOperationTests.cs +++ b/SafeExamBrowser.Service.UnitTests/Operations/SessionInitializationOperationTests.cs @@ -112,6 +112,7 @@ namespace SafeExamBrowser.Service.UnitTests.Operations [TestMethod] public void Revert_MustSetServiceEvent() { + sessionContext.IsRunning = true; sessionContext.ServiceEvent = new EventWaitHandle(false, EventResetMode.AutoReset); var wasSet = false; @@ -124,6 +125,22 @@ namespace SafeExamBrowser.Service.UnitTests.Operations Assert.IsTrue(wasSet); } + [TestMethod] + public void Revert_MustNotSetServiceEventIfNoSessionActive() + { + sessionContext.IsRunning = false; + sessionContext.ServiceEvent = new EventWaitHandle(false, EventResetMode.AutoReset); + + var wasSet = false; + var task = Task.Run(() => wasSet = sessionContext.ServiceEvent.WaitOne(1000)); + var result = sut.Revert(); + + task.Wait(); + + Assert.AreEqual(OperationResult.Success, result); + Assert.IsFalse(wasSet); + } + [TestMethod] public void Revert_MustStartAutoRestoreMechanism() { @@ -132,5 +149,16 @@ namespace SafeExamBrowser.Service.UnitTests.Operations autoRestoreMechanism.Verify(m => m.Start(), Times.Once); Assert.AreEqual(OperationResult.Success, result); } + + [TestMethod] + public void Revert_MustResetSessionFlag() + { + sessionContext.IsRunning = true; + + var result = sut.Revert(); + + Assert.AreEqual(OperationResult.Success, result); + Assert.IsFalse(sessionContext.IsRunning); + } } } diff --git a/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs b/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs index 066b6f9e..7816ab7b 100644 --- a/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs +++ b/SafeExamBrowser.Service.UnitTests/ServiceControllerTests.cs @@ -63,6 +63,7 @@ namespace SafeExamBrowser.Service.UnitTests bootstrapSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success); sessionContext.Configuration = new ServiceConfiguration { SessionId = Guid.NewGuid() }; + sessionContext.IsRunning = true; sut.TryStart(); serviceHost.Raise(h => h.SessionStartRequested += null, args); @@ -79,6 +80,7 @@ namespace SafeExamBrowser.Service.UnitTests bootstrapSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success); sessionContext.Configuration = new ServiceConfiguration { SessionId = args.SessionId }; + sessionContext.IsRunning = true; sut.TryStart(); serviceHost.Raise(h => h.SessionStopRequested += null, args); @@ -106,6 +108,7 @@ namespace SafeExamBrowser.Service.UnitTests var args = new SessionStopEventArgs { SessionId = Guid.NewGuid() }; bootstrapSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success); + sessionContext.IsRunning = false; sut.TryStart(); serviceHost.Raise(h => h.SessionStopRequested += null, args); @@ -145,6 +148,7 @@ namespace SafeExamBrowser.Service.UnitTests bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order); sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order); sessionContext.Configuration = new ServiceConfiguration(); + sessionContext.IsRunning = true; sut.Terminate(); diff --git a/SafeExamBrowser.Service/CompositionRoot.cs b/SafeExamBrowser.Service/CompositionRoot.cs index d391edca..6d48b41c 100644 --- a/SafeExamBrowser.Service/CompositionRoot.cs +++ b/SafeExamBrowser.Service/CompositionRoot.cs @@ -36,11 +36,12 @@ namespace SafeExamBrowser.Service internal void BuildObjectGraph() { const int FIVE_SECONDS = 5000; - var backupFile = BuildBackupFilePath(); + + var backupFilePath = BuildBackupFilePath(); InitializeLogging(); - var featureBackup = new FeatureConfigurationBackup(backupFile, new ModuleLogger(logger, nameof(FeatureConfigurationBackup))); + var featureBackup = new FeatureConfigurationBackup(backupFilePath, new ModuleLogger(logger, nameof(FeatureConfigurationBackup))); var featureFactory = new FeatureConfigurationFactory(new ModuleLogger(logger, nameof(FeatureConfigurationFactory))); var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), new ModuleLogger(logger, nameof(ProxyFactory))); var serviceHost = new ServiceHost(AppConfig.SERVICE_ADDRESS, new HostObjectFactory(), new ModuleLogger(logger, nameof(ServiceHost)), FIVE_SECONDS); @@ -49,7 +50,7 @@ namespace SafeExamBrowser.Service var bootstrapOperations = new Queue(); var sessionOperations = new Queue(); - sessionContext.AutoRestoreMechanism = new AutoRestoreMechanism(featureBackup); + sessionContext.AutoRestoreMechanism = new AutoRestoreMechanism(featureBackup, new ModuleLogger(logger, nameof(AutoRestoreMechanism)), FIVE_SECONDS); bootstrapOperations.Enqueue(new RestoreOperation(featureBackup, logger, sessionContext)); bootstrapOperations.Enqueue(new CommunicationHostOperation(serviceHost, logger)); diff --git a/SafeExamBrowser.Service/Operations/LockdownOperation.cs b/SafeExamBrowser.Service/Operations/LockdownOperation.cs index e7bc5808..bbbace0c 100644 --- a/SafeExamBrowser.Service/Operations/LockdownOperation.cs +++ b/SafeExamBrowser.Service/Operations/LockdownOperation.cs @@ -34,37 +34,46 @@ namespace SafeExamBrowser.Service.Operations public override OperationResult Perform() { groupId = Guid.NewGuid(); + + var success = true; + var configurations = new [] + { + (factory.CreateChromeNotificationConfiguration(groupId), 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), + (factory.CreatePowerOptionsConfiguration(groupId), Context.Configuration.Settings.Service.DisablePowerOptions), + (factory.CreateRemoteConnectionConfiguration(groupId), Context.Configuration.Settings.Service.DisableRemoteConnections), + (factory.CreateSignoutConfiguration(groupId), Context.Configuration.Settings.Service.DisableSignout), + (factory.CreateTaskManagerConfiguration(groupId), Context.Configuration.Settings.Service.DisableTaskManager), + (factory.CreateUserLockConfiguration(groupId), Context.Configuration.Settings.Service.DisableUserLock), + (factory.CreateUserSwitchConfiguration(groupId), Context.Configuration.Settings.Service.DisableUserSwitch), + (factory.CreateVmwareOverlayConfiguration(groupId), Context.Configuration.Settings.Service.DisableVmwareOverlay), + (factory.CreateWindowsUpdateConfiguration(groupId), Context.Configuration.Settings.Service.DisableWindowsUpdate) + }; + logger.Info($"Attempting to perform lockdown (feature configuration group: {groupId})..."); - var chromeNotification = factory.CreateChromeNotificationConfiguration(groupId); - var easeOfAccess = factory.CreateEaseOfAccessConfiguration(groupId); - var networkOptions = factory.CreateNetworkOptionsConfiguration(groupId); - var passwordChange = factory.CreatePasswordChangeConfiguration(groupId); - var powerOptions = factory.CreatePowerOptionsConfiguration(groupId); - var remoteConnection = factory.CreateRemoteConnectionConfiguration(groupId); - var signout = factory.CreateSignoutConfiguration(groupId); - var taskManager = factory.CreateTaskManagerConfiguration(groupId); - var userLock = factory.CreateUserLockConfiguration(groupId); - var userSwitch = factory.CreateUserSwitchConfiguration(groupId); - var vmwareOverlay = factory.CreateVmwareOverlayConfiguration(groupId); - var windowsUpdate = factory.CreateWindowsUpdateConfiguration(groupId); + foreach (var (configuration, disable) in configurations) + { + success &= SetConfiguration(configuration, disable); - SetConfiguration(chromeNotification, Context.Configuration.Settings.Service.DisableChromeNotifications); - SetConfiguration(easeOfAccess, Context.Configuration.Settings.Service.DisableEaseOfAccessOptions); - SetConfiguration(networkOptions, Context.Configuration.Settings.Service.DisableNetworkOptions); - SetConfiguration(passwordChange, Context.Configuration.Settings.Service.DisablePasswordChange); - SetConfiguration(powerOptions, Context.Configuration.Settings.Service.DisablePowerOptions); - SetConfiguration(remoteConnection, Context.Configuration.Settings.Service.DisableRemoteConnections); - SetConfiguration(signout, Context.Configuration.Settings.Service.DisableSignout); - SetConfiguration(taskManager, Context.Configuration.Settings.Service.DisableTaskManager); - SetConfiguration(userLock, Context.Configuration.Settings.Service.DisableUserLock); - SetConfiguration(userSwitch, Context.Configuration.Settings.Service.DisableUserSwitch); - SetConfiguration(vmwareOverlay, Context.Configuration.Settings.Service.DisableVmwareOverlay); - SetConfiguration(windowsUpdate, Context.Configuration.Settings.Service.DisableWindowsUpdate); + if (!success) + { + break; + } + } - logger.Info("Lockdown successful."); + if (success) + { + logger.Info("Lockdown successful."); + } + else + { + logger.Error("Lockdown was not successful!"); + } - return OperationResult.Success; + return success ? OperationResult.Success : OperationResult.Failed; } public override OperationResult Revert() @@ -72,32 +81,60 @@ namespace SafeExamBrowser.Service.Operations logger.Info($"Attempting to revert lockdown (feature configuration group: {groupId})..."); var configurations = backup.GetBy(groupId); + var success = true; foreach (var configuration in configurations) { - configuration.Restore(); - backup.Delete(configuration); + var restored = configuration.Restore(); + + if (restored) + { + backup.Delete(configuration); + } + else + { + logger.Error($"Failed to restore {configuration}!"); + success = false; + } } - logger.Info("Lockdown reversion successful."); + if (success) + { + logger.Info("Lockdown reversion successful."); + } + else + { + logger.Warn("Lockdown reversion was not successful!"); + } - return OperationResult.Success; + return success ? OperationResult.Success : OperationResult.Failed; } - private void SetConfiguration(IFeatureConfiguration configuration, bool disable) + private bool SetConfiguration(IFeatureConfiguration configuration, bool disable) { + var success = false; + backup.Save(configuration); if (disable) { - configuration.DisableFeature(); + success = configuration.DisableFeature(); } else { - configuration.EnableFeature(); + success = configuration.EnableFeature(); } - configuration.Monitor(); + if (success) + { + configuration.Monitor(); + } + else + { + logger.Error($"Failed to configure {configuration}!"); + } + + return success; } } } diff --git a/SafeExamBrowser.Service/Operations/SessionActivationOperation.cs b/SafeExamBrowser.Service/Operations/SessionActivationOperation.cs index b2834ace..df39f495 100644 --- a/SafeExamBrowser.Service/Operations/SessionActivationOperation.cs +++ b/SafeExamBrowser.Service/Operations/SessionActivationOperation.cs @@ -33,6 +33,8 @@ namespace SafeExamBrowser.Service.Operations logger.Error("Failed to inform runtime about new session activation!"); } + Context.IsRunning = success; + return success ? OperationResult.Success : OperationResult.Failed; } diff --git a/SafeExamBrowser.Service/Operations/SessionInitializationOperation.cs b/SafeExamBrowser.Service/Operations/SessionInitializationOperation.cs index 3fe4747e..096c7ae9 100644 --- a/SafeExamBrowser.Service/Operations/SessionInitializationOperation.cs +++ b/SafeExamBrowser.Service/Operations/SessionInitializationOperation.cs @@ -61,7 +61,7 @@ namespace SafeExamBrowser.Service.Operations logger.Info("Finalizing current session..."); - if (Context.ServiceEvent != null) + if (Context.ServiceEvent != null && Context.IsRunning) { success = Context.ServiceEvent.Set(); @@ -83,6 +83,7 @@ namespace SafeExamBrowser.Service.Operations logger.Info("Clearing session data..."); Context.Configuration = null; + Context.IsRunning = false; FinalizeSessionWriter(); @@ -107,7 +108,7 @@ namespace SafeExamBrowser.Service.Operations { sessionWriter = logWriterFactory.Invoke(Context.Configuration.AppConfig.ServiceLogFilePath); logger.Subscribe(sessionWriter); - logger.Debug($"Created session log file {Context.Configuration.AppConfig.ServiceLogFilePath}."); + logger.Debug($"Created session log file '{Context.Configuration.AppConfig.ServiceLogFilePath}'."); } private void FinalizeSessionWriter() diff --git a/SafeExamBrowser.Service/ServiceController.cs b/SafeExamBrowser.Service/ServiceController.cs index 62655d69..21fba95d 100644 --- a/SafeExamBrowser.Service/ServiceController.cs +++ b/SafeExamBrowser.Service/ServiceController.cs @@ -31,7 +31,7 @@ namespace SafeExamBrowser.Service private bool SessionIsRunning { - get { return Session != null; } + get { return sessionContext.IsRunning; } } public ServiceController( diff --git a/SafeExamBrowser.Service/SessionContext.cs b/SafeExamBrowser.Service/SessionContext.cs index 33ecb318..16b9d522 100644 --- a/SafeExamBrowser.Service/SessionContext.cs +++ b/SafeExamBrowser.Service/SessionContext.cs @@ -27,6 +27,11 @@ namespace SafeExamBrowser.Service /// internal ServiceConfiguration Configuration { get; set; } + /// + /// Indicates whether a session is currently running. + /// + internal bool IsRunning { get; set; } + /// /// The global inter-process event used for status synchronization with the runtime component. ///