SEBWIN-301: Changed service procedure so that the service initiates a system configuration update on command from the runtime. Added functionality to terminate the application on service connection loss.

This commit is contained in:
dbuechel 2019-07-04 09:12:28 +02:00
parent 39b63218fb
commit 6f0b0d0fb2
21 changed files with 327 additions and 126 deletions

View file

@ -18,7 +18,7 @@ namespace SafeExamBrowser.Client.Communication
{
internal class ClientHost : BaseHost, IClientHost
{
private bool allowConnection = true;
private bool allowConnection;
private int processId;
public Guid AuthenticationToken { private get; set; }
@ -37,6 +37,7 @@ namespace SafeExamBrowser.Client.Communication
int processId,
int timeout_ms) : base(address, factory, logger, timeout_ms)
{
this.allowConnection = true;
this.processId = processId;
}

View file

@ -7,14 +7,14 @@
*/
using System;
using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Communication.Proxies;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Communication.UnitTests.Proxies
{
@ -39,18 +39,19 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
proxyObjectFactory = new Mock<IProxyObjectFactory>();
proxy = new Mock<IProxyObject>();
proxy.Setup(p => p.Connect(It.IsAny<Guid>())).Returns(response);
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxy.Setup(p => p.Connect(null)).Returns(response);
proxy.Setup(o => o.State).Returns(System.ServiceModel.CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
sut = new ServiceProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor));
sut.Connect();
}
[TestMethod]
public void MustIgnoreConnectIfUnavailable()
{
sut.Ignore = true;
sut.Connect(Guid.NewGuid());
sut.Connect();
proxy.Verify(p => p.Connect(It.IsAny<Guid>()), Times.Never);
}
@ -64,6 +65,61 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
proxy.Verify(p => p.Disconnect(It.IsAny<DisconnectionMessage>()), Times.Never);
}
[TestMethod]
public void MustCorrectlySendSystemConfigurationUpdate()
{
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.UpdateSystemConfiguration))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged));
var communication = sut.RunSystemConfigurationUpdate();
proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.UpdateSystemConfiguration)), Times.Once);
Assert.IsTrue(communication.Success);
}
[TestMethod]
public void MustFailIfSystemConfigurationUpdateNotAcknowledged()
{
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.UpdateSystemConfiguration))).Returns<Response>(null);
var communication = sut.RunSystemConfigurationUpdate();
Assert.IsFalse(communication.Success);
}
[TestMethod]
public void MustIgnoreSystemConfigurationUpdateIfUnavailable()
{
sut.Ignore = true;
sut.RunSystemConfigurationUpdate();
proxy.Verify(p => p.Send(It.IsAny<Message>()), Times.Never);
}
[TestMethod]
public void MustCorrectlyStartSession()
{
var configuration = new ServiceConfiguration { SessionId = Guid.NewGuid() };
proxy.Setup(p => p.Send(It.IsAny<SessionStartMessage>())).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged));
var communication = sut.StartSession(configuration);
proxy.Verify(p => p.Send(It.Is<SessionStartMessage>(m => m.Configuration.SessionId == configuration.SessionId)), Times.Once);
Assert.IsTrue(communication.Success);
}
[TestMethod]
public void MustFailIfSessionStartNotAcknowledged()
{
proxy.Setup(p => p.Send(It.IsAny<SessionStartMessage>())).Returns<Response>(null);
var communication = sut.StartSession(new ServiceConfiguration());
Assert.IsFalse(communication.Success);
}
[TestMethod]
public void MustIgnoreStartSessionIfUnavaiable()
{
@ -75,6 +131,30 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
proxy.Verify(p => p.Send(It.IsAny<Message>()), Times.Never);
}
[TestMethod]
public void MustCorrectlyStopSession()
{
var sessionId = Guid.NewGuid();
proxy.Setup(p => p.Send(It.IsAny<SessionStopMessage>())).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged));
var communication = sut.StopSession(sessionId);
proxy.Verify(p => p.Send(It.Is<SessionStopMessage>(m => m.SessionId == sessionId)), Times.Once);
Assert.IsTrue(communication.Success);
}
[TestMethod]
public void MustFailIfSessionStopNotAcknowledged()
{
proxy.Setup(p => p.Send(It.IsAny<SessionStopMessage>())).Returns<Response>(null);
var communication = sut.StopSession(Guid.Empty);
Assert.IsFalse(communication.Success);
}
[TestMethod]
public void MustIgnoreStopSessionIfUnavaiable()
{

View file

@ -46,6 +46,37 @@ namespace SafeExamBrowser.Communication.Proxies
return base.Disconnect();
}
public CommunicationResult RunSystemConfigurationUpdate()
{
if (IgnoreOperation(nameof(RunSystemConfigurationUpdate)))
{
return new CommunicationResult(true);
}
try
{
var response = Send(SimpleMessagePurport.UpdateSystemConfiguration);
var success = IsAcknowledged(response);
if (success)
{
Logger.Debug("Service acknowledged system configuration update.");
}
else
{
Logger.Error($"Service did not acknowledge system configuration update! Received: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(RunSystemConfigurationUpdate)}'", e);
return new CommunicationResult(false);
}
}
public CommunicationResult StartSession(ServiceConfiguration configuration)
{
if (IgnoreOperation(nameof(StartSession)))

View file

@ -44,6 +44,11 @@ namespace SafeExamBrowser.Contracts.Communication.Data
/// <summary>
/// Sent form the runtime to the client to command the latter to shut itself down.
/// </summary>
Shutdown
Shutdown,
/// <summary>
/// Sent from the runtime to the service to command the latter to update the system configuration.
/// </summary>
UpdateSystemConfiguration
}
}

