diff --git a/SafeExamBrowser.Contracts/Lockdown/FeatureConfigurationStatus.cs b/SafeExamBrowser.Contracts/Lockdown/FeatureConfigurationStatus.cs
new file mode 100644
index 00000000..5e6ea694
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Lockdown/FeatureConfigurationStatus.cs
@@ -0,0 +1,31 @@
+/*
+ * 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.Contracts.Lockdown
+{
+ ///
+ /// Defines all possible states of an .
+ ///
+ public enum FeatureConfigurationStatus
+ {
+ ///
+ /// The configuration is in an undefined state.
+ ///
+ Undefined,
+
+ ///
+ /// The configuration is disabled.
+ ///
+ Disabled,
+
+ ///
+ /// The configuration is enabled.
+ ///
+ Enabled
+ }
+}
diff --git a/SafeExamBrowser.Contracts/Lockdown/IFeatureConfiguration.cs b/SafeExamBrowser.Contracts/Lockdown/IFeatureConfiguration.cs
index f525d062..d2153b4d 100644
--- a/SafeExamBrowser.Contracts/Lockdown/IFeatureConfiguration.cs
+++ b/SafeExamBrowser.Contracts/Lockdown/IFeatureConfiguration.cs
@@ -35,16 +35,16 @@ namespace SafeExamBrowser.Contracts.Lockdown
///
bool EnableFeature();
+ ///
+ /// Retrieves the current status of the configuration.
+ ///
+ FeatureConfigurationStatus GetStatus();
+
///
/// Initializes the currently active configuration of the feature.
///
void Initialize();
- ///
- /// Starts monitoring the feature to ensure that it remains as currently configured.
- ///
- void Monitor();
-
///
/// Restores the feature to its previous configuration (i.e. before it was enabled or disabled). Returns true if successful,
/// otherwise false.
diff --git a/SafeExamBrowser.Contracts/Lockdown/IFeatureConfigurationMonitor.cs b/SafeExamBrowser.Contracts/Lockdown/IFeatureConfigurationMonitor.cs
new file mode 100644
index 00000000..8c7e97f1
--- /dev/null
+++ b/SafeExamBrowser.Contracts/Lockdown/IFeatureConfigurationMonitor.cs
@@ -0,0 +1,31 @@
+/*
+ * 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.Contracts.Lockdown
+{
+ ///
+ /// Provides functionality to ensure that one or more remain in a given .
+ ///
+ public interface IFeatureConfigurationMonitor
+ {
+ ///
+ /// Registers a configuration to be monitored for the given status.
+ ///
+ void Observe(IFeatureConfiguration configuration, FeatureConfigurationStatus status);
+
+ ///
+ /// Stops the monitoring activity and removes all observed configurations.
+ ///
+ void Reset();
+
+ ///
+ /// Starts the monitoring activity.
+ ///
+ void Start();
+ }
+}
diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
index 6a6f9df0..258ebc73 100644
--- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
+++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj
@@ -92,10 +92,12 @@
+
+
diff --git a/SafeExamBrowser.Lockdown.UnitTests/AutoRestoreMechanismTests.cs b/SafeExamBrowser.Lockdown.UnitTests/AutoRestoreMechanismTests.cs
index 15d20107..7295bbb1 100644
--- a/SafeExamBrowser.Lockdown.UnitTests/AutoRestoreMechanismTests.cs
+++ b/SafeExamBrowser.Lockdown.UnitTests/AutoRestoreMechanismTests.cs
@@ -89,7 +89,7 @@ namespace SafeExamBrowser.Lockdown.UnitTests
sync.WaitOne();
sut.Stop();
- backup.Verify(b => b.GetAllConfigurations(), Times.Exactly(limit + 1));
+ 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));
systemConfigurationUpdate.Verify(u => u.Execute(), Times.Never);
@@ -145,7 +145,7 @@ namespace SafeExamBrowser.Lockdown.UnitTests
Thread.Sleep(25);
sut.Stop();
- backup.Verify(b => b.GetAllConfigurations(), Times.Between(counter, counter + 1, Range.Inclusive));
+ backup.Verify(b => b.GetAllConfigurations(), Times.Exactly(counter));
}
[TestMethod]
diff --git a/SafeExamBrowser.Lockdown.UnitTests/FeatureConfigurationMonitorTests.cs b/SafeExamBrowser.Lockdown.UnitTests/FeatureConfigurationMonitorTests.cs
new file mode 100644
index 00000000..dcfac3ba
--- /dev/null
+++ b/SafeExamBrowser.Lockdown.UnitTests/FeatureConfigurationMonitorTests.cs
@@ -0,0 +1,196 @@
+/*
+ * 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.Threading;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Moq;
+using SafeExamBrowser.Contracts.Lockdown;
+using SafeExamBrowser.Contracts.Logging;
+
+namespace SafeExamBrowser.Lockdown.UnitTests
+{
+ [TestClass]
+ public class FeatureConfigurationMonitorTests
+ {
+ private Mock logger;
+ private FeatureConfigurationMonitor sut;
+
+ [TestInitialize]
+ public void Initialize()
+ {
+ logger = new Mock();
+ sut = new FeatureConfigurationMonitor(logger.Object, 0);
+ }
+
+ [TestMethod]
+ public void MustEnforceConfigurations()
+ {
+ var configuration1 = new Mock();
+ var configuration2 = new Mock();
+ var configuration3 = new Mock();
+ var counter = 0;
+ var limit = new Random().Next(5, 50);
+ var sync = new AutoResetEvent(false);
+
+ configuration1.Setup(c => c.GetStatus()).Returns(FeatureConfigurationStatus.Disabled);
+ configuration2.Setup(c => c.GetStatus()).Returns(FeatureConfigurationStatus.Enabled);
+ configuration3.Setup(c => c.GetStatus()).Returns(FeatureConfigurationStatus.Disabled).Callback(() =>
+ {
+ if (++counter >= limit)
+ {
+ sync.Set();
+ }
+ });
+
+ sut = new FeatureConfigurationMonitor(logger.Object, 2);
+
+ sut.Observe(configuration1.Object, FeatureConfigurationStatus.Enabled);
+ sut.Observe(configuration2.Object, FeatureConfigurationStatus.Disabled);
+ sut.Observe(configuration3.Object, FeatureConfigurationStatus.Undefined);
+ sut.Start();
+ sync.WaitOne();
+ sut.Reset();
+
+ configuration1.Verify(c => c.EnableFeature(), Times.Exactly(limit));
+ configuration2.Verify(c => c.DisableFeature(), Times.Exactly(limit));
+ configuration3.Verify(c => c.DisableFeature(), Times.Never);
+ configuration3.Verify(c => c.EnableFeature(), Times.Never);
+ }
+
+ [TestMethod]
+ public void MustExecuteAsynchronously()
+ {
+ var configuration = new Mock();
+ var sync = new AutoResetEvent(false);
+ var threadId = Thread.CurrentThread.ManagedThreadId;
+
+ configuration.Setup(c => c.GetStatus()).Callback(() => { threadId = Thread.CurrentThread.ManagedThreadId; sync.Set(); });
+
+ sut.Observe(configuration.Object, FeatureConfigurationStatus.Disabled);
+ sut.Start();
+ sync.WaitOne();
+ sut.Reset();
+
+ Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
+ }
+
+ [TestMethod]
+ public void MustNotStartMultipleTimes()
+ {
+ var configuration = new Mock();
+ var counter = 0;
+ var sync = new AutoResetEvent(false);
+
+ configuration.Setup(c => c.GetStatus()).Returns(() =>
+ {
+ counter++;
+ Thread.Sleep(50);
+ sync.Set();
+ sut.Reset();
+
+ return FeatureConfigurationStatus.Disabled;
+ });
+
+ sut.Observe(configuration.Object, FeatureConfigurationStatus.Enabled);
+ sut.Start();
+ sut.Start();
+ sut.Start();
+ sut.Start();
+ sut.Start();
+ sut.Start();
+ sut.Start();
+ sync.WaitOne();
+ sut.Reset();
+
+ Assert.AreEqual(1, counter);
+ }
+
+ [TestMethod]
+ public void MustRespectTimeout()
+ {
+ const int TIMEOUT = 50;
+
+ var after = default(DateTime);
+ var before = default(DateTime);
+ var configuration = new Mock();
+ var counter = 0;
+ var sync = new AutoResetEvent(false);
+
+ sut = new FeatureConfigurationMonitor(logger.Object, TIMEOUT);
+
+ configuration.Setup(c => c.GetStatus()).Returns(FeatureConfigurationStatus.Undefined).Callback(() =>
+ {
+ switch (++counter)
+ {
+ case 1:
+ before = DateTime.Now;
+ break;
+ case 2:
+ after = DateTime.Now;
+ sync.Set();
+ break;
+ }
+ });
+
+ sut.Observe(configuration.Object, FeatureConfigurationStatus.Disabled);
+ sut.Start();
+ sync.WaitOne();
+ sut.Reset();
+
+ Assert.IsTrue(after - before >= new TimeSpan(0, 0, 0, 0, TIMEOUT));
+ }
+
+ [TestMethod]
+ public void MustStopWhenReset()
+ {
+ var configuration = new Mock();
+ var counter = 0;
+
+ configuration.Setup(c => c.GetStatus()).Returns(FeatureConfigurationStatus.Disabled).Callback(() => counter++);
+
+ sut.Observe(configuration.Object, FeatureConfigurationStatus.Enabled);
+ sut.Start();
+ Thread.Sleep(10);
+ sut.Reset();
+ Thread.Sleep(10);
+
+ configuration.Verify(c => c.GetStatus(), Times.Exactly(counter));
+ }
+
+ [TestMethod]
+ public void MustRemoveConfigurationsWhenReset()
+ {
+ var configuration = new Mock();
+ var counter = 0;
+
+ configuration.Setup(c => c.GetStatus()).Returns(FeatureConfigurationStatus.Disabled).Callback(() => counter++);
+
+ sut.Observe(configuration.Object, FeatureConfigurationStatus.Enabled);
+ sut.Start();
+ Thread.Sleep(10);
+ sut.Reset();
+
+ configuration.Verify(c => c.GetStatus(), Times.Exactly(counter));
+ configuration.Reset();
+
+ sut.Start();
+ Thread.Sleep(10);
+ sut.Reset();
+
+ configuration.Verify(c => c.GetStatus(), Times.Never);
+ }
+
+ [TestMethod]
+ [ExpectedException(typeof(ArgumentException))]
+ public void MustValidateTimeout()
+ {
+ new FeatureConfigurationMonitor(logger.Object, new Random().Next(int.MinValue, -1));
+ }
+ }
+}
diff --git a/SafeExamBrowser.Lockdown.UnitTests/FeatureConfigurationStub.cs b/SafeExamBrowser.Lockdown.UnitTests/FeatureConfigurationStub.cs
index b63ebdc3..9339a815 100644
--- a/SafeExamBrowser.Lockdown.UnitTests/FeatureConfigurationStub.cs
+++ b/SafeExamBrowser.Lockdown.UnitTests/FeatureConfigurationStub.cs
@@ -32,12 +32,12 @@ namespace SafeExamBrowser.Lockdown.UnitTests
throw new NotImplementedException();
}
- public void Initialize()
+ public FeatureConfigurationStatus GetStatus()
{
throw new NotImplementedException();
}
- public void Monitor()
+ public void Initialize()
{
throw new NotImplementedException();
}
diff --git a/SafeExamBrowser.Lockdown.UnitTests/SafeExamBrowser.Lockdown.UnitTests.csproj b/SafeExamBrowser.Lockdown.UnitTests/SafeExamBrowser.Lockdown.UnitTests.csproj
index 4f7a1751..86494b2d 100644
--- a/SafeExamBrowser.Lockdown.UnitTests/SafeExamBrowser.Lockdown.UnitTests.csproj
+++ b/SafeExamBrowser.Lockdown.UnitTests/SafeExamBrowser.Lockdown.UnitTests.csproj
@@ -81,6 +81,7 @@
+
diff --git a/SafeExamBrowser.Lockdown/AutoRestoreMechanism.cs b/SafeExamBrowser.Lockdown/AutoRestoreMechanism.cs
index 182f70ec..fa0f4012 100644
--- a/SafeExamBrowser.Lockdown/AutoRestoreMechanism.cs
+++ b/SafeExamBrowser.Lockdown/AutoRestoreMechanism.cs
@@ -7,6 +7,7 @@
*/
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using SafeExamBrowser.Contracts.Lockdown;
@@ -48,7 +49,7 @@ namespace SafeExamBrowser.Lockdown
if (!running)
{
running = true;
- Task.Run(new Action(RestoreAll));
+ Task.Run(new Action(AutoRestore));
logger.Info("Started auto-restore mechanism.");
}
else
@@ -74,47 +75,31 @@ namespace SafeExamBrowser.Lockdown
}
}
- private void RestoreAll()
+ private void AutoRestore()
{
+ if (IsStopped())
+ {
+ return;
+ }
+
var configurations = backup.GetAllConfigurations();
- var all = configurations.Count;
- var restored = 0;
if (configurations.Any())
{
- logger.Info($"Attempting to restore {configurations.Count} items...");
+ var success = TryRestoreAll(configurations);
- foreach (var configuration in configurations)
- {
- var success = configuration.Restore();
-
- if (success)
- {
- backup.Delete(configuration);
- restored++;
- }
- else
- {
- logger.Warn($"Failed to restore {configuration}!");
- }
-
- lock (@lock)
- {
- if (!running)
- {
- logger.Info("Auto-restore mechanism was aborted.");
-
- return;
- }
- }
- }
-
- if (all == restored)
+ if (success == true)
{
systemConfigurationUpdate.ExecuteAsync();
}
-
- Task.Delay(timeout_ms).ContinueWith((_) => RestoreAll());
+ else if (success == false)
+ {
+ Task.Delay(timeout_ms).ContinueWith((_) => AutoRestore());
+ }
+ else
+ {
+ logger.Info("Auto-restore mechanism was aborted.");
+ }
}
else
{
@@ -125,5 +110,55 @@ namespace SafeExamBrowser.Lockdown
}
}
}
+
+ private bool IsStopped()
+ {
+ lock (@lock)
+ {
+ return !running;
+ }
+ }
+
+ private bool? TryRestoreAll(IList configurations)
+ {
+ var restored = 0;
+
+ logger.Info($"Attempting to restore {configurations.Count} items...");
+
+ foreach (var configuration in configurations)
+ {
+ var success = TryRestore(configuration);
+
+ if (success)
+ {
+ restored++;
+ }
+
+ if (IsStopped())
+ {
+ return null;
+ }
+ }
+
+ logger.Info($"Restored {restored} of {configurations.Count} items.");
+
+ return restored == configurations.Count;
+ }
+
+ private bool TryRestore(IFeatureConfiguration configuration)
+ {
+ var success = configuration.Restore();
+
+ if (success)
+ {
+ backup.Delete(configuration);
+ }
+ else
+ {
+ logger.Warn($"Failed to restore {configuration}!");
+ }
+
+ return success;
+ }
}
}
diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurationMonitor.cs b/SafeExamBrowser.Lockdown/FeatureConfigurationMonitor.cs
new file mode 100644
index 00000000..2d32c15b
--- /dev/null
+++ b/SafeExamBrowser.Lockdown/FeatureConfigurationMonitor.cs
@@ -0,0 +1,148 @@
+/*
+ * 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 System.Threading.Tasks;
+using SafeExamBrowser.Contracts.Lockdown;
+using SafeExamBrowser.Contracts.Logging;
+
+namespace SafeExamBrowser.Lockdown
+{
+ public class FeatureConfigurationMonitor : IFeatureConfigurationMonitor
+ {
+ private readonly object @lock = new object();
+
+ private IDictionary configurations;
+ private ILogger logger;
+ private readonly int timeout_ms;
+ private bool running;
+
+ public FeatureConfigurationMonitor(ILogger logger, int timeout_ms)
+ {
+ if (timeout_ms < 0)
+ {
+ throw new ArgumentException("Must be 0 or greater!", nameof(timeout_ms));
+ }
+
+ this.configurations = new Dictionary();
+ this.logger = logger;
+ this.timeout_ms = timeout_ms;
+ }
+
+ public void Observe(IFeatureConfiguration configuration, FeatureConfigurationStatus status)
+ {
+ lock (@lock)
+ {
+ configurations.Add(configuration, status);
+ }
+ }
+
+ public void Reset()
+ {
+ lock (@lock)
+ {
+ if (running)
+ {
+ running = false;
+ configurations.Clear();
+ logger.Info("Stopped monitoring.");
+ }
+ else
+ {
+ logger.Info("Monitoring is not running.");
+ }
+ }
+ }
+
+ public void Start()
+ {
+ lock (@lock)
+ {
+ if (!running)
+ {
+ running = true;
+ Task.Run(new Action(Monitor));
+ logger.Info("Started monitoring.");
+ }
+ else
+ {
+ logger.Info("Monitoring is already running.");
+ }
+ }
+ }
+
+ private void Monitor()
+ {
+ if (IsStopped())
+ {
+ return;
+ }
+
+ var configurations = new Dictionary(this.configurations);
+
+ logger.Debug($"Checking {configurations.Count} configurations...");
+
+ foreach (var item in configurations)
+ {
+ var configuration = item.Key;
+ var status = item.Value;
+
+ Enforce(configuration, status);
+
+ if (IsStopped())
+ {
+ logger.Info("Monitoring was aborted.");
+
+ return;
+ }
+ }
+
+ if (configurations.Any())
+ {
+ Task.Delay(timeout_ms).ContinueWith((_) => Monitor());
+ }
+ else
+ {
+ lock (@lock)
+ {
+ running = false;
+ logger.Info("Nothing to be monitored, stopped monitoring.");
+ }
+ }
+ }
+
+ private void Enforce(IFeatureConfiguration configuration, FeatureConfigurationStatus status)
+ {
+ var currentStatus = configuration.GetStatus();
+
+ if (currentStatus != status)
+ {
+ logger.Warn($"{configuration} is {currentStatus.ToString().ToLower()} instead of {status.ToString().ToLower()}!");
+
+ if (status == FeatureConfigurationStatus.Disabled)
+ {
+ configuration.DisableFeature();
+ }
+ else if (status == FeatureConfigurationStatus.Enabled)
+ {
+ configuration.EnableFeature();
+ }
+ }
+ }
+
+ private bool IsStopped()
+ {
+ lock (@lock)
+ {
+ return !running;
+ }
+ }
+ }
+}
diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/FeatureConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/FeatureConfiguration.cs
index 5272c155..c2b2ab3a 100644
--- a/SafeExamBrowser.Lockdown/FeatureConfigurations/FeatureConfiguration.cs
+++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/FeatureConfiguration.cs
@@ -31,8 +31,8 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
public abstract bool DisableFeature();
public abstract bool EnableFeature();
+ public abstract FeatureConfigurationStatus GetStatus();
public abstract void Initialize();
- public abstract void Monitor();
public abstract bool Restore();
public override string ToString()
diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/RegistryConfigurations/RegistryConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/RegistryConfigurations/RegistryConfiguration.cs
index 6c23dfcb..bb5f4d65 100644
--- a/SafeExamBrowser.Lockdown/FeatureConfigurations/RegistryConfigurations/RegistryConfiguration.cs
+++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/RegistryConfigurations/RegistryConfiguration.cs
@@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;
+using SafeExamBrowser.Contracts.Lockdown;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
@@ -88,9 +89,31 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations.RegistryConfigurations
}
}
- public override void Monitor()
+ public override FeatureConfigurationStatus GetStatus()
{
- // TODO!
+ var status = FeatureConfigurationStatus.Undefined;
+
+ foreach (var item in Items)
+ {
+ var current = ReadItem(item.Key, item.Value);
+
+ if (current.Data?.Equals(item.Disabled) == true && status != FeatureConfigurationStatus.Enabled)
+ {
+ status = FeatureConfigurationStatus.Disabled;
+ }
+ else if (current.Data?.Equals(item.Enabled) == true && status != FeatureConfigurationStatus.Disabled)
+ {
+ status = FeatureConfigurationStatus.Enabled;
+ }
+ else
+ {
+ status = FeatureConfigurationStatus.Undefined;
+
+ break;
+ }
+ }
+
+ return status;
}
public override bool Restore()
diff --git a/SafeExamBrowser.Lockdown/FeatureConfigurations/WindowsUpdateConfiguration.cs b/SafeExamBrowser.Lockdown/FeatureConfigurations/WindowsUpdateConfiguration.cs
index d4d0d6ca..56bbda17 100644
--- a/SafeExamBrowser.Lockdown/FeatureConfigurations/WindowsUpdateConfiguration.cs
+++ b/SafeExamBrowser.Lockdown/FeatureConfigurations/WindowsUpdateConfiguration.cs
@@ -7,6 +7,7 @@
*/
using System;
+using SafeExamBrowser.Contracts.Lockdown;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Lockdown.FeatureConfigurations
@@ -28,12 +29,12 @@ namespace SafeExamBrowser.Lockdown.FeatureConfigurations
return true;
}
- public override void Initialize()
+ public override FeatureConfigurationStatus GetStatus()
{
-
+ return FeatureConfigurationStatus.Undefined;
}
- public override void Monitor()
+ public override void Initialize()
{
}
diff --git a/SafeExamBrowser.Lockdown/SafeExamBrowser.Lockdown.csproj b/SafeExamBrowser.Lockdown/SafeExamBrowser.Lockdown.csproj
index 8ce3b927..2aa3e54c 100644
--- a/SafeExamBrowser.Lockdown/SafeExamBrowser.Lockdown.csproj
+++ b/SafeExamBrowser.Lockdown/SafeExamBrowser.Lockdown.csproj
@@ -56,6 +56,7 @@
+
diff --git a/SafeExamBrowser.Service.UnitTests/Operations/LockdownOperationTests.cs b/SafeExamBrowser.Service.UnitTests/Operations/LockdownOperationTests.cs
index aed98b29..7e419612 100644
--- a/SafeExamBrowser.Service.UnitTests/Operations/LockdownOperationTests.cs
+++ b/SafeExamBrowser.Service.UnitTests/Operations/LockdownOperationTests.cs
@@ -25,6 +25,7 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
{
private Mock backup;
private Mock factory;
+ private Mock monitor;
private Mock logger;
private Settings settings;
private SessionContext sessionContext;
@@ -35,6 +36,7 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
{
backup = new Mock();
factory = new Mock();
+ monitor = new Mock();
logger = new Mock();
settings = new Settings();
sessionContext = new SessionContext
@@ -42,7 +44,7 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
Configuration = new ServiceConfiguration { Settings = settings, UserName = "TestName", UserSid = "S-1-234-TEST" }
};
- sut = new LockdownOperation(backup.Object, factory.Object, logger.Object, sessionContext);
+ sut = new LockdownOperation(backup.Object, factory.Object, monitor.Object, logger.Object, sessionContext);
}
[TestMethod]
@@ -63,7 +65,9 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
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));
+ monitor.Verify(m => m.Observe(It.Is(c => c == configuration.Object), It.Is(s => s == FeatureConfigurationStatus.Disabled)), Times.Exactly(3));
+ monitor.Verify(m => m.Observe(It.Is(c => c == configuration.Object), It.Is(s => s == FeatureConfigurationStatus.Enabled)), Times.Exactly(count - 3));
+ monitor.Verify(m => m.Start(), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
@@ -114,7 +118,8 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
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));
+ monitor.Verify(m => m.Observe(It.Is(c => c == configuration.Object), It.Is(s => s == FeatureConfigurationStatus.Enabled)), Times.Exactly(count - offset - 1));
+ monitor.Verify(m => m.Start(), Times.Never);
Assert.AreEqual(OperationResult.Failed, result);
}
@@ -167,6 +172,8 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
configuration4.Verify(c => c.Initialize(), Times.Never);
configuration4.Verify(c => c.Restore(), Times.Once);
+ monitor.Verify(m => m.Reset(), Times.Once);
+
Assert.AreEqual(OperationResult.Success, result);
}
@@ -218,6 +225,8 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
configuration4.Verify(c => c.Initialize(), Times.Never);
configuration4.Verify(c => c.Restore(), Times.Once);
+ monitor.Verify(m => m.Reset(), Times.Once);
+
Assert.AreEqual(OperationResult.Failed, result);
}
}
diff --git a/SafeExamBrowser.Service/CompositionRoot.cs b/SafeExamBrowser.Service/CompositionRoot.cs
index 86e8798e..2ccda4c8 100644
--- a/SafeExamBrowser.Service/CompositionRoot.cs
+++ b/SafeExamBrowser.Service/CompositionRoot.cs
@@ -35,6 +35,7 @@ namespace SafeExamBrowser.Service
internal void BuildObjectGraph()
{
+ const int ONE_SECOND = 1000;
const int FIVE_SECONDS = 5000;
var backupFilePath = BuildBackupFilePath();
@@ -43,6 +44,7 @@ namespace SafeExamBrowser.Service
var featureBackup = new FeatureConfigurationBackup(backupFilePath, new ModuleLogger(logger, nameof(FeatureConfigurationBackup)));
var featureFactory = new FeatureConfigurationFactory(new ModuleLogger(logger, nameof(FeatureConfigurationFactory)));
+ var featureMonitor = new FeatureConfigurationMonitor(new ModuleLogger(logger, nameof(FeatureConfigurationMonitor)), ONE_SECOND);
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);
var sessionContext = new SessionContext();
@@ -58,7 +60,7 @@ namespace SafeExamBrowser.Service
bootstrapOperations.Enqueue(new ServiceEventCleanupOperation(logger, sessionContext));
sessionOperations.Enqueue(new SessionInitializationOperation(logger, ServiceEventFactory, sessionContext));
- sessionOperations.Enqueue(new LockdownOperation(featureBackup, featureFactory, logger, sessionContext));
+ sessionOperations.Enqueue(new LockdownOperation(featureBackup, featureFactory, featureMonitor, logger, sessionContext));
sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext));
var bootstrapSequence = new OperationSequence(logger, bootstrapOperations);
diff --git a/SafeExamBrowser.Service/Operations/LockdownOperation.cs b/SafeExamBrowser.Service/Operations/LockdownOperation.cs
index 382a8750..15fce865 100644
--- a/SafeExamBrowser.Service/Operations/LockdownOperation.cs
+++ b/SafeExamBrowser.Service/Operations/LockdownOperation.cs
@@ -17,17 +17,20 @@ namespace SafeExamBrowser.Service.Operations
{
private IFeatureConfigurationBackup backup;
private IFeatureConfigurationFactory factory;
+ private IFeatureConfigurationMonitor monitor;
private ILogger logger;
private Guid groupId;
public LockdownOperation(
IFeatureConfigurationBackup backup,
IFeatureConfigurationFactory factory,
+ IFeatureConfigurationMonitor monitor,
ILogger logger,
SessionContext sessionContext) : base(sessionContext)
{
this.backup = backup;
this.factory = factory;
+ this.monitor = monitor;
this.logger = logger;
}
@@ -58,7 +61,7 @@ namespace SafeExamBrowser.Service.Operations
foreach (var (configuration, disable) in configurations)
{
- success &= SetConfiguration(configuration, disable);
+ success &= TrySet(configuration, disable);
if (!success)
{
@@ -68,6 +71,7 @@ namespace SafeExamBrowser.Service.Operations
if (success)
{
+ monitor.Start();
logger.Info("Lockdown successful.");
}
else
@@ -85,19 +89,11 @@ namespace SafeExamBrowser.Service.Operations
var configurations = backup.GetBy(groupId);
var success = true;
+ monitor.Reset();
+
foreach (var configuration in configurations)
{
- var restored = configuration.Restore();
-
- if (restored)
- {
- backup.Delete(configuration);
- }
- else
- {
- logger.Error($"Failed to restore {configuration}!");
- success = false;
- }
+ success &= TryRestore(configuration);
}
if (success)
@@ -112,9 +108,26 @@ namespace SafeExamBrowser.Service.Operations
return success ? OperationResult.Success : OperationResult.Failed;
}
- private bool SetConfiguration(IFeatureConfiguration configuration, bool disable)
+ private bool TryRestore(IFeatureConfiguration configuration)
+ {
+ var success = configuration.Restore();
+
+ if (success)
+ {
+ backup.Delete(configuration);
+ }
+ else
+ {
+ logger.Error($"Failed to restore {configuration}!");
+ }
+
+ return success;
+ }
+
+ private bool TrySet(IFeatureConfiguration configuration, bool disable)
{
var success = false;
+ var status = FeatureConfigurationStatus.Undefined;
configuration.Initialize();
backup.Save(configuration);
@@ -122,15 +135,17 @@ namespace SafeExamBrowser.Service.Operations
if (disable)
{
success = configuration.DisableFeature();
+ status = FeatureConfigurationStatus.Disabled;
}
else
{
success = configuration.EnableFeature();
+ status = FeatureConfigurationStatus.Enabled;
}
if (success)
{
- configuration.Monitor();
+ monitor.Observe(configuration, status);
}
else
{