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();
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);
return false;
success = false;
logger.Error("Failed to inform runtime that client is ready!");
}
splashScreen.Hide();
logger.Info("--- Application successfully initialized ---");
logger.Log(string.Empty);
}
else
{
@ -227,14 +226,15 @@ namespace SafeExamBrowser.Client.Behaviour
{
if (success)
{
try
var communication = runtime.RequestReconfiguration(filePath);
if (communication.Success)
{
runtime.RequestReconfiguration(filePath);
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);
}
}
@ -287,13 +287,11 @@ namespace SafeExamBrowser.Client.Behaviour
if (result == MessageBoxResult.Yes)
{
try
var communication = runtime.RequestShutdown();
if (!communication.Success)
{
runtime.RequestShutdown();
}
catch (Exception e)
{
logger.Error("Failed to communicate shutdown request to the runtime!", e);
logger.Error("Failed to communicate shutdown request to the runtime!");
messageBox.Show(TextKey.MessageBox_QuitError, TextKey.MessageBox_QuitErrorTitle, icon: MessageBoxIcon.Error);
}
}

View file

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

View file

@ -36,25 +36,18 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
logger.Info("Initializing runtime connection...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeRuntimeConnection);
try
{
connected = runtime.Connect(token);
}
catch (Exception e)
{
logger.Error("An unexpected error occurred while trying to connect to the runtime!", e);
}
connected = runtime.Connect(token);
if (!connected)
if (connected)
{
logger.Info("Successfully connected to the runtime.");
}
else
{
logger.Error("Failed to connect to the runtime. Aborting startup...");
return OperationResult.Failed;
}
logger.Info("Successfully connected to the runtime.");
return OperationResult.Success;
return connected ? OperationResult.Success : OperationResult.Failed;
}
public OperationResult Repeat()
@ -69,13 +62,15 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
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 RuntimeConnectionOperation(logger, runtimeProxy, startupToken));
operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy));
operations.Enqueue(new DelegateOperation(UpdateAppConfig));
operations.Enqueue(new DelayedInitializationOperation(BuildCommunicationHostOperation));
// TODO
//operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation));
@ -202,9 +203,13 @@ namespace SafeExamBrowser.Client
return operation;
}
private void UpdateClientControllerDependencies()
private void UpdateAppConfig()
{
ClientController.AppConfig = configuration.AppConfig;
}
private void UpdateClientControllerDependencies()
{
ClientController.Browser = browserController;
ClientController.ClientHost = clientHost;
ClientController.SessionId = configuration.SessionId;

View file

@ -23,17 +23,14 @@ namespace SafeExamBrowser.Contracts.Communication
event CommunicationEventHandler ConnectionLost;
/// <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.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
bool Connect(Guid? token = null, bool autoPing = true);
/// <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>
/// <exception cref="InvalidOperationException">If no connection has been established.</exception>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
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>
/// Informs the client that the reconfiguration request for the specified file was denied.
/// </summary>
void InformReconfigurationDenied(string filePath);
CommunicationResult InformReconfigurationDenied(string filePath);
/// <summary>
/// Instructs the client to initiate its shutdown procedure.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
void InitiateShutdown();
CommunicationResult InitiateShutdown();
/// <summary>
/// Instructs the client to submit its authentication data.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
AuthenticationResponse RequestAuthentication();
CommunicationResult<AuthenticationResponse> RequestAuthentication();
/// <summary>
/// Requests the client to render a password dialog and subsequently return the interaction result as separate message.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
void RequestPassword(PasswordRequestPurpose purpose, Guid requestId);
CommunicationResult RequestPassword(PasswordRequestPurpose purpose, Guid requestId);
}
}

View file

@ -7,7 +7,7 @@
*/
using System;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Communication.Data;
namespace SafeExamBrowser.Contracts.Communication.Proxies
{
@ -19,32 +19,27 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// <summary>
/// Retrieves the application configuration from the runtime.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
ClientConfiguration GetConfiguration();
CommunicationResult<ConfigurationResponse> GetConfiguration();
/// <summary>
/// Informs the runtime that the client is ready.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
void InformClientReady();
CommunicationResult InformClientReady();
/// <summary>
/// Requests the runtime to shut down the application.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
void RequestShutdown();
CommunicationResult RequestShutdown();
/// <summary>
/// Requests the runtime to reconfigure the application with the specified configuration.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
void RequestReconfiguration(string filePath);
CommunicationResult RequestReconfiguration(string filePath);
/// <summary>
/// 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>!
/// </summary>
/// /// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
void SubmitPassword(Guid requestId, bool success, string password = null);
CommunicationResult SubmitPassword(Guid requestId, bool success, string password = null);
}
}