View file

@ -15,11 +15,6 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// </summary>
public interface IServiceHost : ICommunicationHost
{
/// <summary>
/// Determines whether another application component may establish a connection with the host.
/// </summary>
bool AllowConnection { get; set; }
/// <summary>
/// Event fired when the runtime requested to start a new session.
/// </summary>
@ -29,5 +24,10 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// Event fired when the runtime requested to stop a running session.
/// </summary>
event CommunicationEventHandler<SessionStopEventArgs> SessionStopRequested;
/// <summary>
/// Event fired when the runtime requested to update the system configuration.
/// </summary>
event CommunicationEventHandler SystemConfigurationUpdateRequested;
}
}

View file

@ -22,6 +22,11 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// </summary>
bool Ignore { set; }
/// <summary>
/// Instructs the service to start a system configuration update.
/// </summary>
CommunicationResult RunSystemConfigurationUpdate();
/// <summary>
/// Instructs the service to start a new session according to the given configuration.
/// </summary>

View file

@ -91,7 +91,6 @@ namespace SafeExamBrowser.Contracts.I18n
OperationStatus_StopWindowMonitoring,
OperationStatus_TerminateBrowser,
OperationStatus_TerminateShell,
OperationStatus_UpdateSystemConfiguration,
OperationStatus_WaitExplorerStartup,
OperationStatus_WaitExplorerTermination,
OperationStatus_WaitRuntimeDisconnection,

View file

@ -231,9 +231,6 @@
<Entry key="OperationStatus_TerminateShell">
Terminating user interface
</Entry>
<Entry key="OperationStatus_UpdateSystemConfiguration">
Updating system configuration
</Entry>
<Entry key="OperationStatus_WaitExplorerStartup">
Waiting for Windows explorer to start up
</Entry>

View file

