SEBWIN-220: Moved exception handling to proxy implementations by introducing the CommunicationResult.

This commit is contained in:
dbuechel 2018-08-10 13:23:24 +02:00
parent 70f68abc8f
commit c32028d3dd
24 changed files with 474 additions and 269 deletions

View file

@ -101,21 +101,20 @@ namespace SafeExamBrowser.Client.Behaviour
{ {
RegisterEvents(); RegisterEvents();
try var communication = runtime.InformClientReady();
if (communication.Success)
{ {
runtime.InformClientReady(); splashScreen.Hide();
logger.Info("--- Application successfully initialized ---");
logger.Log(string.Empty);
} }
catch (Exception e) else
{ {
logger.Error("Failed to inform runtime that client is ready!", e); success = false;
logger.Error("Failed to inform runtime that client is ready!");
return false;
} }
splashScreen.Hide();
logger.Info("--- Application successfully initialized ---");
logger.Log(string.Empty);
} }
else else
{ {
@ -227,14 +226,15 @@ namespace SafeExamBrowser.Client.Behaviour
{ {
if (success) if (success)
{ {
try var communication = runtime.RequestReconfiguration(filePath);
if (communication.Success)
{ {
runtime.RequestReconfiguration(filePath);
logger.Info($"Sent reconfiguration request for '{filePath}' to the runtime."); logger.Info($"Sent reconfiguration request for '{filePath}' to the runtime.");
} }
catch (Exception e) else
{ {
logger.Error($"Failed to communicate reconfiguration request for '{filePath}'!", e); logger.Error($"Failed to communicate reconfiguration request for '{filePath}'!");
messageBox.Show(TextKey.MessageBox_ReconfigurationError, TextKey.MessageBox_ReconfigurationErrorTitle, icon: MessageBoxIcon.Error); messageBox.Show(TextKey.MessageBox_ReconfigurationError, TextKey.MessageBox_ReconfigurationErrorTitle, icon: MessageBoxIcon.Error);
} }
} }
@ -287,13 +287,11 @@ namespace SafeExamBrowser.Client.Behaviour
if (result == MessageBoxResult.Yes) if (result == MessageBoxResult.Yes)
{ {
try var communication = runtime.RequestShutdown();
if (!communication.Success)
{ {
runtime.RequestShutdown(); logger.Error("Failed to communicate shutdown request to the runtime!");
}
catch (Exception e)
{
logger.Error("Failed to communicate shutdown request to the runtime!", e);
messageBox.Show(TextKey.MessageBox_QuitError, TextKey.MessageBox_QuitErrorTitle, icon: MessageBoxIcon.Error); messageBox.Show(TextKey.MessageBox_QuitError, TextKey.MessageBox_QuitErrorTitle, icon: MessageBoxIcon.Error);
} }
} }

View file

@ -38,7 +38,8 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
try try
{ {
var config = runtime.GetConfiguration(); var communication = runtime.GetConfiguration();
var config = communication.Value.Configuration;
configuration.AppConfig = config.AppConfig; configuration.AppConfig = config.AppConfig;
configuration.SessionId = config.SessionId; configuration.SessionId = config.SessionId;

View file

@ -36,25 +36,18 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
logger.Info("Initializing runtime connection..."); logger.Info("Initializing runtime connection...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeRuntimeConnection); ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeRuntimeConnection);
try connected = runtime.Connect(token);
{
connected = runtime.Connect(token);
}
catch (Exception e)
{
logger.Error("An unexpected error occurred while trying to connect to the runtime!", e);
}
if (!connected) if (connected)
{
logger.Info("Successfully connected to the runtime.");
}
else
{ {
logger.Error("Failed to connect to the runtime. Aborting startup..."); logger.Error("Failed to connect to the runtime. Aborting startup...");
return OperationResult.Failed;
} }
logger.Info("Successfully connected to the runtime."); return connected ? OperationResult.Success : OperationResult.Failed;
return OperationResult.Success;
} }
public OperationResult Repeat() public OperationResult Repeat()
@ -69,13 +62,15 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
if (connected) if (connected)
{ {
try var success = runtime.Disconnect();
if (success)
{ {
runtime.Disconnect(); logger.Info("Successfully disconnected from the runtime.");
} }
catch (Exception e) else
{ {
logger.Error("Failed to disconnect from runtime host!", e); logger.Error("Failed to disconnect from the runtime!");
} }
} }
} }

View file

@ -89,6 +89,7 @@ namespace SafeExamBrowser.Client
operations.Enqueue(new I18nOperation(logger, text)); operations.Enqueue(new I18nOperation(logger, text));
operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, startupToken)); operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, startupToken));
operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy)); operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy));
operations.Enqueue(new DelegateOperation(UpdateAppConfig));
operations.Enqueue(new DelayedInitializationOperation(BuildCommunicationHostOperation)); operations.Enqueue(new DelayedInitializationOperation(BuildCommunicationHostOperation));
// TODO // TODO
//operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation)); //operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation));
@ -202,9 +203,13 @@ namespace SafeExamBrowser.Client
return operation; return operation;
} }
private void UpdateClientControllerDependencies() private void UpdateAppConfig()
{ {
ClientController.AppConfig = configuration.AppConfig; ClientController.AppConfig = configuration.AppConfig;
}
private void UpdateClientControllerDependencies()
{
ClientController.Browser = browserController; ClientController.Browser = browserController;
ClientController.ClientHost = clientHost; ClientController.ClientHost = clientHost;
ClientController.SessionId = configuration.SessionId; ClientController.SessionId = configuration.SessionId;

View file

@ -23,17 +23,14 @@ namespace SafeExamBrowser.Contracts.Communication
event CommunicationEventHandler ConnectionLost; event CommunicationEventHandler ConnectionLost;
/// <summary> /// <summary>
/// Tries to establish a connection. Returns <c>true</c> if the connection has been accepted, otherwise <c>false</c>. If a /// Tries to establish a connection. Returns <c>true</c> if the connection has been successful, otherwise <c>false</c>. If a
/// connection was successfully established and the auto-ping flag is set, the connection status will be periodically checked. /// connection was successfully established and the auto-ping flag is set, the connection status will be periodically checked.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
bool Connect(Guid? token = null, bool autoPing = true); bool Connect(Guid? token = null, bool autoPing = true);
/// <summary> /// <summary>
/// Terminates an open connection. Returns <c>true</c> if the disconnection has been acknowledged, otherwise <c>false</c>. /// Terminates an open connection. Returns <c>true</c> if the disconnection has been successful, otherwise <c>false</c>.
/// </summary> /// </summary>
/// <exception cref="InvalidOperationException">If no connection has been established.</exception>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
bool Disconnect(); bool Disconnect();
} }
} }

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2018 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.Communication.Proxies
{
/// <summary>
/// Defines the result of a communication between an <see cref="ICommunicationProxy"/> and its <see cref="ICommunicationHost"/>.
/// </summary>
public class CommunicationResult
{
/// <summary>
/// Defines whether the communication was successful or not.
/// </summary>
public bool Success { get; protected set; }
public CommunicationResult(bool success)
{
Success = success;
}
}
/// <summary>
/// Defines the result of a communication between an <see cref="ICommunicationProxy"/> and its <see cref="ICommunicationHost"/>.
/// </summary>
/// <typeparam name="T">The type of the expected response value.</typeparam>
public class CommunicationResult<T> : CommunicationResult
{
/// <summary>
/// The response value. Can be <c>null</c> or <c>default(T)</c> in case the communication has failed!
/// </summary>
public T Value { get; protected set; }
public CommunicationResult(bool success, T value) : base(success)
{
Value = value;
}
}
}

