SEBWIN-771: Implemented reconfiguration safeguard and refactored and moved reconfiguration and session locking to new coordinator module.
This commit is contained in:
parent
f3a9030505
commit
b48ef21708
9 changed files with 364 additions and 41 deletions
|
@ -14,6 +14,7 @@ using Moq;
|
||||||
using SafeExamBrowser.Applications.Contracts;
|
using SafeExamBrowser.Applications.Contracts;
|
||||||
using SafeExamBrowser.Browser.Contracts;
|
using SafeExamBrowser.Browser.Contracts;
|
||||||
using SafeExamBrowser.Browser.Contracts.Events;
|
using SafeExamBrowser.Browser.Contracts.Events;
|
||||||
|
using SafeExamBrowser.Client.Contracts;
|
||||||
using SafeExamBrowser.Client.Operations.Events;
|
using SafeExamBrowser.Client.Operations.Events;
|
||||||
using SafeExamBrowser.Communication.Contracts.Data;
|
using SafeExamBrowser.Communication.Contracts.Data;
|
||||||
using SafeExamBrowser.Communication.Contracts.Events;
|
using SafeExamBrowser.Communication.Contracts.Events;
|
||||||
|
@ -56,6 +57,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
private Mock<IBrowserApplication> browser;
|
private Mock<IBrowserApplication> browser;
|
||||||
private Mock<IClientHost> clientHost;
|
private Mock<IClientHost> clientHost;
|
||||||
private ClientContext context;
|
private ClientContext context;
|
||||||
|
private Mock<ICoordinator> coordinator;
|
||||||
private Mock<IDisplayMonitor> displayMonitor;
|
private Mock<IDisplayMonitor> displayMonitor;
|
||||||
private Mock<IExplorerShell> explorerShell;
|
private Mock<IExplorerShell> explorerShell;
|
||||||
private Mock<IFileSystemDialog> fileSystemDialog;
|
private Mock<IFileSystemDialog> fileSystemDialog;
|
||||||
|
@ -90,6 +92,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
browser = new Mock<IBrowserApplication>();
|
browser = new Mock<IBrowserApplication>();
|
||||||
clientHost = new Mock<IClientHost>();
|
clientHost = new Mock<IClientHost>();
|
||||||
context = new ClientContext();
|
context = new ClientContext();
|
||||||
|
coordinator = new Mock<ICoordinator>();
|
||||||
displayMonitor = new Mock<IDisplayMonitor>();
|
displayMonitor = new Mock<IDisplayMonitor>();
|
||||||
explorerShell = new Mock<IExplorerShell>();
|
explorerShell = new Mock<IExplorerShell>();
|
||||||
fileSystemDialog = new Mock<IFileSystemDialog>();
|
fileSystemDialog = new Mock<IFileSystemDialog>();
|
||||||
|
@ -120,6 +123,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
actionCenter.Object,
|
actionCenter.Object,
|
||||||
applicationMonitor.Object,
|
applicationMonitor.Object,
|
||||||
context,
|
context,
|
||||||
|
coordinator.Object,
|
||||||
displayMonitor.Object,
|
displayMonitor.Object,
|
||||||
explorerShell.Object,
|
explorerShell.Object,
|
||||||
fileSystemDialog.Object,
|
fileSystemDialog.Object,
|
||||||
|
@ -729,13 +733,17 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
var args = new DownloadEventArgs();
|
var args = new DownloadEventArgs();
|
||||||
|
|
||||||
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
||||||
|
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
|
||||||
runtimeProxy.Setup(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>())).Returns(new CommunicationResult(true));
|
runtimeProxy.Setup(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>())).Returns(new CommunicationResult(true));
|
||||||
|
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
||||||
args.Callback(true, string.Empty);
|
args.Callback(true, string.Empty);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
|
||||||
|
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
||||||
|
|
||||||
Assert.IsTrue(args.AllowDownload);
|
Assert.IsTrue(args.AllowDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -752,7 +760,30 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Never);
|
||||||
|
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
||||||
|
|
||||||
|
Assert.IsFalse(args.AllowDownload);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Reconfiguration_MustNotAllowConcurrentExecution()
|
||||||
|
{
|
||||||
|
var args = new DownloadEventArgs();
|
||||||
|
|
||||||
|
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
||||||
|
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(false);
|
||||||
|
runtimeProxy.Setup(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>())).Returns(new CommunicationResult(true));
|
||||||
|
|
||||||
|
sut.TryStart();
|
||||||
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
||||||
|
args.Callback?.Invoke(true, string.Empty);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
|
||||||
|
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
|
||||||
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
||||||
|
|
||||||
Assert.IsFalse(args.AllowDownload);
|
Assert.IsFalse(args.AllowDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -762,6 +793,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
var args = new DownloadEventArgs { Url = "sebs://www.somehost.org/some/path/some_configuration.seb?query=123" };
|
var args = new DownloadEventArgs { Url = "sebs://www.somehost.org/some/path/some_configuration.seb?query=123" };
|
||||||
|
|
||||||
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
||||||
|
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
|
||||||
settings.Security.AllowReconfiguration = true;
|
settings.Security.AllowReconfiguration = true;
|
||||||
settings.Security.QuitPasswordHash = "abc123";
|
settings.Security.QuitPasswordHash = "abc123";
|
||||||
settings.Security.ReconfigurationUrl = "sebs://www.somehost.org/some/path/*.seb?query=123";
|
settings.Security.ReconfigurationUrl = "sebs://www.somehost.org/some/path/*.seb?query=123";
|
||||||
|
@ -771,7 +803,10 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
||||||
args.Callback(true, string.Empty);
|
args.Callback(true, string.Empty);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
|
||||||
|
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
||||||
|
|
||||||
Assert.IsTrue(args.AllowDownload);
|
Assert.IsTrue(args.AllowDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -786,7 +821,10 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Never);
|
||||||
|
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
||||||
|
|
||||||
Assert.IsFalse(args.AllowDownload);
|
Assert.IsFalse(args.AllowDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -802,7 +840,10 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Never);
|
||||||
|
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
||||||
|
|
||||||
Assert.IsFalse(args.AllowDownload);
|
Assert.IsFalse(args.AllowDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -815,7 +856,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
var args = new DownloadEventArgs();
|
var args = new DownloadEventArgs();
|
||||||
|
|
||||||
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
||||||
settings.Security.AllowReconfiguration = true;
|
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
|
||||||
messageBox.Setup(m => m.Show(
|
messageBox.Setup(m => m.Show(
|
||||||
It.IsAny<TextKey>(),
|
It.IsAny<TextKey>(),
|
||||||
It.IsAny<TextKey>(),
|
It.IsAny<TextKey>(),
|
||||||
|
@ -825,11 +866,14 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
runtimeProxy.Setup(r => r.RequestReconfiguration(
|
runtimeProxy.Setup(r => r.RequestReconfiguration(
|
||||||
It.Is<string>(p => p == downloadPath),
|
It.Is<string>(p => p == downloadPath),
|
||||||
It.Is<string>(u => u == downloadUrl))).Returns(new CommunicationResult(true));
|
It.Is<string>(u => u == downloadUrl))).Returns(new CommunicationResult(true));
|
||||||
|
settings.Security.AllowReconfiguration = true;
|
||||||
|
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||||
args.Callback(true, downloadUrl, downloadPath);
|
args.Callback(true, downloadUrl, downloadPath);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
|
||||||
|
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath), It.Is<string>(u => u == downloadUrl)), Times.Once);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath), It.Is<string>(u => u == downloadUrl)), Times.Once);
|
||||||
|
|
||||||
Assert.AreEqual(downloadPath, args.DownloadPath);
|
Assert.AreEqual(downloadPath, args.DownloadPath);
|
||||||
|
@ -845,7 +889,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
var args = new DownloadEventArgs();
|
var args = new DownloadEventArgs();
|
||||||
|
|
||||||
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
||||||
settings.Security.AllowReconfiguration = true;
|
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
|
||||||
messageBox.Setup(m => m.Show(
|
messageBox.Setup(m => m.Show(
|
||||||
It.IsAny<TextKey>(),
|
It.IsAny<TextKey>(),
|
||||||
It.IsAny<TextKey>(),
|
It.IsAny<TextKey>(),
|
||||||
|
@ -855,11 +899,14 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
runtimeProxy.Setup(r => r.RequestReconfiguration(
|
runtimeProxy.Setup(r => r.RequestReconfiguration(
|
||||||
It.Is<string>(p => p == downloadPath),
|
It.Is<string>(p => p == downloadPath),
|
||||||
It.Is<string>(u => u == downloadUrl))).Returns(new CommunicationResult(true));
|
It.Is<string>(u => u == downloadUrl))).Returns(new CommunicationResult(true));
|
||||||
|
settings.Security.AllowReconfiguration = true;
|
||||||
|
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||||
args.Callback(false, downloadPath);
|
args.Callback(false, downloadPath);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
|
||||||
|
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Once);
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -872,7 +919,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
var args = new DownloadEventArgs();
|
var args = new DownloadEventArgs();
|
||||||
|
|
||||||
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
|
||||||
settings.Security.AllowReconfiguration = true;
|
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
|
||||||
messageBox.Setup(m => m.Show(
|
messageBox.Setup(m => m.Show(
|
||||||
It.IsAny<TextKey>(),
|
It.IsAny<TextKey>(),
|
||||||
It.IsAny<TextKey>(),
|
It.IsAny<TextKey>(),
|
||||||
|
@ -882,18 +929,21 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
runtimeProxy.Setup(r => r.RequestReconfiguration(
|
runtimeProxy.Setup(r => r.RequestReconfiguration(
|
||||||
It.Is<string>(p => p == downloadPath),
|
It.Is<string>(p => p == downloadPath),
|
||||||
It.Is<string>(u => u == downloadUrl))).Returns(new CommunicationResult(false));
|
It.Is<string>(u => u == downloadUrl))).Returns(new CommunicationResult(false));
|
||||||
|
settings.Security.AllowReconfiguration = true;
|
||||||
|
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||||
args.Callback(true, downloadUrl, downloadPath);
|
args.Callback(true, downloadUrl, downloadPath);
|
||||||
|
|
||||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
|
||||||
|
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Once);
|
||||||
messageBox.Verify(m => m.Show(
|
messageBox.Verify(m => m.Show(
|
||||||
It.IsAny<TextKey>(),
|
It.IsAny<TextKey>(),
|
||||||
It.IsAny<TextKey>(),
|
It.IsAny<TextKey>(),
|
||||||
It.IsAny<MessageBoxAction>(),
|
It.IsAny<MessageBoxAction>(),
|
||||||
It.Is<MessageBoxIcon>(i => i == MessageBoxIcon.Error),
|
It.Is<MessageBoxIcon>(i => i == MessageBoxIcon.Error),
|
||||||
It.IsAny<IWindow>()), Times.Once);
|
It.IsAny<IWindow>()), Times.Once);
|
||||||
|
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -1231,8 +1281,9 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
{
|
{
|
||||||
var lockScreen = new Mock<ILockScreen>();
|
var lockScreen = new Mock<ILockScreen>();
|
||||||
|
|
||||||
settings.Service.IgnoreService = true;
|
coordinator.Setup(c => c.RequestSessionLock()).Returns(true);
|
||||||
lockScreen.Setup(l => l.WaitForResult()).Returns(new LockScreenResult());
|
lockScreen.Setup(l => l.WaitForResult()).Returns(new LockScreenResult());
|
||||||
|
settings.Service.IgnoreService = true;
|
||||||
uiFactory
|
uiFactory
|
||||||
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
|
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
|
||||||
.Returns(lockScreen.Object);
|
.Returns(lockScreen.Object);
|
||||||
|
@ -1240,6 +1291,8 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
systemMonitor.Raise(m => m.SessionChanged += null);
|
systemMonitor.Raise(m => m.SessionChanged += null);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestSessionLock(), Times.Once);
|
||||||
|
coordinator.Verify(c => c.ReleaseSessionLock(), Times.Once);
|
||||||
lockScreen.Verify(l => l.Show(), Times.Once);
|
lockScreen.Verify(l => l.Show(), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1249,9 +1302,10 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
var lockScreen = new Mock<ILockScreen>();
|
var lockScreen = new Mock<ILockScreen>();
|
||||||
var result = new LockScreenResult();
|
var result = new LockScreenResult();
|
||||||
|
|
||||||
settings.Service.IgnoreService = true;
|
coordinator.Setup(c => c.RequestSessionLock()).Returns(true);
|
||||||
lockScreen.Setup(l => l.WaitForResult()).Returns(result);
|
lockScreen.Setup(l => l.WaitForResult()).Returns(result);
|
||||||
runtimeProxy.Setup(r => r.RequestShutdown()).Returns(new CommunicationResult(true));
|
runtimeProxy.Setup(r => r.RequestShutdown()).Returns(new CommunicationResult(true));
|
||||||
|
settings.Service.IgnoreService = true;
|
||||||
uiFactory
|
uiFactory
|
||||||
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
|
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
|
||||||
.Callback(new Action<string, string, IEnumerable<LockScreenOption>, LockScreenSettings>((message, title, options, settings) => result.OptionId = options.Last().Id))
|
.Callback(new Action<string, string, IEnumerable<LockScreenOption>, LockScreenSettings>((message, title, options, settings) => result.OptionId = options.Last().Id))
|
||||||
|
@ -1260,6 +1314,8 @@ namespace SafeExamBrowser.Client.UnitTests
|
||||||
sut.TryStart();
|
sut.TryStart();
|
||||||
systemMonitor.Raise(m => m.SessionChanged += null);
|
systemMonitor.Raise(m => m.SessionChanged += null);
|
||||||
|
|
||||||
|
coordinator.Verify(c => c.RequestSessionLock(), Times.Once);
|
||||||
|
coordinator.Verify(c => c.ReleaseSessionLock(), Times.Once);
|
||||||
lockScreen.Verify(l => l.Show(), Times.Once);
|
lockScreen.Verify(l => l.Show(), Times.Once);
|
||||||
runtimeProxy.Verify(p => p.RequestShutdown(), Times.Once);
|
runtimeProxy.Verify(p => p.RequestShutdown(), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
118
SafeExamBrowser.Client.UnitTests/CoordinatorTests.cs
Normal file
118
SafeExamBrowser.Client.UnitTests/CoordinatorTests.cs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* 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.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Client.UnitTests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class CoordinatorTests
|
||||||
|
{
|
||||||
|
private Coordinator sut;
|
||||||
|
|
||||||
|
[TestInitialize]
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
sut = new Coordinator();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ReconfigurationLock_MustWorkCorrectly()
|
||||||
|
{
|
||||||
|
Assert.IsFalse(sut.IsReconfigurationLocked());
|
||||||
|
|
||||||
|
sut.RequestReconfigurationLock();
|
||||||
|
|
||||||
|
var result = Parallel.For(1, 1000, (_) =>
|
||||||
|
{
|
||||||
|
Assert.IsTrue(sut.IsReconfigurationLocked());
|
||||||
|
Assert.IsFalse(sut.RequestReconfigurationLock());
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.IsTrue(result.IsCompleted);
|
||||||
|
|
||||||
|
result = Parallel.For(1, 1000, (_) =>
|
||||||
|
{
|
||||||
|
sut.ReleaseReconfigurationLock();
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.IsFalse(sut.IsReconfigurationLocked());
|
||||||
|
Assert.IsTrue(result.IsCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void RequestReconfigurationLock_MustOnlyAllowLockingOnce()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
Assert.IsFalse(sut.IsReconfigurationLocked());
|
||||||
|
|
||||||
|
var result = Parallel.For(1, 1000, (_) =>
|
||||||
|
{
|
||||||
|
var acquired = sut.RequestReconfigurationLock();
|
||||||
|
|
||||||
|
if (acquired)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.AreEqual(1, count);
|
||||||
|
Assert.IsTrue(sut.IsReconfigurationLocked());
|
||||||
|
Assert.IsTrue(result.IsCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void RequestSessionLock_MustOnlyAllowLockingOnce()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
Assert.IsFalse(sut.IsSessionLocked());
|
||||||
|
|
||||||
|
var result = Parallel.For(1, 1000, (_) =>
|
||||||
|
{
|
||||||
|
var acquired = sut.RequestSessionLock();
|
||||||
|
|
||||||
|
if (acquired)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.AreEqual(1, count);
|
||||||
|
Assert.IsTrue(sut.IsSessionLocked());
|
||||||
|
Assert.IsTrue(result.IsCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SessionLock_MustWorkCorrectly()
|
||||||
|
{
|
||||||
|
Assert.IsFalse(sut.IsSessionLocked());
|
||||||
|
|
||||||
|
sut.RequestSessionLock();
|
||||||
|
|
||||||
|
var result = Parallel.For(1, 1000, (_) =>
|
||||||
|
{
|
||||||
|
Assert.IsTrue(sut.IsSessionLocked());
|
||||||
|
Assert.IsFalse(sut.RequestSessionLock());
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.IsTrue(result.IsCompleted);
|
||||||
|
|
||||||
|
result = Parallel.For(1, 1000, (_) =>
|
||||||
|
{
|
||||||
|
sut.ReleaseSessionLock();
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.IsFalse(sut.IsSessionLocked());
|
||||||
|
Assert.IsTrue(result.IsCompleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -166,6 +166,7 @@
|
||||||
<Compile Include="Operations\SystemMonitorOperationTests.cs" />
|
<Compile Include="Operations\SystemMonitorOperationTests.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="ClientControllerTests.cs" />
|
<Compile Include="ClientControllerTests.cs" />
|
||||||
|
<Compile Include="CoordinatorTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="app.config">
|
<None Include="app.config">
|
||||||
|
|
|
@ -16,6 +16,7 @@ using System.Threading.Tasks;
|
||||||
using SafeExamBrowser.Applications.Contracts;
|
using SafeExamBrowser.Applications.Contracts;
|
||||||
using SafeExamBrowser.Browser.Contracts;
|
using SafeExamBrowser.Browser.Contracts;
|
||||||
using SafeExamBrowser.Browser.Contracts.Events;
|
using SafeExamBrowser.Browser.Contracts.Events;
|
||||||
|
using SafeExamBrowser.Client.Contracts;
|
||||||
using SafeExamBrowser.Client.Operations.Events;
|
using SafeExamBrowser.Client.Operations.Events;
|
||||||
using SafeExamBrowser.Communication.Contracts.Data;
|
using SafeExamBrowser.Communication.Contracts.Data;
|
||||||
using SafeExamBrowser.Communication.Contracts.Events;
|
using SafeExamBrowser.Communication.Contracts.Events;
|
||||||
|
@ -53,6 +54,7 @@ namespace SafeExamBrowser.Client
|
||||||
private readonly IActionCenter actionCenter;
|
private readonly IActionCenter actionCenter;
|
||||||
private readonly IApplicationMonitor applicationMonitor;
|
private readonly IApplicationMonitor applicationMonitor;
|
||||||
private readonly ClientContext context;
|
private readonly ClientContext context;
|
||||||
|
private readonly ICoordinator coordinator;
|
||||||
private readonly IDisplayMonitor displayMonitor;
|
private readonly IDisplayMonitor displayMonitor;
|
||||||
private readonly IExplorerShell explorerShell;
|
private readonly IExplorerShell explorerShell;
|
||||||
private readonly IFileSystemDialog fileSystemDialog;
|
private readonly IFileSystemDialog fileSystemDialog;
|
||||||
|
@ -78,12 +80,12 @@ namespace SafeExamBrowser.Client
|
||||||
private AppSettings Settings => context.Settings;
|
private AppSettings Settings => context.Settings;
|
||||||
|
|
||||||
private ILockScreen lockScreen;
|
private ILockScreen lockScreen;
|
||||||
private bool sessionLocked;
|
|
||||||
|
|
||||||
internal ClientController(
|
internal ClientController(
|
||||||
IActionCenter actionCenter,
|
IActionCenter actionCenter,
|
||||||
IApplicationMonitor applicationMonitor,
|
IApplicationMonitor applicationMonitor,
|
||||||
ClientContext context,
|
ClientContext context,
|
||||||
|
ICoordinator coordinator,
|
||||||
IDisplayMonitor displayMonitor,
|
IDisplayMonitor displayMonitor,
|
||||||
IExplorerShell explorerShell,
|
IExplorerShell explorerShell,
|
||||||
IFileSystemDialog fileSystemDialog,
|
IFileSystemDialog fileSystemDialog,
|
||||||
|
@ -104,6 +106,7 @@ namespace SafeExamBrowser.Client
|
||||||
this.actionCenter = actionCenter;
|
this.actionCenter = actionCenter;
|
||||||
this.applicationMonitor = applicationMonitor;
|
this.applicationMonitor = applicationMonitor;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.coordinator = coordinator;
|
||||||
this.displayMonitor = displayMonitor;
|
this.displayMonitor = displayMonitor;
|
||||||
this.explorerShell = explorerShell;
|
this.explorerShell = explorerShell;
|
||||||
this.fileSystemDialog = fileSystemDialog;
|
this.fileSystemDialog = fileSystemDialog;
|
||||||
|
@ -487,6 +490,36 @@ namespace SafeExamBrowser.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Browser_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
|
private void Browser_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
|
||||||
|
{
|
||||||
|
args.AllowDownload = false;
|
||||||
|
|
||||||
|
if (IsAllowedToReconfigure(args.Url))
|
||||||
|
{
|
||||||
|
if (coordinator.RequestReconfigurationLock())
|
||||||
|
{
|
||||||
|
args.AllowDownload = true;
|
||||||
|
args.Callback = Browser_ConfigurationDownloadFinished;
|
||||||
|
args.DownloadPath = Path.Combine(context.AppConfig.TemporaryDirectory, fileName);
|
||||||
|
|
||||||
|
splashScreen.Show();
|
||||||
|
splashScreen.BringToForeground();
|
||||||
|
splashScreen.SetIndeterminate();
|
||||||
|
splashScreen.UpdateStatus(TextKey.OperationStatus_InitializeSession, true);
|
||||||
|
|
||||||
|
logger.Info($"Allowed download request for configuration file '{fileName}'.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Warn($"A reconfiguration is already in progress, denied download request for configuration file '{fileName}'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Info($"Reconfiguration is not allowed, denied download request for configuration file '{fileName}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsAllowedToReconfigure(string url)
|
||||||
{
|
{
|
||||||
var allow = false;
|
var allow = false;
|
||||||
var hasQuitPassword = !string.IsNullOrWhiteSpace(Settings.Security.QuitPasswordHash);
|
var hasQuitPassword = !string.IsNullOrWhiteSpace(Settings.Security.QuitPasswordHash);
|
||||||
|
@ -498,9 +531,9 @@ namespace SafeExamBrowser.Client
|
||||||
{
|
{
|
||||||
var expression = Regex.Escape(Settings.Security.ReconfigurationUrl).Replace(@"\*", ".*");
|
var expression = Regex.Escape(Settings.Security.ReconfigurationUrl).Replace(@"\*", ".*");
|
||||||
var regex = new Regex($"^{expression}$", RegexOptions.IgnoreCase);
|
var regex = new Regex($"^{expression}$", RegexOptions.IgnoreCase);
|
||||||
var sebUrl = args.Url.Replace(Uri.UriSchemeHttps, context.AppConfig.SebUriSchemeSecure).Replace(Uri.UriSchemeHttp, context.AppConfig.SebUriScheme);
|
var sebUrl = url.Replace(Uri.UriSchemeHttps, context.AppConfig.SebUriSchemeSecure).Replace(Uri.UriSchemeHttp, context.AppConfig.SebUriScheme);
|
||||||
|
|
||||||
allow = Settings.Security.AllowReconfiguration && (regex.IsMatch(args.Url) || regex.IsMatch(sebUrl));
|
allow = Settings.Security.AllowReconfiguration && (regex.IsMatch(url) || regex.IsMatch(sebUrl));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -512,24 +545,7 @@ namespace SafeExamBrowser.Client
|
||||||
allow = Settings.ConfigurationMode == ConfigurationMode.ConfigureClient || Settings.Security.AllowReconfiguration;
|
allow = Settings.ConfigurationMode == ConfigurationMode.ConfigureClient || Settings.Security.AllowReconfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allow)
|
return allow;
|
||||||
{
|
|
||||||
args.AllowDownload = true;
|
|
||||||
args.Callback = Browser_ConfigurationDownloadFinished;
|
|
||||||
args.DownloadPath = Path.Combine(context.AppConfig.TemporaryDirectory, fileName);
|
|
||||||
|
|
||||||
splashScreen.Show();
|
|
||||||
splashScreen.BringToForeground();
|
|
||||||
splashScreen.SetIndeterminate();
|
|
||||||
splashScreen.UpdateStatus(TextKey.OperationStatus_InitializeSession, true);
|
|
||||||
|
|
||||||
logger.Info($"Allowed download request for configuration file '{fileName}'.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
args.AllowDownload = false;
|
|
||||||
logger.Info($"Denied download request for configuration file '{fileName}'.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Browser_ConfigurationDownloadFinished(bool success, string url, string filePath = null)
|
private void Browser_ConfigurationDownloadFinished(bool success, string url, string filePath = null)
|
||||||
|
@ -547,15 +563,19 @@ namespace SafeExamBrowser.Client
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.Error($"Failed to communicate reconfiguration request for '{filePath}'!");
|
logger.Error($"Failed to communicate reconfiguration request for '{filePath}'!");
|
||||||
|
|
||||||
messageBox.Show(TextKey.MessageBox_ReconfigurationError, TextKey.MessageBox_ReconfigurationErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen);
|
messageBox.Show(TextKey.MessageBox_ReconfigurationError, TextKey.MessageBox_ReconfigurationErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen);
|
||||||
splashScreen.Hide();
|
splashScreen.Hide();
|
||||||
|
coordinator.ReleaseReconfigurationLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.Error($"Failed to download configuration file '{filePath}'!");
|
logger.Error($"Failed to download configuration file '{filePath}'!");
|
||||||
|
|
||||||
messageBox.Show(TextKey.MessageBox_ConfigurationDownloadError, TextKey.MessageBox_ConfigurationDownloadErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen);
|
messageBox.Show(TextKey.MessageBox_ConfigurationDownloadError, TextKey.MessageBox_ConfigurationDownloadErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen);
|
||||||
splashScreen.Hide();
|
splashScreen.Hide();
|
||||||
|
coordinator.ReleaseReconfigurationLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -643,6 +663,7 @@ namespace SafeExamBrowser.Client
|
||||||
{
|
{
|
||||||
logger.Info("The reconfiguration was aborted by the runtime.");
|
logger.Info("The reconfiguration was aborted by the runtime.");
|
||||||
splashScreen.Hide();
|
splashScreen.Hide();
|
||||||
|
coordinator.ReleaseReconfigurationLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClientHost_ReconfigurationDenied(ReconfigurationEventArgs args)
|
private void ClientHost_ReconfigurationDenied(ReconfigurationEventArgs args)
|
||||||
|
@ -650,6 +671,7 @@ namespace SafeExamBrowser.Client
|
||||||
logger.Info($"The reconfiguration request for '{args.ConfigurationPath}' was denied by the runtime!");
|
logger.Info($"The reconfiguration request for '{args.ConfigurationPath}' was denied by the runtime!");
|
||||||
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle, parent: splashScreen);
|
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle, parent: splashScreen);
|
||||||
splashScreen.Hide();
|
splashScreen.Hide();
|
||||||
|
coordinator.ReleaseReconfigurationLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClientHost_ServerFailureActionRequested(ServerFailureActionRequestEventArgs args)
|
private void ClientHost_ServerFailureActionRequested(ServerFailureActionRequestEventArgs args)
|
||||||
|
@ -776,14 +798,13 @@ namespace SafeExamBrowser.Client
|
||||||
{
|
{
|
||||||
logger.Warn($@"The cursor registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen...");
|
logger.Warn($@"The cursor registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen...");
|
||||||
|
|
||||||
if (!sessionLocked)
|
if (coordinator.RequestSessionLock())
|
||||||
{
|
{
|
||||||
var message = text.Get(TextKey.LockScreen_CursorMessage);
|
var message = text.Get(TextKey.LockScreen_CursorMessage);
|
||||||
var title = text.Get(TextKey.LockScreen_Title);
|
var title = text.Get(TextKey.LockScreen_Title);
|
||||||
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorContinueOption) };
|
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorContinueOption) };
|
||||||
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorTerminateOption) };
|
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorTerminateOption) };
|
||||||
|
|
||||||
sessionLocked = true;
|
|
||||||
registry.StopMonitoring(key, name);
|
registry.StopMonitoring(key, name);
|
||||||
|
|
||||||
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
|
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
|
||||||
|
@ -798,7 +819,7 @@ namespace SafeExamBrowser.Client
|
||||||
TryRequestShutdown();
|
TryRequestShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionLocked = false;
|
coordinator.ReleaseSessionLock();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -810,14 +831,13 @@ namespace SafeExamBrowser.Client
|
||||||
{
|
{
|
||||||
logger.Warn($@"The ease of access registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen...");
|
logger.Warn($@"The ease of access registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen...");
|
||||||
|
|
||||||
if (!sessionLocked)
|
if (coordinator.RequestSessionLock())
|
||||||
{
|
{
|
||||||
var message = text.Get(TextKey.LockScreen_EaseOfAccessMessage);
|
var message = text.Get(TextKey.LockScreen_EaseOfAccessMessage);
|
||||||
var title = text.Get(TextKey.LockScreen_Title);
|
var title = text.Get(TextKey.LockScreen_Title);
|
||||||
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_EaseOfAccessContinueOption) };
|
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_EaseOfAccessContinueOption) };
|
||||||
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_EaseOfAccessTerminateOption) };
|
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_EaseOfAccessTerminateOption) };
|
||||||
|
|
||||||
sessionLocked = true;
|
|
||||||
registry.StopMonitoring(key, name);
|
registry.StopMonitoring(key, name);
|
||||||
|
|
||||||
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
|
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
|
||||||
|
@ -832,7 +852,7 @@ namespace SafeExamBrowser.Client
|
||||||
TryRequestShutdown();
|
TryRequestShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionLocked = false;
|
coordinator.ReleaseSessionLock();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -843,8 +863,8 @@ namespace SafeExamBrowser.Client
|
||||||
private void Runtime_ConnectionLost()
|
private void Runtime_ConnectionLost()
|
||||||
{
|
{
|
||||||
logger.Error("Lost connection to the runtime!");
|
logger.Error("Lost connection to the runtime!");
|
||||||
messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error);
|
|
||||||
|
|
||||||
|
messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error);
|
||||||
shutdown.Invoke();
|
shutdown.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -858,11 +878,10 @@ namespace SafeExamBrowser.Client
|
||||||
{
|
{
|
||||||
logger.Info("Attempting to show lock screen as requested by the server...");
|
logger.Info("Attempting to show lock screen as requested by the server...");
|
||||||
|
|
||||||
if (!sessionLocked)
|
if (coordinator.RequestSessionLock())
|
||||||
{
|
{
|
||||||
sessionLocked = true;
|
|
||||||
ShowLockScreen(message, text.Get(TextKey.LockScreen_Title), Enumerable.Empty<LockScreenOption>());
|
ShowLockScreen(message, text.Get(TextKey.LockScreen_Title), Enumerable.Empty<LockScreenOption>());
|
||||||
sessionLocked = false;
|
coordinator.ReleaseSessionLock();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -900,10 +919,8 @@ namespace SafeExamBrowser.Client
|
||||||
{
|
{
|
||||||
logger.Warn("Detected user session change!");
|
logger.Warn("Detected user session change!");
|
||||||
|
|
||||||
if (!sessionLocked)
|
if (coordinator.RequestSessionLock())
|
||||||
{
|
{
|
||||||
sessionLocked = true;
|
|
||||||
|
|
||||||
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
|
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
|
||||||
|
|
||||||
if (result.OptionId == terminateOption.Id)
|
if (result.OptionId == terminateOption.Id)
|
||||||
|
@ -912,7 +929,7 @@ namespace SafeExamBrowser.Client
|
||||||
TryRequestShutdown();
|
TryRequestShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionLocked = false;
|
coordinator.ReleaseSessionLock();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -114,6 +114,7 @@ namespace SafeExamBrowser.Client
|
||||||
|
|
||||||
var applicationFactory = new ApplicationFactory(applicationMonitor, ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory, new Registry(ModuleLogger(nameof(Registry))));
|
var applicationFactory = new ApplicationFactory(applicationMonitor, ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory, new Registry(ModuleLogger(nameof(Registry))));
|
||||||
var clipboard = new Clipboard(ModuleLogger(nameof(Clipboard)), nativeMethods);
|
var clipboard = new Clipboard(ModuleLogger(nameof(Clipboard)), nativeMethods);
|
||||||
|
var coordinator = new Coordinator();
|
||||||
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
||||||
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
|
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
|
||||||
var fileSystemDialog = BuildFileSystemDialog();
|
var fileSystemDialog = BuildFileSystemDialog();
|
||||||
|
@ -148,6 +149,7 @@ namespace SafeExamBrowser.Client
|
||||||
actionCenter,
|
actionCenter,
|
||||||
applicationMonitor,
|
applicationMonitor,
|
||||||
context,
|
context,
|
||||||
|
coordinator,
|
||||||
displayMonitor,
|
displayMonitor,
|
||||||
explorerShell,
|
explorerShell,
|
||||||
fileSystemDialog,
|
fileSystemDialog,
|
||||||
|
|
46
SafeExamBrowser.Client/Contracts/ICoordinator.cs
Normal file
46
SafeExamBrowser.Client/Contracts/ICoordinator.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* 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.Client.Contracts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Coordinates concurrent operations of the client application.
|
||||||
|
/// </summary>
|
||||||
|
internal interface ICoordinator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the reconfiguration lock is currently occupied.
|
||||||
|
/// </summary>
|
||||||
|
bool IsReconfigurationLocked();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the session lock is currently occupied.
|
||||||
|
/// </summary>
|
||||||
|
bool IsSessionLocked();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases the reconfiguration lock.
|
||||||
|
/// </summary>
|
||||||
|
void ReleaseReconfigurationLock();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases the session lock.
|
||||||
|
/// </summary>
|
||||||
|
void ReleaseSessionLock();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to acquire the unique reconfiguration lock. Returns <c>true</c> if successful, otherwise <c>false</c>.
|
||||||
|
/// </summary>
|
||||||
|
bool RequestReconfigurationLock();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to acquire the unique session lock. Returns <c>true</c> if successful, otherwise <c>false</c>.
|
||||||
|
/// </summary>
|
||||||
|
bool RequestSessionLock();
|
||||||
|
}
|
||||||
|
}
|
78
SafeExamBrowser.Client/Coordinator.cs
Normal file
78
SafeExamBrowser.Client/Coordinator.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||||
|
*
|
||||||
|
* 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.Concurrent;
|
||||||
|
using SafeExamBrowser.Client.Contracts;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Client
|
||||||
|
{
|
||||||
|
internal class Coordinator : ICoordinator
|
||||||
|
{
|
||||||
|
private readonly ConcurrentBag<Guid> reconfiguration;
|
||||||
|
private readonly ConcurrentBag<Guid> session;
|
||||||
|
|
||||||
|
internal Coordinator()
|
||||||
|
{
|
||||||
|
reconfiguration = new ConcurrentBag<Guid>();
|
||||||
|
session = new ConcurrentBag<Guid>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReconfigurationLocked()
|
||||||
|
{
|
||||||
|
return !reconfiguration.IsEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSessionLocked()
|
||||||
|
{
|
||||||
|
return !session.IsEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReleaseReconfigurationLock()
|
||||||
|
{
|
||||||
|
reconfiguration.TryTake(out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReleaseSessionLock()
|
||||||
|
{
|
||||||
|
session.TryTake(out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RequestReconfigurationLock()
|
||||||
|
{
|
||||||
|
var acquired = false;
|
||||||
|
|
||||||
|
lock (reconfiguration)
|
||||||
|
{
|
||||||
|
if (reconfiguration.IsEmpty)
|
||||||
|
{
|
||||||
|
reconfiguration.Add(Guid.NewGuid());
|
||||||
|
acquired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acquired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RequestSessionLock()
|
||||||
|
{
|
||||||
|
var acquired = false;
|
||||||
|
|
||||||
|
lock (session)
|
||||||
|
{
|
||||||
|
if (session.IsEmpty)
|
||||||
|
{
|
||||||
|
session.Add(Guid.NewGuid());
|
||||||
|
acquired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acquired;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,9 @@ using System.Windows;
|
||||||
// to COM components. If you need to access a type in this assembly from
|
// to COM components. If you need to access a type in this assembly from
|
||||||
// COM, set the ComVisible attribute to true on that type.
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// Required for mocking internal contracts with Moq
|
||||||
|
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||||
[assembly: InternalsVisibleTo("SafeExamBrowser.Client.UnitTests")]
|
[assembly: InternalsVisibleTo("SafeExamBrowser.Client.UnitTests")]
|
||||||
|
|
||||||
//In order to begin building localizable applications, set
|
//In order to begin building localizable applications, set
|
||||||
|
|
|
@ -74,6 +74,8 @@
|
||||||
<Compile Include="App.cs" />
|
<Compile Include="App.cs" />
|
||||||
<Compile Include="ClientContext.cs" />
|
<Compile Include="ClientContext.cs" />
|
||||||
<Compile Include="ClientController.cs" />
|
<Compile Include="ClientController.cs" />
|
||||||
|
<Compile Include="Contracts\ICoordinator.cs" />
|
||||||
|
<Compile Include="Coordinator.cs" />
|
||||||
<Compile Include="Operations\ClientHostDisconnectionOperation.cs" />
|
<Compile Include="Operations\ClientHostDisconnectionOperation.cs" />
|
||||||
<Compile Include="Operations\ClientOperation.cs" />
|
<Compile Include="Operations\ClientOperation.cs" />
|
||||||
<Compile Include="Operations\ConfigurationOperation.cs" />
|
<Compile Include="Operations\ConfigurationOperation.cs" />
|
||||||
|
|
Loading…
Reference in a new issue