@ -15,7 +15,6 @@ using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Lockdown;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.SystemComponents;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
@ -35,7 +34,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
private SessionConfiguration session;
private SessionContext sessionContext;
private Settings settings;
private Mock<ISystemConfigurationUpdate> systemConfigurationUpdate;
private Mock<IUserInfo> userInfo;
private ServiceOperation sut;
@ -52,7 +50,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
session = new SessionConfiguration();
sessionContext = new SessionContext();
settings = new Settings();
systemConfigurationUpdate = new Mock<ISystemConfigurationUpdate>();
userInfo = new Mock<IUserInfo>();
appConfig.ServiceEventName = serviceEventName;
@ -63,7 +60,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
session.Settings = settings;
settings.Service.Policy = ServicePolicy.Mandatory;
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, systemConfigurationUpdate.Object, 0, userInfo.Object);
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, 0, userInfo.Object);
}
[TestMethod]
@ -92,7 +89,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var result = sut.Perform();
service.Verify(s => s.StartSession(It.IsAny<ServiceConfiguration>()), Times.Once);
systemConfigurationUpdate.Verify(u => u.Execute(), Times.Once);
service.Verify(s => s.RunSystemConfigurationUpdate(), Times.Never);
userInfo.Verify(u => u.GetUserName(), Times.Once);
userInfo.Verify(u => u.GetUserSid(), Times.Once);
@ -125,7 +122,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
service.Setup(s => s.Connect(null, true)).Returns(true);
service.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>())).Returns(new CommunicationResult(true));
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, systemConfigurationUpdate.Object, TIMEOUT, userInfo.Object);
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, TIMEOUT, userInfo.Object);
before = DateTime.Now;
var result = sut.Perform();
@ -158,7 +155,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var result = sut.Perform();
service.Verify(s => s.StartSession(It.IsAny<ServiceConfiguration>()), Times.Once);
systemConfigurationUpdate.Verify(u => u.Execute(), Times.Never);
Assert.AreEqual(OperationResult.Failed, result);
}
@ -213,7 +209,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
PerformNormally();
systemConfigurationUpdate.Reset();
var result = sut.Repeat();
@ -221,7 +216,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.StartSession(It.IsAny<ServiceConfiguration>()), Times.Exactly(2));
service.Verify(s => s.Disconnect(), Times.Never);
systemConfigurationUpdate.Verify(u => u.Execute(), Times.Exactly(2));
service.Verify(s => s.RunSystemConfigurationUpdate(), Times.Never);
Assert.AreEqual(OperationResult.Success, result);
}
@ -268,7 +263,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var before = default(DateTime);
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true));
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, systemConfigurationUpdate.Object, TIMEOUT, userInfo.Object);
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, TIMEOUT, userInfo.Object);
PerformNormally();
@ -288,6 +283,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
service.Setup(s => s.Disconnect()).Returns(true);
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
service.Setup(s => s.RunSystemConfigurationUpdate()).Returns(new CommunicationResult(true));
PerformNormally();
@ -303,14 +299,15 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
service.Setup(s => s.Disconnect()).Returns(true);
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
service.Setup(s => s.RunSystemConfigurationUpdate()).Returns(new CommunicationResult(true));
PerformNormally();
systemConfigurationUpdate.Reset();
var result = sut.Revert();
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.Disconnect(), Times.Once);
systemConfigurationUpdate.Verify(u => u.Execute(), Times.Once);
service.Verify(s => s.RunSystemConfigurationUpdate(), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
@ -319,14 +316,31 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
public void Revert_MustHandleCommunicationFailureWhenStoppingSession()
{
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(false));
PerformNormally();
systemConfigurationUpdate.Reset();
var result = sut.Revert();
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.Disconnect(), Times.Once);
systemConfigurationUpdate.Verify(u => u.Execute(), Times.Never);
service.Verify(s => s.RunSystemConfigurationUpdate(), Times.Never);
Assert.AreEqual(OperationResult.Failed, result);
}
[TestMethod]
public void Revert_MustHandleCommunicationFailureWhenInitiatingSystemConfigurationUpdate()
{
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
service.Setup(s => s.RunSystemConfigurationUpdate()).Returns(new CommunicationResult(false));
PerformNormally();
var result = sut.Revert();
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.Disconnect(), Times.Once);
service.Verify(s => s.RunSystemConfigurationUpdate(), Times.Once);
Assert.AreEqual(OperationResult.Failed, result);
}
@ -340,6 +354,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var result = sut.Revert();
service.Verify(s => s.RunSystemConfigurationUpdate(), Times.Never);
Assert.AreEqual(OperationResult.Failed, result);
}
@ -352,7 +368,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var before = default(DateTime);
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true));
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, systemConfigurationUpdate.Object, TIMEOUT, userInfo.Object);
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, TIMEOUT, userInfo.Object);
PerformNormally();
@ -362,6 +378,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.Disconnect(), Times.Once);
service.Verify(s => s.RunSystemConfigurationUpdate(), Times.Never);
Assert.AreEqual(OperationResult.Failed, result);
Assert.IsTrue(after - before >= new TimeSpan(0, 0, 0, 0, TIMEOUT));