View file

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

View file

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

View file

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

View file

@ -10,7 +10,6 @@ using System;
using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies;
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));
sut.InitiateShutdown();
var communication = sut.InitiateShutdown();
proxy.Verify(p => p.Send(It.Is<SimpleMessage>(m => m.Purport == SimpleMessagePurport.Shutdown)), Times.Once);
Assert.IsTrue(communication.Success);
}
[TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfShutdownCommandNotAcknowledged()
public void MustIndicateIfShutdownCommandNotAcknowledged()
{
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]
@ -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());
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);
Assert.IsTrue(communication.Success);
Assert.IsInstanceOfType(response, typeof(AuthenticationResponse));
}
[TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfAuthenticationCommandNotFollowed()
public void MustIndicateIfAuthenticationCommandNotAcknowledged()
{
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);
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);
Assert.IsTrue(communication.Success);
Assert.IsInstanceOfType(configuration, typeof(ClientConfiguration));
}
[TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfConfigurationNotRetrieved()
{
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]
@ -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));
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);
}
[TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfClientReadyNotAcknowledged()
{
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]
@ -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));
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);
}
[TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfReconfigurationRequestNotAcknowledged()
{
var url = "file:///C:/Some/file/url.seb";
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]
@ -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));
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);
}
[TestMethod]
[ExpectedException(typeof(CommunicationException))]
public void MustFailIfShutdownRequestNotAcknowledged()
{
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 Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging;
@ -68,8 +67,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
public void MustIgnoreStartSessionIfUnavaiable()
{
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);
}
@ -77,8 +78,10 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
public void MustIgnoreStopSessionIfUnavaiable()
{
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);
}
}

View file

@ -31,6 +31,7 @@ namespace SafeExamBrowser.Core.Communication.Proxies
private Guid? communicationToken;
private Timer timer;
protected bool IsConnected { get { return communicationToken.HasValue; } }
protected ILogger Logger { get; private set; }
public event CommunicationEventHandler ConnectionLost;
@ -44,50 +45,76 @@ namespace SafeExamBrowser.Core.Communication.Proxies
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)}...");
InitializeProxyObject();
var response = proxy.Connect(token);
communicationToken = response.CommunicationToken;
Logger.Debug($"Connection was {(response.ConnectionEstablished ? "established" : "refused")}.");
if (response.ConnectionEstablished && autoPing)
try
{
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()
{
FailIfNotConnected(nameof(Disconnect));
StopAutoPing();
try
{
if (!IsConnected)
{
Logger.Warn($"Cannot disconnect from endpoint '{address}' before being connected!");
var message = new DisconnectionMessage { CommunicationToken = communicationToken.Value };
var response = proxy.Disconnect(message);
return false;
}
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>
/// Sends the given message, optionally returning a response. If no response is expected, <c>null</c> will be returned.
/// </summary>
/// <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="System.ServiceModel.*">If the communication failed.</exception>
/// <exception cref="InvalidOperationException">If no connection has been established yet or the connection is corrupted.</exception>
protected virtual Response Send(Message message)
{
if (message is null)
{
throw new ArgumentNullException(nameof(message));
}
FailIfNotConnected(nameof(Send));
FailIfNull(message);
FailIfNotConnected();
message.CommunicationToken = communicationToken.Value;
@ -195,16 +222,24 @@ namespace SafeExamBrowser.Core.Communication.Proxies
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)
{
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.ServiceModel;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies;
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));
if (!IsAcknowledged(response))
try
{
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);
if (!IsAcknowledged(response))
try
{
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);
if (response is AuthenticationResponse authenticationResponse)
try
{
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));
if (!IsAcknowledged(response))
try
{
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.ServiceModel;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
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);
if (response is ConfigurationResponse configurationResponse)
try
{
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);
}
throw new CommunicationException($"Could not retrieve client configuration! Received: {ToString(response)}.");
}
public void InformClientReady()
{
var response = Send(SimpleMessagePurport.ClientIsReady);
if (!IsAcknowledged(response))
catch (Exception e)
{
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));
if (!IsAcknowledged(response))
try
{
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);
if (!IsAcknowledged(response))
try
{
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));
if (!IsAcknowledged(response))
try
{
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)))
{
return true;
return false;
}
return base.Disconnect();
}
public void StartSession(Guid sessionId, Settings settings)
public CommunicationResult StartSession(Guid sessionId, Settings settings)
{
if (IgnoreOperation(nameof(StartSession)))
{
return;
return new CommunicationResult(true);
}
// TODO: Implement service communication
// Send(new StartSessionMessage { Id = sessionId, Settings = settings });
throw new NotImplementedException();
}
public void StopSession(Guid sessionId)
public CommunicationResult StopSession(Guid sessionId)
{
if (IgnoreOperation(nameof(StopSession)))
{
return;
return new CommunicationResult(true);
}
// TODO: Implement service communication
// Send(new StopSessionMessage { SessionId = sessionId });
throw new NotImplementedException();
}
private bool IgnoreOperation(string operationName)