View file

@ -19,24 +19,21 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// <summary> /// <summary>
/// Informs the client that the reconfiguration request for the specified file was denied. /// Informs the client that the reconfiguration request for the specified file was denied.
/// </summary> /// </summary>
void InformReconfigurationDenied(string filePath); CommunicationResult InformReconfigurationDenied(string filePath);
/// <summary> /// <summary>
/// Instructs the client to initiate its shutdown procedure. /// Instructs the client to initiate its shutdown procedure.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult InitiateShutdown();
void InitiateShutdown();
/// <summary> /// <summary>
/// Instructs the client to submit its authentication data. /// Instructs the client to submit its authentication data.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult<AuthenticationResponse> RequestAuthentication();
AuthenticationResponse RequestAuthentication();
/// <summary> /// <summary>
/// Requests the client to render a password dialog and subsequently return the interaction result as separate message. /// Requests the client to render a password dialog and subsequently return the interaction result as separate message.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult RequestPassword(PasswordRequestPurpose purpose, Guid requestId);
void RequestPassword(PasswordRequestPurpose purpose, Guid requestId);
} }
} }

View file

@ -7,7 +7,7 @@
*/ */
using System; using System;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Communication.Data;
namespace SafeExamBrowser.Contracts.Communication.Proxies namespace SafeExamBrowser.Contracts.Communication.Proxies
{ {
@ -19,32 +19,27 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// <summary> /// <summary>
/// Retrieves the application configuration from the runtime. /// Retrieves the application configuration from the runtime.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult<ConfigurationResponse> GetConfiguration();
ClientConfiguration GetConfiguration();
/// <summary> /// <summary>
/// Informs the runtime that the client is ready. /// Informs the runtime that the client is ready.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult InformClientReady();
void InformClientReady();
/// <summary> /// <summary>
/// Requests the runtime to shut down the application. /// Requests the runtime to shut down the application.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult RequestShutdown();
void RequestShutdown();
/// <summary> /// <summary>
/// Requests the runtime to reconfigure the application with the specified configuration. /// Requests the runtime to reconfigure the application with the specified configuration.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult RequestReconfiguration(string filePath);
void RequestReconfiguration(string filePath);
/// <summary> /// <summary>
/// Submits the result of a password input previously requested by the runtime. If the procedure was aborted by the user, /// Submits the result of a password input previously requested by the runtime. If the procedure was aborted by the user,
/// the password parameter will be <c>null</c>! /// the password parameter will be <c>null</c>!
/// </summary> /// </summary>
/// /// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult SubmitPassword(Guid requestId, bool success, string password = null);
void SubmitPassword(Guid requestId, bool success, string password = null);
} }
} }

View file

@ -25,13 +25,11 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// <summary> /// <summary>
/// Instructs the service to start a new session according to the given parameters. /// Instructs the service to start a new session according to the given parameters.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult StartSession(Guid sessionId, Settings settings);
void StartSession(Guid sessionId, Settings settings);
/// <summary> /// <summary>
/// Instructs the service to stop the specified session. /// Instructs the service to stop the specified session.
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> CommunicationResult StopSession(Guid sessionId);
void StopSession(Guid sessionId);
} }
} }