View file

@ -389,6 +389,42 @@ namespace SafeExamBrowser.Runtime.UnitTests
runtimeWindow.Verify(s => s.UpdateStatus(It.Is<TextKey>(k => k == key), It.IsAny<bool>()), Times.Once);
}
[TestMethod]
public void ServiceProxy_MustShutdownWhenConnectionLostAndMandatory()
{
currentSettings.Service.Policy = ServicePolicy.Mandatory;
StartSession();
service.Raise(c => c.ConnectionLost += null);
messageBox.Verify(m => m.Show(
It.IsAny<TextKey>(),
It.IsAny<TextKey>(),
It.IsAny<MessageBoxAction>(),
It.Is<MessageBoxIcon>(i => i == MessageBoxIcon.Error),
It.IsAny<IWindow>()), Times.Once);
sessionSequence.Verify(s => s.TryRevert(), Times.Once);
shutdown.Verify(s => s(), Times.Once);
}
[TestMethod]
public void ServiceProxy_MustNotShutdownWhenConnectionLostAndNotMandatory()
{
currentSettings.Service.Policy = ServicePolicy.Optional;
StartSession();
service.Raise(c => c.ConnectionLost += null);
messageBox.Verify(m => m.Show(
It.IsAny<TextKey>(),
It.IsAny<TextKey>(),
It.IsAny<MessageBoxAction>(),
It.Is<MessageBoxIcon>(i => i == MessageBoxIcon.Error),
It.IsAny<IWindow>()), Times.Never);
sessionSequence.Verify(s => s.TryRevert(), Times.Never);
shutdown.Verify(s => s(), Times.Never);
}
[TestMethod]
public void Shutdown_MustRevertSessionThenBootstrapSequence()
{

View file

@ -27,7 +27,6 @@ using SafeExamBrowser.Contracts.SystemComponents;
using SafeExamBrowser.Core.OperationModel;
using SafeExamBrowser.Core.Operations;
using SafeExamBrowser.I18n;
using SafeExamBrowser.Lockdown;
using SafeExamBrowser.Logging;
using SafeExamBrowser.Runtime.Communication;
using SafeExamBrowser.Runtime.Operations;
@ -71,7 +70,6 @@ namespace SafeExamBrowser.Runtime
var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS);
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
var sessionContext = new SessionContext();
var systemConfigurationUpdate = new SystemConfigurationUpdate(ModuleLogger(nameof(SystemConfigurationUpdate)));
var uiFactory = new UserInterfaceFactory(text);
var userInfo = new UserInfo();
@ -83,7 +81,7 @@ namespace SafeExamBrowser.Runtime
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost, sessionContext));
sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, new HashAlgorithm(), logger, sessionContext));
sessionOperations.Enqueue(new ServiceOperation(logger, runtimeHost, serviceProxy, sessionContext, systemConfigurationUpdate, THIRTY_SECONDS, userInfo));
sessionOperations.Enqueue(new ServiceOperation(logger, runtimeHost, serviceProxy, sessionContext, THIRTY_SECONDS, userInfo));
sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
sessionOperations.Enqueue(new KioskModeOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new ClientOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));

View file