View file

@ -67,10 +67,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
{
var result = default(OperationResult);
var response = new AuthenticationResponse { ProcessId = 1234 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(response.ProcessId);
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);
result = sut.Perform();
@ -86,10 +87,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
{
var result = default(OperationResult);
var response = new AuthenticationResponse { ProcessId = 1234 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(response.ProcessId);
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);
result = sut.Repeat();
@ -134,10 +136,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
{
var result = default(OperationResult);
var response = new AuthenticationResponse { ProcessId = -1 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(1234);
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);
result = sut.Perform();
@ -204,10 +207,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
private void PerformNormally()
{
var response = new AuthenticationResponse { ProcessId = 1234 };
var communication = new CommunicationResult<AuthenticationResponse>(true, response);
process.SetupGet(p => p.Id).Returns(response.ProcessId);
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);
sut.Perform();

View file

@ -335,6 +335,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
public void MustRequestPasswordViaClientDuringReconfigurationOnNewDesktop()
{
var clientProxy = new Mock<IClientProxy>();
var communication = new CommunicationResult(true);
var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
{
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 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);
@ -361,6 +362,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
public void MustAbortAskingForPasswordViaClientIfDecidedByUser()
{
var clientProxy = new Mock<IClientProxy>();
var communication = new CommunicationResult(true);
var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
{
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 url = @"http://www.safeexambrowser.org/whatever.seb";
clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).Callback(passwordReceived);
passwordDialog.Setup(d => d.Show(null)).Returns(new PasswordDialogResultStub { Success = true });
clientProxy.Setup(c => c.RequestPassword(It.IsAny<PasswordRequestPurpose>(), It.IsAny<Guid>())).Returns(communication).Callback(passwordReceived);
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.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
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 });
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]
@ -177,7 +167,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
public void MustNotFailWhenDisconnecting()
{
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 });
sut.Perform();
@ -201,18 +191,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
sut.Perform();
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);
}
}

View file

@ -120,9 +120,10 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
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!");

View file

@ -172,17 +172,11 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
var title = isAdmin ? TextKey.PasswordDialog_AdminPasswordRequiredTitle : TextKey.PasswordDialog_SettingsPasswordRequiredTitle;
var dialog = uiFactory.CreatePasswordDialog(text.Get(message), text.Get(title));
var result = dialog.Show();
var success = result.Success;
if (result.Success)
{
password = result.Password;
}
else
{
password = default(string);
}
password = success ? result.Password : default(string);
return result.Success;
return success;
}
private bool TryGetPasswordViaClient(PasswordRequestPurpose purpose, out string password)
@ -200,20 +194,20 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
});
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;
password = success ? response.Password : default(string);
if (response.Success)
{
password = response.Password;
}
else
{
password = default(string);
}
return response.Success;
return success;
}
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/.
*/
using System;
using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
@ -40,15 +39,8 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Info($"Initializing service session...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeServiceSession);
try
{
mandatory = configuration.CurrentSettings.ServicePolicy == ServicePolicy.Mandatory;
connected = service.Connect();
}
catch (Exception e)
{
LogException(e);
}
mandatory = configuration.CurrentSettings.ServicePolicy == ServicePolicy.Mandatory;
connected = service.Connect();
if (mandatory && !connected)
{
@ -89,13 +81,15 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
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);
}
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}");
}
}
}
}