View file

@ -75,6 +75,7 @@
<Compile Include="Communication\Hosts\IHostObject.cs" /> <Compile Include="Communication\Hosts\IHostObject.cs" />
<Compile Include="Communication\Hosts\IHostObjectFactory.cs" /> <Compile Include="Communication\Hosts\IHostObjectFactory.cs" />
<Compile Include="Communication\ICommunication.cs" /> <Compile Include="Communication\ICommunication.cs" />
<Compile Include="Communication\Proxies\CommunicationResult.cs" />
<Compile Include="Communication\Proxies\IClientProxy.cs" /> <Compile Include="Communication\Proxies\IClientProxy.cs" />
<Compile Include="Communication\ICommunicationHost.cs" /> <Compile Include="Communication\ICommunicationHost.cs" />
<Compile Include="Communication\ICommunicationProxy.cs" /> <Compile Include="Communication\ICommunicationProxy.cs" />

View file

@ -106,14 +106,14 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void MustFailToDisconnectIfNotConnected() public void MustFailToDisconnectIfNotConnected()
{ {
sut.Disconnect(); var success = sut.Disconnect();
Assert.IsFalse(success);
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailToDisconnectIfChannelNotOpen() public void MustFailToDisconnectIfChannelNotOpen()
{ {
var proxy = new Mock<IProxyObject>(); var proxy = new Mock<IProxyObject>();
@ -130,7 +130,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
var token = Guid.NewGuid(); var token = Guid.NewGuid();
sut.Connect(token); sut.Connect(token);
sut.Disconnect();
var success = sut.Disconnect();
Assert.IsFalse(success);
} }
[TestMethod] [TestMethod]
@ -141,7 +144,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(CommunicationException))] [ExpectedException(typeof(InvalidOperationException))]
public void MustFailToSendIfChannelNotOpen() public void MustFailToSendIfChannelNotOpen()
{ {
var proxy = new Mock<IProxyObject>(); var proxy = new Mock<IProxyObject>();

View file

@ -10,7 +10,6 @@ using System;
using System.ServiceModel; using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -52,18 +51,20 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
{ {
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Shutdown))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged)); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Shutdown))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged));
sut.InitiateShutdown(); var communication = sut.InitiateShutdown();
proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Shutdown)), Times.Once); proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Shutdown)), Times.Once);
Assert.IsTrue(communication.Success);
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(CommunicationException))] public void MustIndicateIfShutdownCommandNotAcknowledged()
public void MustFailIfShutdownCommandNotAcknowledged()
{ {
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Shutdown))).Returns<Response>(null); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Shutdown))).Returns<Response>(null);
sut.InitiateShutdown(); var communication = sut.InitiateShutdown();
Assert.IsFalse(communication.Success);
} }
[TestMethod] [TestMethod]
@ -71,20 +72,24 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
{ {
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Authenticate))).Returns(new AuthenticationResponse()); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Authenticate))).Returns(new AuthenticationResponse());
var response = sut.RequestAuthentication(); var communication = sut.RequestAuthentication();
var response = communication.Value;
proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Authenticate)), Times.Once); proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Authenticate)), Times.Once);
Assert.IsTrue(communication.Success);
Assert.IsInstanceOfType(response, typeof(AuthenticationResponse)); Assert.IsInstanceOfType(response, typeof(AuthenticationResponse));
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(CommunicationException))] public void MustIndicateIfAuthenticationCommandNotAcknowledged()
public void MustFailIfAuthenticationCommandNotFollowed()
{ {
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Authenticate))).Returns<Response>(null); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Authenticate))).Returns<Response>(null);
sut.RequestAuthentication(); var communication = sut.RequestAuthentication();
Assert.AreEqual(default(AuthenticationResponse), communication.Value);
Assert.IsFalse(communication.Success);
} }
} }
} }

View file