@ -16,7 +16,6 @@ using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Lockdown;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.SystemComponents;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
@ -29,7 +28,6 @@ namespace SafeExamBrowser.Runtime.Operations
private ILogger logger;
private IRuntimeHost runtimeHost;
private IServiceProxy service;
private ISystemConfigurationUpdate systemConfigurationUpdate;
private int timeout_ms;
private IUserInfo userInfo;
@ -41,14 +39,12 @@ namespace SafeExamBrowser.Runtime.Operations
IRuntimeHost runtimeHost,
IServiceProxy service,
SessionContext sessionContext,
ISystemConfigurationUpdate systemConfigurationUpdate,
int timeout_ms,
IUserInfo userInfo) : base(sessionContext)
{
this.logger = logger;
this.runtimeHost = runtimeHost;
this.service = service;
this.systemConfigurationUpdate = systemConfigurationUpdate;
this.timeout_ms = timeout_ms;
this.userInfo = userInfo;
}
@ -103,7 +99,7 @@ namespace SafeExamBrowser.Runtime.Operations
{
if (Context.Current != null)
{
success &= TryStopSession();
success &= TryStopSession(true);
}
success &= TryTerminateConnection();
@ -192,21 +188,18 @@ namespace SafeExamBrowser.Runtime.Operations
{
logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!");
}
StatusChanged?.Invoke(TextKey.OperationStatus_UpdateSystemConfiguration);
systemConfigurationUpdate.Execute();
}
else
{
logger.Error("Failed to communicate session start to service!");
logger.Error("Failed to communicate session start command to service!");
}
return started;
}
private bool TryStopSession()
private bool TryStopSession(bool isFinalSession = false)
{
var stopped = false;
var success = false;
logger.Info("Stopping current service session...");
@ -214,9 +207,9 @@ namespace SafeExamBrowser.Runtime.Operations
if (communication.Success)
{
stopped = TryWaitForServiceEvent(Context.Current.AppConfig.ServiceEventName);
success = TryWaitForServiceEvent(Context.Current.AppConfig.ServiceEventName);
if (stopped)
if (success)
{
logger.Info("Successfully stopped service session.");
}
@ -224,16 +217,28 @@ namespace SafeExamBrowser.Runtime.Operations
{
logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!");
}
StatusChanged?.Invoke(TextKey.OperationStatus_UpdateSystemConfiguration);
systemConfigurationUpdate.Execute();
}
else
{
logger.Error("Failed to communicate session stop to service!");
logger.Error("Failed to communicate session stop command to service!");
}
return stopped;
if (success && isFinalSession)
{
communication = service.RunSystemConfigurationUpdate();
success = communication.Success;
if (communication.Success)
{
logger.Info("Instructed service to perform system configuration update.");
}
else
{
logger.Error("Failed to communicate system configuration update command to service!");
}
}
return success;
}
private bool TryWaitForServiceEvent(string eventName)

View file

@ -260,6 +260,7 @@ namespace SafeExamBrowser.Runtime
runtimeHost.ClientConfigurationNeeded += RuntimeHost_ClientConfigurationNeeded;
runtimeHost.ReconfigurationRequested += RuntimeHost_ReconfigurationRequested;
runtimeHost.ShutdownRequested += RuntimeHost_ShutdownRequested;
service.ConnectionLost += ServiceProxy_ConnectionLost;
}
private void DeregisterEvents()
@ -267,6 +268,7 @@ namespace SafeExamBrowser.Runtime
runtimeHost.ClientConfigurationNeeded -= RuntimeHost_ClientConfigurationNeeded;
runtimeHost.ReconfigurationRequested -= RuntimeHost_ReconfigurationRequested;
runtimeHost.ShutdownRequested -= RuntimeHost_ShutdownRequested;
service.ConnectionLost -= ServiceProxy_ConnectionLost;
}
private void RegisterSessionEvents()
@ -358,6 +360,21 @@ namespace SafeExamBrowser.Runtime
shutdown.Invoke();
}
private void ServiceProxy_ConnectionLost()
{
if (SessionIsRunning && Session.Settings.Service.Policy == ServicePolicy.Mandatory)
{
logger.Error("Lost connection to the service component!");
StopSession();
messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow);
shutdown.Invoke();
}
else
{
logger.Warn("Lost connection to the service component!");
}
}
private void SessionSequence_ActionRequired(ActionRequiredEventArgs args)
{
switch (args)

View file

@ -155,10 +155,6 @@
<Project>{10c62628-8e6a-45aa-9d97-339b119ad21d}</Project>
<Name>SafeExamBrowser.I18n</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Lockdown\SafeExamBrowser.Lockdown.csproj">
<Project>{386b6042-3e12-4753-9fc6-c88ea4f97030}</Project>
<Name>SafeExamBrowser.Lockdown</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Logging\SafeExamBrowser.Logging.csproj">
<Project>{e107026c-2011-4552-a7d8-3a0d37881df6}</Project>
<Name>SafeExamBrowser.Logging</Name>

View file

@ -48,7 +48,6 @@ namespace SafeExamBrowser.Service.UnitTests.Communication
var response3 = sut.Connect();
Assert.IsTrue(response.ConnectionEstablished);
Assert.IsFalse(sut.AllowConnection);
Assert.IsFalse(response2.ConnectionEstablished);
Assert.IsFalse(response3.ConnectionEstablished);
}
@ -59,15 +58,33 @@ namespace SafeExamBrowser.Service.UnitTests.Communication
var connect = sut.Connect();
var disconnect = sut.Disconnect(new DisconnectionMessage { CommunicationToken = connect.CommunicationToken.Value, Interlocutor = Interlocutor.Runtime });
Assert.IsTrue(sut.AllowConnection);
Assert.IsTrue(disconnect.ConnectionTerminated);
var connect2 = sut.Connect();
Assert.IsFalse(sut.AllowConnection);
Assert.IsTrue(connect2.ConnectionEstablished);
}
[TestMethod]
public void Send_MustHandleSystemConfigurationUpdate()
{
var sync = new AutoResetEvent(false);
var systemConfigurationUpdateRequested = false;
sut.SystemConfigurationUpdateRequested += () => { systemConfigurationUpdateRequested = true; sync.Set(); };
var token = sut.Connect().CommunicationToken.Value;
var message = new SimpleMessage(SimpleMessagePurport.UpdateSystemConfiguration) { CommunicationToken = token };
var response = sut.Send(message);
sync.WaitOne();
Assert.IsTrue(systemConfigurationUpdateRequested);
Assert.IsNotNull(response);
Assert.IsInstanceOfType(response, typeof(SimpleResponse));
Assert.AreEqual(SimpleResponsePurport.Acknowledged, (response as SimpleResponse)?.Purport);
}
[TestMethod]
public void Send_MustHandleSessionStartRequest()
{

View file

@ -11,7 +11,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
@ -26,7 +25,6 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
{
private Mock<IAutoRestoreMechanism> autoRestoreMechanism;
private Mock<ILogger> logger;
private Mock<IServiceHost> serviceHost;
private Mock<Func<string, EventWaitHandle>> serviceEventFactory;
private SessionContext sessionContext;
private SessionInitializationOperation sut;
@ -36,7 +34,6 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
{
autoRestoreMechanism = new Mock<IAutoRestoreMechanism>();
logger = new Mock<ILogger>();
serviceHost = new Mock<IServiceHost>();
serviceEventFactory = new Mock<Func<string, EventWaitHandle>>();
sessionContext = new SessionContext();
@ -48,17 +45,7 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
Settings = new Settings()
};
sut = new SessionInitializationOperation(logger.Object, serviceEventFactory.Object, serviceHost.Object, sessionContext);
}
[TestMethod]
public void Perform_MustDisableNewConnections()
{
var result = sut.Perform();
serviceHost.VerifySet(h => h.AllowConnection = false, Times.Once);
Assert.AreEqual(OperationResult.Success, result);
sut = new SessionInitializationOperation(logger.Object, serviceEventFactory.Object, sessionContext);
}
[TestMethod]
@ -97,17 +84,6 @@ namespace SafeExamBrowser.Service.UnitTests.Operations
Assert.IsNull(sessionContext.Configuration);
}
[TestMethod]
public void Revert_MustEnableNewConnections()
{
sessionContext.ServiceEvent = new EventStub();
var result = sut.Revert();
serviceHost.VerifySet(h => h.AllowConnection = true, Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Revert_MustSetServiceEvent()
{

View file

@ -7,6 +7,7 @@
*/
using System;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Communication.Events;
@ -14,6 +15,7 @@ using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Lockdown;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Service.UnitTests
@ -27,6 +29,7 @@ namespace SafeExamBrowser.Service.UnitTests
private SessionContext sessionContext;
private Mock<IOperationSequence> sessionSequence;
private Mock<IServiceHost> serviceHost;
private Mock<ISystemConfigurationUpdate> systemConfigurationUpdate;
private ServiceController sut;
[TestInitialize]
@ -38,10 +41,18 @@ namespace SafeExamBrowser.Service.UnitTests
sessionContext = new SessionContext();
sessionSequence = new Mock<IOperationSequence>();
serviceHost = new Mock<IServiceHost>();
systemConfigurationUpdate = new Mock<ISystemConfigurationUpdate>();
logWriterFactory.Setup(f => f.Invoke(It.IsAny<string>())).Returns(new Mock<ILogObserver>().Object);
sut = new ServiceController(logger.Object, logWriterFactory.Object, bootstrapSequence.Object, sessionSequence.Object, serviceHost.Object, sessionContext);
sut = new ServiceController(
logger.Object,
logWriterFactory.Object,
bootstrapSequence.Object,
sessionSequence.Object,
serviceHost.Object,
sessionContext,
systemConfigurationUpdate.Object);
}
[TestMethod]
@ -160,6 +171,23 @@ namespace SafeExamBrowser.Service.UnitTests
sessionSequence.Verify(s => s.TryRevert(), Times.Never);
}
[TestMethod]
public void Communication_MustStartSystemConfigurationUpdate()
{
var sync = new AutoResetEvent(false);
bootstrapSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success);
systemConfigurationUpdate.Setup(u => u.ExecuteAsync()).Callback(() => sync.Set());
sut.TryStart();
serviceHost.Raise(h => h.SystemConfigurationUpdateRequested += null);
sync.WaitOne();
systemConfigurationUpdate.Verify(u => u.Execute(), Times.Never);
systemConfigurationUpdate.Verify(u => u.ExecuteAsync(), Times.Once);
}
[TestMethod]
public void Start_MustOnlyPerformBootstrapSequence()
{

View file

@ -18,47 +18,34 @@ namespace SafeExamBrowser.Service.Communication
{
internal class ServiceHost : BaseHost, IServiceHost
{
private readonly object @lock = new object();
private bool allowConnection;
public bool AllowConnection
{
get { lock (@lock) { return allowConnection; } }
set { lock (@lock) { allowConnection = value; } }
}
public event CommunicationEventHandler<SessionStartEventArgs> SessionStartRequested;
public event CommunicationEventHandler<SessionStopEventArgs> SessionStopRequested;
public event CommunicationEventHandler SystemConfigurationUpdateRequested;
internal ServiceHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms)
{
AllowConnection = true;
allowConnection = true;
}
protected override bool OnConnect(Guid? token)
{
lock (@lock)
var allow = allowConnection;
if (allow)
{
var allow = AllowConnection;
if (allow)
{
AllowConnection = false;
}
return allow;
allowConnection = false;
}
return allow;
}
protected override void OnDisconnect(Interlocutor interlocutor)
{
if (interlocutor == Interlocutor.Runtime)
{
lock (@lock)
{
AllowConnection = true;
}
allowConnection = true;
}
}
@ -79,6 +66,13 @@ namespace SafeExamBrowser.Service.Communication
protected override Response OnReceive(SimpleMessagePurport message)
{
switch (message)
{
case SimpleMessagePurport.UpdateSystemConfiguration:
SystemConfigurationUpdateRequested?.InvokeAsync();
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
}
}

View file

@ -57,14 +57,14 @@ namespace SafeExamBrowser.Service
bootstrapOperations.Enqueue(new CommunicationHostOperation(serviceHost, logger));
bootstrapOperations.Enqueue(new ServiceEventCleanupOperation(logger, sessionContext));
sessionOperations.Enqueue(new SessionInitializationOperation(logger, ServiceEventFactory, serviceHost, sessionContext));
sessionOperations.Enqueue(new SessionInitializationOperation(logger, ServiceEventFactory, sessionContext));
sessionOperations.Enqueue(new LockdownOperation(featureBackup, featureFactory, logger, sessionContext));
sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext));
var bootstrapSequence = new OperationSequence(logger, bootstrapOperations);
var sessionSequence = new OperationSequence(logger, sessionOperations);
ServiceController = new ServiceController(logger, LogWriterFactory, bootstrapSequence, sessionSequence, serviceHost, sessionContext);
ServiceController = new ServiceController(logger, LogWriterFactory, bootstrapSequence, sessionSequence, serviceHost, sessionContext, systemConfigurationUpdate);
}
private string BuildBackupFilePath()