@ -57,20 +57,23 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ConfigurationNeeded))).Returns(response); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ConfigurationNeeded))).Returns(response);
var configuration = sut.GetConfiguration(); var communication = sut.GetConfiguration();
var configuration = communication.Value.Configuration;
proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ConfigurationNeeded)), Times.Once); proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ConfigurationNeeded)), Times.Once);
Assert.IsTrue(communication.Success);
Assert.IsInstanceOfType(configuration, typeof(ClientConfiguration)); Assert.IsInstanceOfType(configuration, typeof(ClientConfiguration));
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfConfigurationNotRetrieved() public void MustFailIfConfigurationNotRetrieved()
{ {
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ConfigurationNeeded))).Returns<Response>(null); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ConfigurationNeeded))).Returns<Response>(null);
sut.GetConfiguration(); var communication = sut.GetConfiguration();
Assert.IsFalse(communication.Success);
} }
[TestMethod] [TestMethod]
@ -78,18 +81,20 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
{ {
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ClientIsReady))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged)); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ClientIsReady))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged));
sut.InformClientReady(); var communication = sut.InformClientReady();
Assert.IsTrue(communication.Success);
proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ClientIsReady)), Times.Once); proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ClientIsReady)), Times.Once);
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfClientReadyNotAcknowledged() public void MustFailIfClientReadyNotAcknowledged()
{ {
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ClientIsReady))).Returns<Response>(null); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.ClientIsReady))).Returns<Response>(null);
sut.InformClientReady(); var communication = sut.InformClientReady();
Assert.IsFalse(communication.Success);
} }
[TestMethod] [TestMethod]
@ -99,20 +104,22 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationPath == url))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged)); proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationPath == url))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged));
sut.RequestReconfiguration(url); var communication = sut.RequestReconfiguration(url);
Assert.IsTrue(communication.Success);
proxy.Verify(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationPath == url)), Times.Once); proxy.Verify(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationPath == url)), Times.Once);
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfReconfigurationRequestNotAcknowledged() public void MustFailIfReconfigurationRequestNotAcknowledged()
{ {
var url = "file:///C:/Some/file/url.seb"; var url = "file:///C:/Some/file/url.seb";
proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationPath == url))).Returns<Response>(null); proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationPath == url))).Returns<Response>(null);
sut.RequestReconfiguration(url); var communication = sut.RequestReconfiguration(url);
Assert.IsFalse(communication.Success);
} }
[TestMethod] [TestMethod]
@ -120,18 +127,20 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
{ {
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.RequestShutdown))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged)); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.RequestShutdown))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged));
sut.RequestShutdown(); var communication = sut.RequestShutdown();
Assert.IsTrue(communication.Success);
proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.RequestShutdown)), Times.Once); proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.RequestShutdown)), Times.Once);
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfShutdownRequestNotAcknowledged() public void MustFailIfShutdownRequestNotAcknowledged()
{ {
proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.RequestShutdown))).Returns<Response>(null); proxy.Setup(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.RequestShutdown))).Returns<Response>(null);
sut.RequestShutdown(); var communication = sut.RequestShutdown();
Assert.IsFalse(communication.Success);
} }
} }
} }

View file

@ -10,7 +10,6 @@ using System;
using System.ServiceModel; using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -68,8 +67,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
public void MustIgnoreStartSessionIfUnavaiable() public void MustIgnoreStartSessionIfUnavaiable()
{ {
sut.Ignore = true; sut.Ignore = true;
sut.StartSession(Guid.Empty, null);
var communication = sut.StartSession(Guid.Empty, null);
Assert.IsTrue(communication.Success);
proxy.Verify(p => p.Send(It.IsAny<Message>()), Times.Never); proxy.Verify(p => p.Send(It.IsAny<Message>()), Times.Never);
} }
@ -77,8 +78,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
public void MustIgnoreStopSessionIfUnavaiable() public void MustIgnoreStopSessionIfUnavaiable()
{ {
sut.Ignore = true; sut.Ignore = true;
sut.StopSession(Guid.Empty);
var communication = sut.StopSession(Guid.Empty);
Assert.IsTrue(communication.Success);
proxy.Verify(p => p.Send(It.IsAny<Message>()), Times.Never); proxy.Verify(p => p.Send(It.IsAny<Message>()), Times.Never);
} }
} }

View file

@ -31,6 +31,7 @@ namespace SafeExamBrowser.Core.Communication.Proxies
private Guid? communicationToken; private Guid? communicationToken;
private Timer timer; private Timer timer;
protected bool IsConnected { get { return communicationToken.HasValue; } }
protected ILogger Logger { get; private set; } protected ILogger Logger { get; private set; }
public event CommunicationEventHandler ConnectionLost; public event CommunicationEventHandler ConnectionLost;
@ -44,50 +45,76 @@ namespace SafeExamBrowser.Core.Communication.Proxies
public virtual bool Connect(Guid? token = null, bool autoPing = true) public virtual bool Connect(Guid? token = null, bool autoPing = true)
{ {
Logger.Debug($"Trying to connect to endpoint '{address}'{(token.HasValue ? $" with authentication token '{token}'" : string.Empty)}..."); try
InitializeProxyObject();
var response = proxy.Connect(token);
communicationToken = response.CommunicationToken;
Logger.Debug($"Connection was {(response.ConnectionEstablished ? "established" : "refused")}.");
if (response.ConnectionEstablished && autoPing)
{ {
StartAutoPing(); Logger.Debug($"Trying to connect to endpoint '{address}'{(token.HasValue ? $" with authentication token '{token}'" : string.Empty)}...");
}
return response.ConnectionEstablished; InitializeProxyObject();
var response = proxy.Connect(token);
var success = response.ConnectionEstablished;
communicationToken = response.CommunicationToken;
Logger.Debug($"Connection was {(success ? "established" : "refused")}.");
if (success && autoPing)
{
StartAutoPing();
}
return success;
}
catch (Exception e)
{
Logger.Error($"Failed to connect to endpoint '{address}'!", e);
return false;
}
} }
public virtual bool Disconnect() public virtual bool Disconnect()
{ {
FailIfNotConnected(nameof(Disconnect)); try
StopAutoPing(); {
if (!IsConnected)
{
Logger.Warn($"Cannot disconnect from endpoint '{address}' before being connected!");
var message = new DisconnectionMessage { CommunicationToken = communicationToken.Value }; return false;
var response = proxy.Disconnect(message); }
Logger.Debug($"{(response.ConnectionTerminated ? "Disconnected" : "Failed to disconnect")} from {address}."); StopAutoPing();
return response.ConnectionTerminated; var message = new DisconnectionMessage { CommunicationToken = communicationToken.Value };
var response = proxy.Disconnect(message);
var success = response.ConnectionTerminated;
Logger.Debug($"{(success ? "Disconnected" : "Failed to disconnect")} from {address}.");
if (success)
{
communicationToken = null;
}
return success;
}
catch (Exception e)
{
Logger.Error($"Failed to disconnect from endpoint '{address}'!", e);
return false;
}
} }
/// <summary> /// <summary>
/// Sends the given message, optionally returning a response. If no response is expected, <c>null</c> will be returned. /// Sends the given message, optionally returning a response. If no response is expected, <c>null</c> will be returned.
/// </summary> /// </summary>
/// <exception cref="ArgumentNullException">If the given message is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">If the given message is <c>null</c>.</exception>
/// <exception cref="InvalidOperationException">If no connection has been established yet.</exception> /// <exception cref="InvalidOperationException">If no connection has been established yet or the connection is corrupted.</exception>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
protected virtual Response Send(Message message) protected virtual Response Send(Message message)
{ {
if (message is null) FailIfNull(message);
{ FailIfNotConnected();
throw new ArgumentNullException(nameof(message));
}
FailIfNotConnected(nameof(Send));
message.CommunicationToken = communicationToken.Value; message.CommunicationToken = communicationToken.Value;
@ -195,16 +222,24 @@ namespace SafeExamBrowser.Core.Communication.Proxies
Logger.Debug("Communication channel is opening..."); Logger.Debug("Communication channel is opening...");
} }
private void FailIfNotConnected(string operationName) private void FailIfNull(Message message)
{ {
if (!communicationToken.HasValue) if (message is null)
{ {
throw new InvalidOperationException($"Cannot perform '{operationName}' before being connected to endpoint!"); throw new ArgumentNullException(nameof(message));
}
}
private void FailIfNotConnected()
{
if (!IsConnected)
{
throw new InvalidOperationException($"Cannot send message before being connected to endpoint '{address}'!");
} }
if (proxy == null || proxy.State != CommunicationState.Opened) if (proxy == null || proxy.State != CommunicationState.Opened)
{ {
throw new CommunicationException($"Tried to perform {operationName}, but channel was {GetChannelState()}!"); throw new InvalidOperationException($"Tried to send message, but channel was {GetChannelState()}!");
} }
} }

View file

@ -7,7 +7,6 @@
*/ */
using System; using System;
using System.ServiceModel;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -23,45 +22,107 @@ namespace SafeExamBrowser.Core.Communication.Proxies
{ {
} }
public void InformReconfigurationDenied(string filePath) public CommunicationResult InformReconfigurationDenied(string filePath)
{ {
var response = Send(new ReconfigurationDeniedMessage(filePath)); try
if (!IsAcknowledged(response))
{ {
throw new CommunicationException($"Client did not acknowledge shutdown request! Received: {ToString(response)}."); var response = Send(new ReconfigurationDeniedMessage(filePath));
var success = IsAcknowledged(response);
if (success)
{
Logger.Debug("Client acknowledged reconfiguration denial.");
}
else
{
Logger.Error($"Client did not acknowledge reconfiguration denial! Received: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(InformReconfigurationDenied)}'", e);
return new CommunicationResult(false);
} }
} }
public void InitiateShutdown() public CommunicationResult InitiateShutdown()
{ {
var response = Send(SimpleMessagePurport.Shutdown); try
if (!IsAcknowledged(response))
{ {
throw new CommunicationException($"Client did not acknowledge shutdown request! Received: {ToString(response)}."); var response = Send(SimpleMessagePurport.Shutdown);
var success = IsAcknowledged(response);
if (success)
{
Logger.Debug("Client acknowledged shutdown request.");
}
else
{
Logger.Error($"Client did not acknowledge shutdown request! Received: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(InitiateShutdown)}'", e);
return new CommunicationResult(false);
} }
} }
public AuthenticationResponse RequestAuthentication() public CommunicationResult<AuthenticationResponse> RequestAuthentication()
{ {
var response = Send(SimpleMessagePurport.Authenticate); try
if (response is AuthenticationResponse authenticationResponse)
{ {
return authenticationResponse; var response = Send(SimpleMessagePurport.Authenticate);
} var success = response is AuthenticationResponse;
throw new CommunicationException($"Did not receive authentication response! Received: {ToString(response)}."); if (success)
{
Logger.Debug("Received authentication response.");
}
else
{
Logger.Error($"Did not receive authentication response! Received: {ToString(response)}.");
}
return new CommunicationResult<AuthenticationResponse>(success, response as AuthenticationResponse);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(RequestAuthentication)}'", e);
return new CommunicationResult<AuthenticationResponse>(false, default(AuthenticationResponse));
}
} }
public void RequestPassword(PasswordRequestPurpose purpose, Guid requestId) public CommunicationResult RequestPassword(PasswordRequestPurpose purpose, Guid requestId)
{ {
var response = Send(new PasswordRequestMessage(purpose, requestId)); try
if (!IsAcknowledged(response))
{ {
throw new CommunicationException($"Client did not acknowledge shutdown request! Received: {ToString(response)}."); var response = Send(new PasswordRequestMessage(purpose, requestId));
var success = IsAcknowledged(response);
if (success)
{
Logger.Debug("Client acknowledged password request.");
}
else
{
Logger.Error($"Client did not acknowledge password request! Received: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(RequestPassword)}'", e);
return new CommunicationResult(false);
} }
} }
} }