View file

@ -8,7 +8,6 @@
using System;
using System.Threading;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging;
@ -18,17 +17,11 @@ namespace SafeExamBrowser.Service.Operations
{
private ILogger logger;
private Func<string, EventWaitHandle> serviceEventFactory;
private IServiceHost serviceHost;
public SessionInitializationOperation(
ILogger logger,
Func<string, EventWaitHandle> serviceEventFactory,
IServiceHost serviceHost,
SessionContext sessionContext) : base(sessionContext)
public SessionInitializationOperation(ILogger logger, Func<string, EventWaitHandle> serviceEventFactory, SessionContext sessionContext) : base(sessionContext)
{
this.logger = logger;
this.serviceEventFactory = serviceEventFactory;
this.serviceHost = serviceHost;
}
public override OperationResult Perform()
@ -41,9 +34,6 @@ namespace SafeExamBrowser.Service.Operations
logger.Info("Stopping auto-restore mechanism...");
Context.AutoRestoreMechanism.Stop();
logger.Info("Disabling service host...");
serviceHost.AllowConnection = false;
InitializeServiceEvent();
return OperationResult.Success;
@ -72,9 +62,6 @@ namespace SafeExamBrowser.Service.Operations
logger.Info("Starting auto-restore mechanism...");
Context.AutoRestoreMechanism.Start();
logger.Info("Enabling service host...");
serviceHost.AllowConnection = true;
logger.Info("Clearing session data...");
Context.Configuration = null;
Context.IsRunning = false;

View file

@ -11,6 +11,7 @@ using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Lockdown;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Service;
@ -24,6 +25,7 @@ namespace SafeExamBrowser.Service
private IOperationSequence sessionSequence;
private IServiceHost serviceHost;
private SessionContext sessionContext;
private ISystemConfigurationUpdate systemConfigurationUpdate;
private ILogObserver sessionWriter;
private ServiceConfiguration Session
@ -42,7 +44,8 @@ namespace SafeExamBrowser.Service
IOperationSequence bootstrapSequence,
IOperationSequence sessionSequence,
IServiceHost serviceHost,
SessionContext sessionContext)
SessionContext sessionContext,
ISystemConfigurationUpdate systemConfigurationUpdate)
{
this.logger = logger;
this.logWriterFactory = logWriterFactory;
@ -50,6 +53,7 @@ namespace SafeExamBrowser.Service
this.sessionSequence = sessionSequence;
this.serviceHost = serviceHost;
this.sessionContext = sessionContext;
this.systemConfigurationUpdate = systemConfigurationUpdate;
}
public bool TryStart()
@ -141,12 +145,14 @@ namespace SafeExamBrowser.Service
{
serviceHost.SessionStartRequested += ServiceHost_SessionStartRequested;
serviceHost.SessionStopRequested += ServiceHost_SessionStopRequested;
serviceHost.SystemConfigurationUpdateRequested += ServiceHost_SystemConfigurationUpdateRequested;
}
private void DeregisterEvents()
{
serviceHost.SessionStartRequested -= ServiceHost_SessionStartRequested;
serviceHost.SessionStopRequested -= ServiceHost_SessionStopRequested;
serviceHost.SystemConfigurationUpdateRequested -= ServiceHost_SystemConfigurationUpdateRequested;
}
private void ServiceHost_SessionStartRequested(SessionStartEventArgs args)
@ -182,6 +188,12 @@ namespace SafeExamBrowser.Service
}
}
private void ServiceHost_SystemConfigurationUpdateRequested()
{
logger.Info("Received request to initiate system configuration update.");
systemConfigurationUpdate.ExecuteAsync();
}
private string AppendDivider(string message)
{
var dashesLeft = new String('-', 48 - message.Length / 2 - message.Length % 2);