View file

@ -7,10 +7,8 @@
*/ */
using System; using System;
using System.ServiceModel;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Core.Communication.Proxies namespace SafeExamBrowser.Core.Communication.Proxies
@ -24,55 +22,133 @@ namespace SafeExamBrowser.Core.Communication.Proxies
{ {
} }
public ClientConfiguration GetConfiguration() public CommunicationResult<ConfigurationResponse> GetConfiguration()
{ {
var response = Send(SimpleMessagePurport.ConfigurationNeeded); try
if (response is ConfigurationResponse configurationResponse)
{ {
return configurationResponse.Configuration; var response = Send(SimpleMessagePurport.ConfigurationNeeded);
var success = response is ConfigurationResponse;
if (success)
{
Logger.Debug("Received configuration response.");
}
else
{
Logger.Error($"Did not retrieve configuration response! Received: {ToString(response)}.");
}
return new CommunicationResult<ConfigurationResponse>(success, response as ConfigurationResponse);
} }
catch (Exception e)
throw new CommunicationException($"Could not retrieve client configuration! Received: {ToString(response)}.");
}
public void InformClientReady()
{
var response = Send(SimpleMessagePurport.ClientIsReady);
if (!IsAcknowledged(response))
{ {
throw new CommunicationException($"Runtime did not acknowledge that client is ready! Response: {ToString(response)}."); Logger.Error($"Failed to perform '{nameof(GetConfiguration)}'", e);
return new CommunicationResult<ConfigurationResponse>(false, default(ConfigurationResponse));
} }
} }
public void RequestReconfiguration(string filePath) public CommunicationResult InformClientReady()
{ {
var response = Send(new ReconfigurationMessage(filePath)); try
if (!IsAcknowledged(response))
{ {
throw new CommunicationException($"Runtime did not acknowledge reconfiguration request! Response: {ToString(response)}."); var response = Send(SimpleMessagePurport.ClientIsReady);
var success = IsAcknowledged(response);
if (success)
{
Logger.Debug("Runtime acknowledged that the client is ready.");
}
else
{
Logger.Error($"Runtime did not acknowledge that the client is ready! Response: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(InformClientReady)}'", e);
return new CommunicationResult(false);
} }
} }
public void RequestShutdown() public CommunicationResult RequestReconfiguration(string filePath)
{ {
var response = Send(SimpleMessagePurport.RequestShutdown); try
if (!IsAcknowledged(response))
{ {
throw new CommunicationException($"Runtime did not acknowledge shutdown request! Response: {ToString(response)}."); var response = Send(new ReconfigurationMessage(filePath));
var success = IsAcknowledged(response);
if (success)
{
Logger.Debug("Runtime acknowledged reconfiguration request.");
}
else
{
Logger.Error($"Runtime did not acknowledge reconfiguration request! Response: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(RequestReconfiguration)}'", e);
return new CommunicationResult(false);
} }
} }
public void SubmitPassword(Guid requestId, bool success, string password = null) public CommunicationResult RequestShutdown()
{ {
var response = Send(new PasswordReplyMessage(requestId, success, password)); try
if (!IsAcknowledged(response))
{ {
throw new CommunicationException($"Runtime did not acknowledge password submission! Response: {ToString(response)}."); var response = Send(SimpleMessagePurport.RequestShutdown);
var success = IsAcknowledged(response);
if (success)
{
Logger.Debug("Runtime acknowledged shutdown request.");
}
else
{
Logger.Error($"Runtime did not acknowledge shutdown request! Response: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(RequestShutdown)}'", e);
return new CommunicationResult(false);
}
}
public CommunicationResult SubmitPassword(Guid requestId, bool success, string password = null)
{
try
{
var response = Send(new PasswordReplyMessage(requestId, success, password));
var acknowledged = IsAcknowledged(response);
if (acknowledged)
{
Logger.Debug("Runtime acknowledged password transmission.");
}
else
{
Logger.Error($"Runtime did not acknowledge password transmission! Response: {ToString(response)}.");
}
return new CommunicationResult(acknowledged);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(SubmitPassword)}'", e);
return new CommunicationResult(false);
} }
} }
} }

View file

@ -38,32 +38,36 @@ namespace SafeExamBrowser.Core.Communication.Proxies
{ {
if (IgnoreOperation(nameof(Disconnect))) if (IgnoreOperation(nameof(Disconnect)))
{ {
return true; return false;
} }
return base.Disconnect(); return base.Disconnect();
} }
public void StartSession(Guid sessionId, Settings settings) public CommunicationResult StartSession(Guid sessionId, Settings settings)
{ {
if (IgnoreOperation(nameof(StartSession))) if (IgnoreOperation(nameof(StartSession)))
{ {
return; return new CommunicationResult(true);
} }
// TODO: Implement service communication // TODO: Implement service communication
// Send(new StartSessionMessage { Id = sessionId, Settings = settings }); // Send(new StartSessionMessage { Id = sessionId, Settings = settings });
throw new NotImplementedException();
} }
public void StopSession(Guid sessionId) public CommunicationResult StopSession(Guid sessionId)
{ {
if (IgnoreOperation(nameof(StopSession))) if (IgnoreOperation(nameof(StopSession)))
{ {
return; return new CommunicationResult(true);
} }
// TODO: Implement service communication // TODO: Implement service communication
// Send(new StopSessionMessage { SessionId = sessionId }); // Send(new StopSessionMessage { SessionId = sessionId });
throw new NotImplementedException();
} }
private bool IgnoreOperation(string operationName) private bool IgnoreOperation(string operationName)

View file

@ -67,10 +67,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
{ {
var result = default(OperationResult); var result = default(OperationResult);
var response = new AuthenticationResponse { ProcessId = 1234 }; var response = new AuthenticationResponse { ProcessId = 1234 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(response.ProcessId); process.SetupGet(p => p.Id).Returns(response.ProcessId);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady); processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady);
proxy.Setup(p => p.RequestAuthentication()).Returns(response); proxy.Setup(p => p.RequestAuthentication()).Returns(communication);
proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true); proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true);
result = sut.Perform(); result = sut.Perform();
@ -86,10 +87,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
{ {
var result = default(OperationResult); var result = default(OperationResult);
var response = new AuthenticationResponse { ProcessId = 1234 }; var response = new AuthenticationResponse { ProcessId = 1234 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(response.ProcessId); process.SetupGet(p => p.Id).Returns(response.ProcessId);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady); processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady);
proxy.Setup(p => p.RequestAuthentication()).Returns(response); proxy.Setup(p => p.RequestAuthentication()).Returns(communication);
proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true); proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true);
result = sut.Repeat(); result = sut.Repeat();
@ -134,10 +136,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
{ {
var result = default(OperationResult); var result = default(OperationResult);
var response = new AuthenticationResponse { ProcessId = -1 }; var response = new AuthenticationResponse { ProcessId = -1 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(1234); process.SetupGet(p => p.Id).Returns(1234);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady); processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady);
proxy.Setup(p => p.RequestAuthentication()).Returns(response); proxy.Setup(p => p.RequestAuthentication()).Returns(communication);
proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true); proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true);
result = sut.Perform(); result = sut.Perform();
@ -204,10 +207,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
private void PerformNormally() private void PerformNormally()
{ {
var response = new AuthenticationResponse { ProcessId = 1234 }; var response = new AuthenticationResponse { ProcessId = 1234 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(response.ProcessId); process.SetupGet(p => p.Id).Returns(response.ProcessId);
processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady); processFactory.Setup(f => f.StartNew(It.IsAny<string>(), It.IsAny<string[]>())).Returns(process.Object).Callback(clientReady);
proxy.Setup(p => p.RequestAuthentication()).Returns(response); proxy.Setup(p => p.RequestAuthentication()).Returns(communication);
proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true); proxy.Setup(p => p.Connect(It.IsAny<Guid>(), true)).Returns(true);
sut.Perform(); sut.Perform();

View file

@ -335,6 +335,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
public void MustRequestPasswordViaClientDuringReconfigurationOnNewDesktop() public void MustRequestPasswordViaClientDuringReconfigurationOnNewDesktop()
{ {
var clientProxy = new Mock<IClientProxy>(); var clientProxy = new Mock<IClientProxy>();
var communication = new CommunicationResult(true);
var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) => var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
{ {
runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = true }); runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = true });
@ -342,7 +343,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
var session = new Mock<ISessionData>(); var session = new Mock<ISessionData>();
var url = @"http://www.safeexambrowser.org/whatever.seb"; var url = @"http://www.safeexambrowser.org/whatever.seb";
clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).Callback(passwordReceived); clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).Returns(communication).Callback(passwordReceived);
passwordDialog.Setup(d => d.Show(null)).Returns(new PasswordDialogResultStub { Success = true }); passwordDialog.Setup(d => d.Show(null)).Returns(new PasswordDialogResultStub { Success = true });
repository.SetupGet(r => r.CurrentSession).Returns(session.Object); repository.SetupGet(r => r.CurrentSession).Returns(session.Object);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
@ -361,6 +362,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
public void MustAbortAskingForPasswordViaClientIfDecidedByUser() public void MustAbortAskingForPasswordViaClientIfDecidedByUser()
{ {
var clientProxy = new Mock<IClientProxy>(); var clientProxy = new Mock<IClientProxy>();
var communication = new CommunicationResult(true);
var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) => var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
{ {
runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = false }); runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = false });
@ -368,8 +370,28 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
var session = new Mock<ISessionData>(); var session = new Mock<ISessionData>();
var url = @"http://www.safeexambrowser.org/whatever.seb"; var url = @"http://www.safeexambrowser.org/whatever.seb";
clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).Callback(passwordReceived); clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).Returns(communication).Callback(passwordReceived);
passwordDialog.Setup(d => d.Show(null)).Returns(new PasswordDialogResultStub { Success = true }); repository.SetupGet(r => r.CurrentSession).Returns(session.Object);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object);
settings.KioskMode = KioskMode.CreateNewDesktop;
sut = new ConfigurationOperation(appConfig, repository.Object, logger.Object, messageBox.Object, resourceLoader.Object, runtimeHost.Object, text.Object, uiFactory.Object, new[] { "blubb.exe", url });
var result = sut.Perform();
Assert.AreEqual(OperationResult.Aborted, result);
}
[TestMethod]
public void MustNotWaitForPasswordViaClientIfCommunicationHasFailed()
{
var clientProxy = new Mock<IClientProxy>();
var communication = new CommunicationResult(false);
var session = new Mock<ISessionData>();
var url = @"http://www.safeexambrowser.org/whatever.seb";
clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).Returns(communication);
repository.SetupGet(r => r.CurrentSession).Returns(session.Object); repository.SetupGet(r => r.CurrentSession).Returns(session.Object);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object); session.SetupGet(r => r.ClientProxy).Returns(clientProxy.Object);

View file

@ -97,16 +97,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional }); configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
sut.Perform(); sut.Perform();
service.Setup(s => s.Connect(null, true)).Throws<Exception>();
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
sut.Perform();
service.Setup(s => s.Connect(null, true)).Throws<Exception>();
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
sut.Perform();
} }
[TestMethod] [TestMethod]
@ -177,7 +167,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
public void MustNotFailWhenDisconnecting() public void MustNotFailWhenDisconnecting()
{ {
service.Setup(s => s.Connect(null, true)).Returns(true); service.Setup(s => s.Connect(null, true)).Returns(true);
service.Setup(s => s.Disconnect()).Throws<Exception>(); service.Setup(s => s.Disconnect()).Returns(false);
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional }); configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
sut.Perform(); sut.Perform();
@ -201,18 +191,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
sut.Perform(); sut.Perform();
sut.Revert(); sut.Revert();
service.Setup(s => s.Connect(null, true)).Throws<Exception>();
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Mandatory });
sut.Perform();
sut.Revert();
service.Setup(s => s.Connect(null, true)).Throws<Exception>();
configuration.SetupGet(s => s.CurrentSettings).Returns(new Settings { ServicePolicy = ServicePolicy.Optional });
sut.Perform();
sut.Revert();
service.Verify(s => s.Disconnect(), Times.Never); service.Verify(s => s.Disconnect(), Times.Never);
} }
} }

View file

@ -120,9 +120,10 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Info("Connection with client has been established. Requesting authentication..."); logger.Info("Connection with client has been established. Requesting authentication...");
var response = ClientProxy.RequestAuthentication(); var communication = ClientProxy.RequestAuthentication();
var response = communication.Value;
if (ClientProcess.Id != response?.ProcessId) if (!communication.Success || ClientProcess.Id != response?.ProcessId)
{ {
logger.Error("Failed to verify client integrity!"); logger.Error("Failed to verify client integrity!");

View file

@ -172,17 +172,11 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
var title = isAdmin ? TextKey.PasswordDialog_AdminPasswordRequiredTitle : TextKey.PasswordDialog_SettingsPasswordRequiredTitle; var title = isAdmin ? TextKey.PasswordDialog_AdminPasswordRequiredTitle : TextKey.PasswordDialog_SettingsPasswordRequiredTitle;
var dialog = uiFactory.CreatePasswordDialog(text.Get(message), text.Get(title)); var dialog = uiFactory.CreatePasswordDialog(text.Get(message), text.Get(title));
var result = dialog.Show(); var result = dialog.Show();
var success = result.Success;
if (result.Success) password = success ? result.Password : default(string);
{
password = result.Password;
}
else
{
password = default(string);
}
return result.Success; return success;
} }
private bool TryGetPasswordViaClient(PasswordRequestPurpose purpose, out string password) private bool TryGetPasswordViaClient(PasswordRequestPurpose purpose, out string password)
@ -200,20 +194,20 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
}); });
runtimeHost.PasswordReceived += responseEventHandler; runtimeHost.PasswordReceived += responseEventHandler;
configuration.CurrentSession.ClientProxy.RequestPassword(purpose, requestId);
responseEvent.WaitOne(); var communication = configuration.CurrentSession.ClientProxy.RequestPassword(purpose, requestId);
if (communication.Success)
{
responseEvent.WaitOne();
}
var success = response?.Success == true;
runtimeHost.PasswordReceived -= responseEventHandler; runtimeHost.PasswordReceived -= responseEventHandler;
password = success ? response.Password : default(string);
if (response.Success) return success;
{
password = response.Password;
}
else
{
password = default(string);
}
return response.Success;
} }
private void HandleInvalidData(ref LoadStatus status, Uri uri) private void HandleInvalidData(ref LoadStatus status, Uri uri)

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using SafeExamBrowser.Contracts.Behaviour.OperationModel; using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
@ -40,15 +39,8 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Info($"Initializing service session..."); logger.Info($"Initializing service session...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeServiceSession); ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeServiceSession);
try mandatory = configuration.CurrentSettings.ServicePolicy == ServicePolicy.Mandatory;
{ connected = service.Connect();
mandatory = configuration.CurrentSettings.ServicePolicy == ServicePolicy.Mandatory;
connected = service.Connect();
}
catch (Exception e)
{
LogException(e);
}
if (mandatory && !connected) if (mandatory && !connected)
{ {
@ -89,13 +81,15 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
StopServiceSession(); StopServiceSession();
try var success = service.Disconnect();
if (success)
{ {
service.Disconnect(); logger.Info("Successfully disconnected from the service.");
} }
catch (Exception e) else
{ {
logger.Error("Failed to disconnect from the service!", e); logger.Error("Failed to disconnect from the service!");
} }
} }
} }
@ -109,19 +103,5 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
service.StopSession(configuration.CurrentSession.Id); service.StopSession(configuration.CurrentSession.Id);
} }
private void LogException(Exception e)
{
var message = "Failed to connect to the service component!";
if (mandatory)
{
logger.Error(message, e);
}
else
{
logger.Info($"{message} Reason: {e.Message}");
}
}
} }
} }