SEBWIN-301: Implemented basic service session procedure.

This commit is contained in:
dbuechel 2019-06-18 10:18:56 +02:00
parent 77a3b50ca9
commit 6b24554abc
61 changed files with 911 additions and 296 deletions

View file

@ -105,7 +105,12 @@ namespace SafeExamBrowser.Client.UnitTests.Communication
sut.AuthenticationToken = token; sut.AuthenticationToken = token;
var connectionResponse = sut.Connect(token); var connectionResponse = sut.Connect(token);
var response = sut.Disconnect(new DisconnectionMessage { CommunicationToken = connectionResponse.CommunicationToken.Value }); var message = new DisconnectionMessage
{
CommunicationToken = connectionResponse.CommunicationToken.Value,
Interlocutor = Interlocutor.Runtime
};
var response = sut.Disconnect(message);
Assert.IsNotNull(response); Assert.IsNotNull(response);
Assert.IsTrue(response.ConnectionTerminated); Assert.IsTrue(response.ConnectionTerminated);
@ -118,8 +123,13 @@ namespace SafeExamBrowser.Client.UnitTests.Communication
{ {
var token = sut.AuthenticationToken = Guid.NewGuid(); var token = sut.AuthenticationToken = Guid.NewGuid();
var response = sut.Connect(token); var response = sut.Connect(token);
var message = new DisconnectionMessage
{
CommunicationToken = response.CommunicationToken.Value,
Interlocutor = Interlocutor.Runtime
};
sut.Disconnect(new DisconnectionMessage { CommunicationToken = response.CommunicationToken.Value }); sut.Disconnect(message);
sut.AuthenticationToken = token = Guid.NewGuid(); sut.AuthenticationToken = token = Guid.NewGuid();
response = sut.Connect(token); response = sut.Connect(token);
@ -274,7 +284,7 @@ namespace SafeExamBrowser.Client.UnitTests.Communication
sut.Send(new PasswordRequestMessage(default(PasswordRequestPurpose), Guid.Empty) { CommunicationToken = token }); sut.Send(new PasswordRequestMessage(default(PasswordRequestPurpose), Guid.Empty) { CommunicationToken = token });
sut.Send(new ReconfigurationDeniedMessage("") { CommunicationToken = token }); sut.Send(new ReconfigurationDeniedMessage("") { CommunicationToken = token });
sut.Send(new SimpleMessage(SimpleMessagePurport.Shutdown) { CommunicationToken = token }); sut.Send(new SimpleMessage(SimpleMessagePurport.Shutdown) { CommunicationToken = token });
sut.Disconnect(new DisconnectionMessage { CommunicationToken = token }); sut.Disconnect(new DisconnectionMessage { CommunicationToken = token, Interlocutor = Interlocutor.Runtime });
} }
private class TestMessage : Message { }; private class TestMessage : Message { };

View file

@ -65,12 +65,14 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
{ {
runtime.Setup(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>())).Returns(true); runtime.Setup(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>())).Returns(true);
runtime.Setup(r => r.Disconnect()).Returns(true); runtime.Setup(r => r.Disconnect()).Returns(true);
runtime.SetupGet(r => r.IsConnected).Returns(true);
sut.Perform(); sut.Perform();
var result = sut.Revert(); var result = sut.Revert();
runtime.Verify(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once); runtime.Verify(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
runtime.Verify(r => r.Disconnect(), Times.Once); runtime.Verify(r => r.Disconnect(), Times.Once);
runtime.VerifyGet(r => r.IsConnected, Times.Once);
runtime.VerifyNoOtherCalls(); runtime.VerifyNoOtherCalls();
Assert.AreEqual(OperationResult.Success, result); Assert.AreEqual(OperationResult.Success, result);
@ -81,12 +83,14 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
{ {
runtime.Setup(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>())).Returns(true); runtime.Setup(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>())).Returns(true);
runtime.Setup(r => r.Disconnect()).Returns(false); runtime.Setup(r => r.Disconnect()).Returns(false);
runtime.SetupGet(r => r.IsConnected).Returns(true);
sut.Perform(); sut.Perform();
var result = sut.Revert(); var result = sut.Revert();
runtime.Verify(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once); runtime.Verify(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
runtime.Verify(r => r.Disconnect(), Times.Once); runtime.Verify(r => r.Disconnect(), Times.Once);
runtime.VerifyGet(r => r.IsConnected, Times.Once);
runtime.VerifyNoOtherCalls(); runtime.VerifyNoOtherCalls();
Assert.AreEqual(OperationResult.Failed, result); Assert.AreEqual(OperationResult.Failed, result);
@ -97,6 +101,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
{ {
var result = sut.Revert(); var result = sut.Revert();
runtime.VerifyGet(r => r.IsConnected, Times.Once);
runtime.VerifyNoOtherCalls(); runtime.VerifyNoOtherCalls();
Assert.AreEqual(OperationResult.Success, result); Assert.AreEqual(OperationResult.Success, result);

View file

@ -8,6 +8,7 @@
using System; using System;
using SafeExamBrowser.Communication.Hosts; using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
@ -53,10 +54,13 @@ namespace SafeExamBrowser.Client.Communication
return accepted; return accepted;
} }
protected override void OnDisconnect() protected override void OnDisconnect(Interlocutor interlocutor)
{ {
RuntimeDisconnected?.Invoke(); if (interlocutor == Interlocutor.Runtime)
IsConnected = false; {
RuntimeDisconnected?.Invoke();
IsConnected = false;
}
} }
protected override Response OnReceive(Message message) protected override Response OnReceive(Message message)

View file

@ -20,6 +20,7 @@ using SafeExamBrowser.Communication.Proxies;
using SafeExamBrowser.Configuration.Cryptography; using SafeExamBrowser.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Browser; using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Client; using SafeExamBrowser.Contracts.Client;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
@ -97,7 +98,7 @@ namespace SafeExamBrowser.Client
powerSupply = new PowerSupply(new ModuleLogger(logger, nameof(PowerSupply)), text); powerSupply = new PowerSupply(new ModuleLogger(logger, nameof(PowerSupply)), text);
processMonitor = new ProcessMonitor(new ModuleLogger(logger, nameof(ProcessMonitor)), nativeMethods); processMonitor = new ProcessMonitor(new ModuleLogger(logger, nameof(ProcessMonitor)), nativeMethods);
uiFactory = BuildUserInterfaceFactory(); uiFactory = BuildUserInterfaceFactory();
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(RuntimeProxy))); runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(RuntimeProxy)), Interlocutor.Client);
taskbar = BuildTaskbar(); taskbar = BuildTaskbar();
terminationActivator = new TerminationActivator(new ModuleLogger(logger, nameof(TerminationActivator))); terminationActivator = new TerminationActivator(new ModuleLogger(logger, nameof(TerminationActivator)));
windowMonitor = new WindowMonitor(new ModuleLogger(logger, nameof(WindowMonitor)), nativeMethods); windowMonitor = new WindowMonitor(new ModuleLogger(logger, nameof(WindowMonitor)), nativeMethods);

View file

@ -17,7 +17,6 @@ namespace SafeExamBrowser.Client.Operations
{ {
internal class RuntimeConnectionOperation : IOperation internal class RuntimeConnectionOperation : IOperation
{ {
private bool connected;
private ILogger logger; private ILogger logger;
private IRuntimeProxy runtime; private IRuntimeProxy runtime;
private Guid token; private Guid token;
@ -37,7 +36,7 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("Initializing runtime connection..."); logger.Info("Initializing runtime connection...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeRuntimeConnection); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeRuntimeConnection);
connected = runtime.Connect(token); var connected = runtime.Connect(token);
if (connected) if (connected)
{ {
@ -56,7 +55,7 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("Closing runtime connection..."); logger.Info("Closing runtime connection...");
StatusChanged?.Invoke(TextKey.OperationStatus_CloseRuntimeConnection); StatusChanged?.Invoke(TextKey.OperationStatus_CloseRuntimeConnection);
if (connected) if (runtime.IsConnected)
{ {
var success = runtime.Disconnect(); var success = runtime.Disconnect();

View file

@ -7,7 +7,9 @@
*/ */
using System; using System;
using System.Linq;
using SafeExamBrowser.Communication.Hosts; using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -17,7 +19,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
internal class BaseHostStub : BaseHost internal class BaseHostStub : BaseHost
{ {
public Func<Guid?, bool> OnConnectStub { get; set; } public Func<Guid?, bool> OnConnectStub { get; set; }
public Action OnDisconnectStub { get; set; } public Action<Interlocutor> OnDisconnectStub { get; set; }
public Func<Message, Response> OnReceiveStub { get; set; } public Func<Message, Response> OnReceiveStub { get; set; }
public Func<SimpleMessagePurport, Response> OnReceiveSimpleMessageStub { get; set; } public Func<SimpleMessagePurport, Response> OnReceiveSimpleMessageStub { get; set; }
@ -27,7 +29,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
public Guid? GetCommunicationToken() public Guid? GetCommunicationToken()
{ {
return CommunicationToken; return CommunicationToken.Any() ? CommunicationToken.First() : default(Guid?);
} }
protected override bool OnConnect(Guid? token) protected override bool OnConnect(Guid? token)
@ -35,9 +37,9 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
return OnConnectStub?.Invoke(token) == true; return OnConnectStub?.Invoke(token) == true;
} }
protected override void OnDisconnect() protected override void OnDisconnect(Interlocutor interlocutor)
{ {
OnDisconnectStub?.Invoke(); OnDisconnectStub?.Invoke(interlocutor);
} }
protected override Response OnReceive(Message message) protected override Response OnReceive(Message message)

View file

@ -154,17 +154,19 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
[TestMethod] [TestMethod]
public void MustCorrectlyHandleDisconnectionRequest() public void MustCorrectlyHandleDisconnectionRequest()
{ {
var message = new DisconnectionMessage(); var message = new DisconnectionMessage { Interlocutor = Interlocutor.Runtime };
var disconnected = false; var disconnected = false;
var interlocutor = Interlocutor.Unknown;
sut.OnConnectStub = (t) => { return true; }; sut.OnConnectStub = (t) => { return true; };
sut.OnDisconnectStub = () => disconnected = true; sut.OnDisconnectStub = (i) => { disconnected = true; interlocutor = i; };
sut.Connect(); sut.Connect();
message.CommunicationToken = sut.GetCommunicationToken().Value; message.CommunicationToken = sut.GetCommunicationToken().Value;
var response = sut.Disconnect(message); var response = sut.Disconnect(message);
Assert.AreEqual(message.Interlocutor, interlocutor);
Assert.IsTrue(disconnected); Assert.IsTrue(disconnected);
Assert.IsTrue(response.ConnectionTerminated); Assert.IsTrue(response.ConnectionTerminated);
Assert.IsNull(sut.GetCommunicationToken()); Assert.IsNull(sut.GetCommunicationToken());
@ -176,7 +178,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
var disconnected = false; var disconnected = false;
sut.OnConnectStub = (t) => { return true; }; sut.OnConnectStub = (t) => { return true; };
sut.OnDisconnectStub = () => disconnected = true; sut.OnDisconnectStub = (i) => disconnected = true;
sut.Connect(); sut.Connect();
var response = sut.Disconnect(new DisconnectionMessage()); var response = sut.Disconnect(new DisconnectionMessage());

View file

@ -7,16 +7,17 @@
*/ */
using System; using System;
using SafeExamBrowser.Communication.Proxies;
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;
using SafeExamBrowser.Communication.Proxies;
namespace SafeExamBrowser.Communication.UnitTests.Proxies namespace SafeExamBrowser.Communication.UnitTests.Proxies
{ {
internal class BaseProxyImpl : BaseProxy internal class BaseProxyImpl : BaseProxy
{ {
public BaseProxyImpl(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger) public BaseProxyImpl(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner) : base(address, factory, logger, owner)
{ {
} }

View file

@ -10,6 +10,7 @@ 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;
@ -29,7 +30,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
proxyObjectFactory = new Mock<IProxyObjectFactory>(); proxyObjectFactory = new Mock<IProxyObjectFactory>();
logger = new Mock<ILogger>(); logger = new Mock<ILogger>();
sut = new BaseProxyImpl("net.pipe://some/address/here", proxyObjectFactory.Object, logger.Object); sut = new BaseProxyImpl("net.pipe://some/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor));
} }
[TestMethod] [TestMethod]

View file

@ -11,6 +11,7 @@ using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Communication.Proxies; using SafeExamBrowser.Communication.Proxies;
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;
@ -43,7 +44,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
sut = new ClientProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object); sut = new ClientProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor));
sut.Connect(Guid.NewGuid()); sut.Connect(Guid.NewGuid());
} }

View file

@ -11,6 +11,7 @@ using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Communication.Proxies; using SafeExamBrowser.Communication.Proxies;
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.Configuration; using SafeExamBrowser.Contracts.Configuration;
@ -44,7 +45,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
sut = new RuntimeProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object); sut = new RuntimeProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor));
sut.Connect(Guid.NewGuid()); sut.Connect(Guid.NewGuid());
} }

View file

@ -14,6 +14,7 @@ using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Communication.Proxies; using SafeExamBrowser.Communication.Proxies;
using SafeExamBrowser.Contracts.Communication;
namespace SafeExamBrowser.Communication.UnitTests.Proxies namespace SafeExamBrowser.Communication.UnitTests.Proxies
{ {
@ -42,7 +43,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
proxy.Setup(o => o.State).Returns(CommunicationState.Opened); proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object); proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
sut = new ServiceProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object); sut = new ServiceProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor));
} }
[TestMethod] [TestMethod]

View file

@ -7,6 +7,7 @@
*/ */
using System; using System;
using System.Collections.Generic;
using System.ServiceModel; using System.ServiceModel;
using System.Threading; using System.Threading;
using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication;
@ -30,7 +31,7 @@ namespace SafeExamBrowser.Communication.Hosts
private Thread hostThread; private Thread hostThread;
private int timeout_ms; private int timeout_ms;
protected Guid? CommunicationToken { get; private set; } protected IList<Guid> CommunicationToken { get; private set; }
protected ILogger Logger { get; private set; } protected ILogger Logger { get; private set; }
public bool IsRunning public bool IsRunning
@ -47,13 +48,14 @@ namespace SafeExamBrowser.Communication.Hosts
public BaseHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) public BaseHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms)
{ {
this.address = address; this.address = address;
this.CommunicationToken = new List<Guid>();
this.factory = factory; this.factory = factory;
this.Logger = logger; this.Logger = logger;
this.timeout_ms = timeout_ms; this.timeout_ms = timeout_ms;
} }
protected abstract bool OnConnect(Guid? token); protected abstract bool OnConnect(Guid? token);
protected abstract void OnDisconnect(); protected abstract void OnDisconnect(Interlocutor interlocutor);
protected abstract Response OnReceive(Message message); protected abstract Response OnReceive(Message message);
protected abstract Response OnReceive(SimpleMessagePurport message); protected abstract Response OnReceive(SimpleMessagePurport message);
@ -61,15 +63,19 @@ namespace SafeExamBrowser.Communication.Hosts
{ {
lock (@lock) lock (@lock)
{ {
Logger.Debug($"Received connection request with authentication token '{token}'."); Logger.Debug($"Received connection request {(token.HasValue ? $"with authentication token '{token}'" : "without authentication token")}.");
var response = new ConnectionResponse(); var response = new ConnectionResponse();
var connected = OnConnect(token); var connected = OnConnect(token);
if (connected) if (connected)
{ {
response.CommunicationToken = CommunicationToken = Guid.NewGuid(); var communicationToken = Guid.NewGuid();
response.CommunicationToken = communicationToken;
response.ConnectionEstablished = true; response.ConnectionEstablished = true;
CommunicationToken.Add(communicationToken);
} }
Logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request."); Logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request.");
@ -88,10 +94,10 @@ namespace SafeExamBrowser.Communication.Hosts
if (IsAuthorized(message?.CommunicationToken)) if (IsAuthorized(message?.CommunicationToken))
{ {
OnDisconnect(); OnDisconnect(message.Interlocutor);
CommunicationToken = null;
response.ConnectionTerminated = true; response.ConnectionTerminated = true;
CommunicationToken.Remove(message.CommunicationToken);
} }
return response; return response;
@ -166,7 +172,7 @@ namespace SafeExamBrowser.Communication.Hosts
private bool IsAuthorized(Guid? token) private bool IsAuthorized(Guid? token)
{ {
return CommunicationToken == token; return token.HasValue && CommunicationToken.Contains(token.Value);
} }
private void TryStartHost(AutoResetEvent startedEvent, out Exception exception) private void TryStartHost(AutoResetEvent startedEvent, out Exception exception)

View file

@ -26,21 +26,23 @@ namespace SafeExamBrowser.Communication.Proxies
private readonly object @lock = new object(); private readonly object @lock = new object();
private string address; private string address;
private Interlocutor owner;
private IProxyObject proxy; private IProxyObject proxy;
private IProxyObjectFactory factory; private IProxyObjectFactory factory;
private Guid? communicationToken; private Guid? communicationToken;
private Timer timer; private Timer timer;
protected bool IsConnected { get { return communicationToken.HasValue; } } protected ILogger Logger { get; }
protected ILogger Logger { get; private set; } public bool IsConnected => communicationToken.HasValue;
public event CommunicationEventHandler ConnectionLost; public event CommunicationEventHandler ConnectionLost;
public BaseProxy(string address, IProxyObjectFactory factory, ILogger logger) public BaseProxy(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner)
{ {
this.address = address; this.address = address;
this.factory = factory; this.factory = factory;
this.Logger = logger; this.Logger = logger;
this.owner = owner;
} }
public virtual bool Connect(Guid? token = null, bool autoPing = true) public virtual bool Connect(Guid? token = null, bool autoPing = true)
@ -89,7 +91,11 @@ namespace SafeExamBrowser.Communication.Proxies
StopAutoPing(); StopAutoPing();
var message = new DisconnectionMessage { CommunicationToken = communicationToken.Value }; var message = new DisconnectionMessage
{
CommunicationToken = communicationToken.Value,
Interlocutor = owner
};
var response = proxy.Disconnect(message); var response = proxy.Disconnect(message);
var success = response.ConnectionTerminated; var success = response.ConnectionTerminated;

View file

@ -7,6 +7,7 @@
*/ */
using System; using System;
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;
@ -19,7 +20,7 @@ namespace SafeExamBrowser.Communication.Proxies
/// </summary> /// </summary>
public class ClientProxy : BaseProxy, IClientProxy public class ClientProxy : BaseProxy, IClientProxy
{ {
public ClientProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger) public ClientProxy(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner) : base(address, factory, logger, owner)
{ {
} }

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Communication.Proxies
{
/// <summary>
/// Default implementation of the <see cref="IProxyFactory"/>, creating instances of the default proxy implementations.
/// </summary>
public class ProxyFactory : IProxyFactory
{
private IProxyObjectFactory factory;
private IModuleLogger logger;
public ProxyFactory(IProxyObjectFactory factory, IModuleLogger logger)
{
this.factory = factory;
this.logger = logger;
}
public IClientProxy CreateClientProxy(string address, Interlocutor owner)
{
return new ClientProxy(address, factory, logger.CloneFor(nameof(ClientProxy)), owner);
}
}
}

View file

@ -7,6 +7,7 @@
*/ */
using System; using System;
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;
@ -19,7 +20,7 @@ namespace SafeExamBrowser.Communication.Proxies
/// </summary> /// </summary>
public class RuntimeProxy : BaseProxy, IRuntimeProxy public class RuntimeProxy : BaseProxy, IRuntimeProxy
{ {
public RuntimeProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger) public RuntimeProxy(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner) : base(address, factory, logger, owner)
{ {
} }

View file

@ -7,6 +7,8 @@
*/ */
using System; using System;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -19,9 +21,8 @@ namespace SafeExamBrowser.Communication.Proxies
public class ServiceProxy : BaseProxy, IServiceProxy public class ServiceProxy : BaseProxy, IServiceProxy
{ {
public bool Ignore { private get; set; } public bool Ignore { private get; set; }
public new bool IsConnected => base.IsConnected;
public ServiceProxy(string address, IProxyObjectFactory factory, ILogger logger) : base(address, factory, logger) public ServiceProxy(string address, IProxyObjectFactory factory, ILogger logger, Interlocutor owner) : base(address, factory, logger, owner)
{ {
} }
@ -52,10 +53,28 @@ namespace SafeExamBrowser.Communication.Proxies
return new CommunicationResult(true); return new CommunicationResult(true);
} }
// Implement service communication... try
// Send(new StartSessionMessage { Id = sessionId, Settings = settings }); {
var response = Send(new SessionStartMessage(configuration));
var success = IsAcknowledged(response);
throw new NotImplementedException(); if (success)
{
Logger.Debug("Service acknowledged session start.");
}
else
{
Logger.Error($"Service did not acknowledge session start! Received: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(StartSession)}'", e);
return new CommunicationResult(false);
}
} }
public CommunicationResult StopSession(Guid sessionId) public CommunicationResult StopSession(Guid sessionId)
@ -65,10 +84,28 @@ namespace SafeExamBrowser.Communication.Proxies
return new CommunicationResult(true); return new CommunicationResult(true);
} }
// Implement service communication... try
// Send(new StopSessionMessage { SessionId = sessionId }); {
var response = Send(new SessionStopMessage(sessionId));
var success = IsAcknowledged(response);
throw new NotImplementedException(); if (success)
{
Logger.Debug("Service acknowledged session stop.");
}
else
{
Logger.Error($"Service did not acknowledge session stop! Received: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(StopSession)}'", e);
return new CommunicationResult(false);
}
} }
private bool IgnoreOperation(string operationName) private bool IgnoreOperation(string operationName)

View file

@ -60,6 +60,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Proxies\BaseProxy.cs" /> <Compile Include="Proxies\BaseProxy.cs" />
<Compile Include="Proxies\ClientProxy.cs" /> <Compile Include="Proxies\ClientProxy.cs" />
<Compile Include="Proxies\ProxyFactory.cs" />
<Compile Include="Proxies\ProxyObjectFactory.cs" /> <Compile Include="Proxies\ProxyObjectFactory.cs" />
<Compile Include="Proxies\RuntimeProxy.cs" /> <Compile Include="Proxies\RuntimeProxy.cs" />
<Compile Include="Proxies\ServiceProxy.cs" /> <Compile Include="Proxies\ServiceProxy.cs" />

View file

@ -278,7 +278,6 @@ namespace SafeExamBrowser.Configuration.UnitTests
Assert.IsNull(configuration.Settings); Assert.IsNull(configuration.Settings);
Assert.IsInstanceOfType(configuration.AppConfig, typeof(AppConfig)); Assert.IsInstanceOfType(configuration.AppConfig, typeof(AppConfig));
Assert.IsInstanceOfType(configuration.ClientAuthenticationToken, typeof(Guid)); Assert.IsInstanceOfType(configuration.ClientAuthenticationToken, typeof(Guid));
Assert.IsInstanceOfType(configuration.ServiceAuthenticationToken, typeof(Guid));
Assert.IsInstanceOfType(configuration.SessionId, typeof(Guid)); Assert.IsInstanceOfType(configuration.SessionId, typeof(Guid));
} }
@ -292,14 +291,18 @@ namespace SafeExamBrowser.Configuration.UnitTests
var runtimeAddress = appConfig.RuntimeAddress; var runtimeAddress = appConfig.RuntimeAddress;
var runtimeId = appConfig.RuntimeId; var runtimeId = appConfig.RuntimeId;
var runtimeLogFilePath = appConfig.RuntimeLogFilePath; var runtimeLogFilePath = appConfig.RuntimeLogFilePath;
var serviceEventName = appConfig.ServiceEventName;
var configuration = sut.InitializeSessionConfiguration(); var configuration = sut.InitializeSessionConfiguration();
Assert.AreNotEqual(configuration.AppConfig.ClientAddress, clientAddress);
Assert.AreNotEqual(configuration.AppConfig.ClientId, clientId);
Assert.AreEqual(configuration.AppConfig.ClientLogFilePath, clientLogFilePath); Assert.AreEqual(configuration.AppConfig.ClientLogFilePath, clientLogFilePath);
Assert.AreEqual(configuration.AppConfig.RuntimeAddress, runtimeAddress); Assert.AreEqual(configuration.AppConfig.RuntimeAddress, runtimeAddress);
Assert.AreEqual(configuration.AppConfig.RuntimeId, runtimeId); Assert.AreEqual(configuration.AppConfig.RuntimeId, runtimeId);
Assert.AreEqual(configuration.AppConfig.RuntimeLogFilePath, runtimeLogFilePath); Assert.AreEqual(configuration.AppConfig.RuntimeLogFilePath, runtimeLogFilePath);
Assert.AreNotEqual(configuration.AppConfig.ClientAddress, clientAddress);
Assert.AreNotEqual(configuration.AppConfig.ClientId, clientId);
Assert.AreNotEqual(configuration.AppConfig.ServiceEventName, serviceEventName);
} }
[TestMethod] [TestMethod]
@ -312,10 +315,8 @@ namespace SafeExamBrowser.Configuration.UnitTests
Assert.AreNotEqual(firstSession.SessionId, secondSession.SessionId); Assert.AreNotEqual(firstSession.SessionId, secondSession.SessionId);
Assert.AreNotEqual(firstSession.ClientAuthenticationToken, secondSession.ClientAuthenticationToken); Assert.AreNotEqual(firstSession.ClientAuthenticationToken, secondSession.ClientAuthenticationToken);
Assert.AreNotEqual(firstSession.ServiceAuthenticationToken, secondSession.ServiceAuthenticationToken);
Assert.AreNotEqual(secondSession.SessionId, thirdSession.SessionId); Assert.AreNotEqual(secondSession.SessionId, thirdSession.SessionId);
Assert.AreNotEqual(secondSession.ClientAuthenticationToken, thirdSession.ClientAuthenticationToken); Assert.AreNotEqual(secondSession.ClientAuthenticationToken, thirdSession.ClientAuthenticationToken);
Assert.AreNotEqual(secondSession.ServiceAuthenticationToken, thirdSession.ServiceAuthenticationToken);
} }
private void RegisterModules() private void RegisterModules()

View file

@ -78,10 +78,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
appConfig.ClientId = Guid.NewGuid(); appConfig.ClientId = Guid.NewGuid();
appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}"; appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}";
appConfig.ServiceEventName = $@"Global\{nameof(SafeExamBrowser)}-{Guid.NewGuid()}";
configuration.AppConfig = appConfig.Clone(); configuration.AppConfig = appConfig.Clone();
configuration.ClientAuthenticationToken = Guid.NewGuid(); configuration.ClientAuthenticationToken = Guid.NewGuid();
configuration.ServiceAuthenticationToken = Guid.NewGuid();
configuration.SessionId = Guid.NewGuid(); configuration.SessionId = Guid.NewGuid();
return configuration; return configuration;

View file

@ -16,5 +16,9 @@ namespace SafeExamBrowser.Contracts.Communication.Data
[Serializable] [Serializable]
public class DisconnectionMessage : Message public class DisconnectionMessage : Message
{ {
/// <summary>
/// Identifies the component sending the message.
/// </summary>
public Interlocutor Interlocutor { get; set; }
} }
} }

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Contracts.Communication.Data
{
/// <summary>
/// This message is transmitted to the service to request the initialization of a new session.
/// </summary>
[Serializable]
public class SessionStartMessage : Message
{
/// <summary>
/// The configuration to be used by the service.
/// </summary>
public ServiceConfiguration Configuration { get; }
public SessionStartMessage(ServiceConfiguration configuration)
{
Configuration = configuration;
}
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Contracts.Communication.Data
{
/// <summary>
/// This message is transmitted to the service to request the termination of a currently running session.
/// </summary>
[Serializable]
public class SessionStopMessage : Message
{
public Guid SessionId { get; }
public SessionStopMessage(Guid sessionId)
{
SessionId = sessionId;
}
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Contracts.Communication.Events
{
/// <summary>
/// The event arguments used for the session start event fired by the <see cref="Hosts.IServiceHost"/>.
/// </summary>
public class SessionStartEventArgs : CommunicationEventArgs
{
/// <summary>
/// The configuration to be used for the new session.
/// </summary>
public ServiceConfiguration Configuration { get; set; }
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Contracts.Communication.Events
{
/// <summary>
/// The event arguments used for the session stop event fired by the <see cref="Hosts.IServiceHost"/>.
/// </summary>
public class SessionStopEventArgs : CommunicationEventArgs
{
/// <summary>
/// The identifier of the session to be stopped.
/// </summary>
public Guid SessionId { get; set; }
}
}

View file

@ -24,7 +24,7 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// <summary> /// <summary>
/// The token used for initial authentication. /// The token used for initial authentication.
/// </summary> /// </summary>
Guid AuthenticationToken { set; } Guid? AuthenticationToken { set; }
/// <summary> /// <summary>
/// Event fired when the client disconnected from the runtime. /// Event fired when the client disconnected from the runtime.
@ -56,26 +56,6 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// </summary> /// </summary>
event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested; event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
/// <summary>
/// Event fired when the service disconnected from the runtime.
/// </summary>
event CommunicationEventHandler ServiceDisconnected;
/// <summary>
/// Event fired when the service has experienced a critical failure.
/// </summary>
event CommunicationEventHandler ServiceFailed;
/// <summary>
/// Event fired when the service has successfully started a new session.
/// </summary>
event CommunicationEventHandler ServiceSessionStarted;
/// <summary>
/// Event fired when the service has successfully stopped the currently running session.
/// </summary>
event CommunicationEventHandler ServiceSessionStopped;
/// <summary> /// <summary>
/// Event fired when the client requests to shut down the application. /// Event fired when the client requests to shut down the application.
/// </summary> /// </summary>

View file

@ -6,6 +6,8 @@
* 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 SafeExamBrowser.Contracts.Communication.Events;
namespace SafeExamBrowser.Contracts.Communication.Hosts namespace SafeExamBrowser.Contracts.Communication.Hosts
{ {
/// <summary> /// <summary>
@ -13,5 +15,19 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// </summary> /// </summary>
public interface IServiceHost : ICommunicationHost public interface IServiceHost : ICommunicationHost
{ {
/// <summary>
/// Determines whether another application component may establish a connection with the host.
/// </summary>
bool AllowConnection { get; set; }
/// <summary>
/// Event fired when the runtime requested to start a new session.
/// </summary>
event CommunicationEventHandler<SessionStartEventArgs> SessionStartRequested;
/// <summary>
/// Event fired when the runtime requested to stop a running session.
/// </summary>
event CommunicationEventHandler<SessionStopEventArgs> SessionStopRequested;
} }
} }

View file

@ -28,6 +28,8 @@ namespace SafeExamBrowser.Contracts.Communication
[ServiceKnownType(typeof(ReconfigurationMessage))] [ServiceKnownType(typeof(ReconfigurationMessage))]
[ServiceKnownType(typeof(ReconfigurationDeniedMessage))] [ServiceKnownType(typeof(ReconfigurationDeniedMessage))]
[ServiceKnownType(typeof(ServiceConfiguration))] [ServiceKnownType(typeof(ServiceConfiguration))]
[ServiceKnownType(typeof(SessionStartMessage))]
[ServiceKnownType(typeof(SessionStopMessage))]
[ServiceKnownType(typeof(SimpleMessage))] [ServiceKnownType(typeof(SimpleMessage))]
[ServiceKnownType(typeof(SimpleResponse))] [ServiceKnownType(typeof(SimpleResponse))]
public interface ICommunication public interface ICommunication

View file

@ -17,6 +17,11 @@ namespace SafeExamBrowser.Contracts.Communication
/// </summary> /// </summary>
public interface ICommunicationProxy public interface ICommunicationProxy
{ {
/// <summary>
/// Indicates whether a connection to the host has been established.
/// </summary>
bool IsConnected { get; }
/// <summary> /// <summary>
/// Fired when the connection to the host was lost, e.g. if a ping request failed or a communication fault occurred. /// Fired when the connection to the host was lost, e.g. if a ping request failed or a communication fault occurred.
/// </summary> /// </summary>

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Contracts.Communication
{
/// <summary>
/// Defines all possible interlocutors for inter-process communication within the application.
/// </summary>
[Serializable]
public enum Interlocutor
{
/// <summary>
/// The interlocutor is not an application component.
/// </summary>
Unknown = 0,
/// <summary>
/// The client application component.
/// </summary>
Client,
/// <summary>
/// The runtime application component.
/// </summary>
Runtime,
/// <summary>
/// The service application component.
/// </summary>
Service
}
}

View file

@ -14,8 +14,8 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
public interface IProxyFactory public interface IProxyFactory
{ {
/// <summary> /// <summary>
/// Creates a new <see cref="IClientProxy"/> for the given endpoint address. /// Creates a new <see cref="IClientProxy"/> for the given endpoint address and owner.
/// </summary> /// </summary>
IClientProxy CreateClientProxy(string address); IClientProxy CreateClientProxy(string address, Interlocutor owner);
} }
} }

View file

@ -22,11 +22,6 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// </summary> /// </summary>
bool Ignore { set; } bool Ignore { set; }
/// <summary>
/// Indicates whether a connection to the communication host of the service has been established.
/// </summary>
bool IsConnected { get; }
/// <summary> /// <summary>
/// Instructs the service to start a new session according to the given configuration. /// Instructs the service to start a new session according to the given configuration.
/// </summary> /// </summary>

View file

@ -116,6 +116,11 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary> /// </summary>
public string ServiceAddress { get; set; } public string ServiceAddress { get; set; }
/// <summary>
/// The name of the global inter-process synchronization event hosted by the service.
/// </summary>
public string ServiceEventName { get; set; }
/// <summary> /// <summary>
/// Creates a shallow clone. /// Creates a shallow clone.
/// </summary> /// </summary>

View file

@ -21,11 +21,6 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary> /// </summary>
public AppConfig AppConfig { get; set; } public AppConfig AppConfig { get; set; }
/// <summary>
/// The token used for initial authentication with the runtime.
/// </summary>
public Guid AuthenticationToken { get; set; }
/// <summary> /// <summary>
/// The unique identifier for the current session. /// The unique identifier for the current session.
/// </summary> /// </summary>

View file

@ -25,11 +25,6 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary> /// </summary>
public Guid ClientAuthenticationToken { get; set; } public Guid ClientAuthenticationToken { get; set; }
/// <summary>
/// The token used for initial communication authentication with the service.
/// </summary>
public Guid ServiceAuthenticationToken { get; set; }
/// <summary> /// <summary>
/// The unique session identifier. /// The unique session identifier.
/// </summary> /// </summary>

View file

@ -56,10 +56,15 @@
<Compile Include="Browser\ProgressChangedEventHandler.cs" /> <Compile Include="Browser\ProgressChangedEventHandler.cs" />
<Compile Include="Communication\Data\MessageBoxReplyMessage.cs" /> <Compile Include="Communication\Data\MessageBoxReplyMessage.cs" />
<Compile Include="Communication\Data\MessageBoxRequestMessage.cs" /> <Compile Include="Communication\Data\MessageBoxRequestMessage.cs" />
<Compile Include="Communication\Data\SessionStartMessage.cs" />
<Compile Include="Communication\Data\SessionStopMessage.cs" />
<Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" /> <Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" />
<Compile Include="Communication\Events\MessageBoxReplyEventArgs.cs" /> <Compile Include="Communication\Events\MessageBoxReplyEventArgs.cs" />
<Compile Include="Communication\Events\MessageBoxRequestEventArgs.cs" /> <Compile Include="Communication\Events\MessageBoxRequestEventArgs.cs" />
<Compile Include="Communication\Events\SessionStartEventArgs.cs" />
<Compile Include="Communication\Events\SessionStopEventArgs.cs" />
<Compile Include="Communication\Hosts\IServiceHost.cs" /> <Compile Include="Communication\Hosts\IServiceHost.cs" />
<Compile Include="Communication\Interlocutor.cs" />
<Compile Include="Configuration\ClientConfiguration.cs" /> <Compile Include="Configuration\ClientConfiguration.cs" />
<Compile Include="Configuration\Cryptography\EncryptionParameters.cs" /> <Compile Include="Configuration\Cryptography\EncryptionParameters.cs" />
<Compile Include="Configuration\Cryptography\ICertificateStore.cs" /> <Compile Include="Configuration\Cryptography\ICertificateStore.cs" />

View file

@ -43,7 +43,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication
} }
[TestMethod] [TestMethod]
public void MustOnlyAllowConnectionIfTokenIsValid() public void MustAllowConnectionIfTokenIsValid()
{ {
var token = Guid.NewGuid(); var token = Guid.NewGuid();
@ -70,6 +70,19 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication
Assert.IsFalse(response.ConnectionEstablished); Assert.IsFalse(response.ConnectionEstablished);
} }
[TestMethod]
public void MustRejectConnectionIfNoAuthenticationTokenSet()
{
var token = Guid.Empty;
sut.AllowConnection = true;
var response = sut.Connect(token);
Assert.IsNotNull(response);
Assert.IsFalse(response.ConnectionEstablished);
}
[TestMethod] [TestMethod]
public void MustOnlyAllowOneConcurrentConnection() public void MustOnlyAllowOneConcurrentConnection()
{ {
@ -94,7 +107,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication
} }
[TestMethod] [TestMethod]
public void MustCorrectlyDisconnect() public void MustCorrectlyDisconnectClient()
{ {
var disconnected = false; var disconnected = false;
var token = Guid.NewGuid(); var token = Guid.NewGuid();
@ -104,7 +117,12 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication
sut.ClientDisconnected += () => disconnected = true; sut.ClientDisconnected += () => disconnected = true;
var connectionResponse = sut.Connect(token); var connectionResponse = sut.Connect(token);
var response = sut.Disconnect(new DisconnectionMessage { CommunicationToken = connectionResponse.CommunicationToken.Value }); var message = new DisconnectionMessage
{
CommunicationToken = connectionResponse.CommunicationToken.Value,
Interlocutor = Interlocutor.Client
};
var response = sut.Disconnect(message);
Assert.IsNotNull(response); Assert.IsNotNull(response);
Assert.IsTrue(disconnected); Assert.IsTrue(disconnected);

View file

@ -10,6 +10,7 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
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.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
@ -63,7 +64,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
session.Settings = settings; session.Settings = settings;
sessionContext.Current = session; sessionContext.Current = session;
sessionContext.Next = session; sessionContext.Next = session;
proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny<string>())).Returns(proxy.Object); proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny<string>(), It.IsAny<Interlocutor>())).Returns(proxy.Object);
sut = new ClientOperation(logger.Object, processFactory.Object, proxyFactory.Object, runtimeHost.Object, sessionContext, 0); sut = new ClientOperation(logger.Object, processFactory.Object, proxyFactory.Object, runtimeHost.Object, sessionContext, 0);
} }

View file

@ -9,6 +9,7 @@
using System; using System;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
@ -60,7 +61,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
sessionContext.ClientProxy = proxy.Object; sessionContext.ClientProxy = proxy.Object;
sessionContext.Current = session; sessionContext.Current = session;
sessionContext.Next = session; sessionContext.Next = session;
proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny<string>())).Returns(proxy.Object); proxyFactory.Setup(f => f.CreateClientProxy(It.IsAny<string>(), It.IsAny<Interlocutor>())).Returns(proxy.Object);
sut = new ClientTerminationOperation(logger.Object, processFactory.Object, proxyFactory.Object, runtimeHost.Object, sessionContext, 0); sut = new ClientTerminationOperation(logger.Object, processFactory.Object, proxyFactory.Object, runtimeHost.Object, sessionContext, 0);
} }

View file

@ -7,6 +7,7 @@
*/ */
using System; using System;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
@ -24,9 +25,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestClass] [TestClass]
public class ServiceOperationTests public class ServiceOperationTests
{ {
private AppConfig appConfig;
private Mock<ILogger> logger; private Mock<ILogger> logger;
private Mock<IRuntimeHost> runtimeHost; private Mock<IRuntimeHost> runtimeHost;
private Mock<IServiceProxy> service; private Mock<IServiceProxy> service;
private EventWaitHandle serviceEvent;
private SessionConfiguration session; private SessionConfiguration session;
private SessionContext sessionContext; private SessionContext sessionContext;
private Settings settings; private Settings settings;
@ -35,15 +38,22 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
{ {
var serviceEventName = $"{nameof(SafeExamBrowser)}-{nameof(ServiceOperationTests)}";
appConfig = new AppConfig();
logger = new Mock<ILogger>(); logger = new Mock<ILogger>();
runtimeHost = new Mock<IRuntimeHost>(); runtimeHost = new Mock<IRuntimeHost>();
service = new Mock<IServiceProxy>(); service = new Mock<IServiceProxy>();
serviceEvent = new EventWaitHandle(false, EventResetMode.AutoReset, serviceEventName);
session = new SessionConfiguration(); session = new SessionConfiguration();
sessionContext = new SessionContext(); sessionContext = new SessionContext();
settings = new Settings(); settings = new Settings();
appConfig.ServiceEventName = serviceEventName;
sessionContext.Current = session; sessionContext.Current = session;
sessionContext.Current.AppConfig = appConfig;
sessionContext.Next = session; sessionContext.Next = session;
sessionContext.Next.AppConfig = appConfig;
session.Settings = settings; session.Settings = settings;
settings.ServicePolicy = ServicePolicy.Mandatory; settings.ServicePolicy = ServicePolicy.Mandatory;
@ -71,10 +81,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{ {
service.SetupGet(s => s.IsConnected).Returns(true); service.SetupGet(s => s.IsConnected).Returns(true);
service.Setup(s => s.Connect(null, true)).Returns(true); service.Setup(s => s.Connect(null, true)).Returns(true);
service service.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStarted += null));
var result = sut.Perform(); var result = sut.Perform();
@ -88,10 +95,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{ {
service.SetupGet(s => s.IsConnected).Returns(true); service.SetupGet(s => s.IsConnected).Returns(true);
service.Setup(s => s.Connect(null, true)).Returns(true); service.Setup(s => s.Connect(null, true)).Returns(true);
service service.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>())).Returns(new CommunicationResult(true));
.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceFailed += null));
var result = sut.Perform(); var result = sut.Perform();
@ -197,10 +201,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod] [TestMethod]
public void Repeat_MustStopCurrentAndStartNewSession() public void Repeat_MustStopCurrentAndStartNewSession()
{ {
service service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
PerformNormally(); PerformNormally();
@ -222,10 +223,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
service.Reset(); service.Reset();
service.SetupGet(s => s.IsConnected).Returns(false); service.SetupGet(s => s.IsConnected).Returns(false);
service.Setup(s => s.Connect(null, true)).Returns(true); service.Setup(s => s.Connect(null, true)).Returns(true);
service service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true));
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
var result = sut.Repeat(); var result = sut.Repeat();
@ -251,58 +249,49 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
} }
[TestMethod] [TestMethod]
public void Revert_MustDisconnect() public void Repeat_MustFailIfSessionNotStoppedWithinTimeout()
{
service.Setup(s => s.Disconnect()).Returns(true).Callback(() => runtimeHost.Raise(h => h.ServiceDisconnected += null));
service
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
PerformNormally();
var result = sut.Revert();
service.Verify(s => s.Disconnect(), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Revert_MustFailIfServiceNotDisconnectedWithinTimeout()
{ {
const int TIMEOUT = 50; const int TIMEOUT = 50;
var after = default(DateTime); var after = default(DateTime);
var before = default(DateTime); var before = default(DateTime);
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true));
sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, TIMEOUT); sut = new ServiceOperation(logger.Object, runtimeHost.Object, service.Object, sessionContext, TIMEOUT);
service.Setup(s => s.Disconnect()).Returns(true);
service
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
PerformNormally(); PerformNormally();
before = DateTime.Now; before = DateTime.Now;
var result = sut.Revert(); var result = sut.Repeat();
after = DateTime.Now; after = DateTime.Now;
service.Verify(s => s.Disconnect(), Times.Once); service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Once);
service.Verify(s => s.Disconnect(), Times.Never);
Assert.AreEqual(OperationResult.Failed, result); Assert.AreEqual(OperationResult.Failed, result);
Assert.IsTrue(after - before >= new TimeSpan(0, 0, 0, 0, TIMEOUT)); Assert.IsTrue(after - before >= new TimeSpan(0, 0, 0, 0, TIMEOUT));
} }
[TestMethod]
public void Revert_MustDisconnect()
{
service.Setup(s => s.Disconnect()).Returns(true);
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
PerformNormally();
var result = sut.Revert();
service.Verify(s => s.Disconnect(), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod] [TestMethod]
public void Revert_MustStopSessionIfConnected() public void Revert_MustStopSessionIfConnected()
{ {
service.Setup(s => s.Disconnect()).Returns(true).Callback(() => runtimeHost.Raise(h => h.ServiceDisconnected += null)); service.Setup(s => s.Disconnect()).Returns(true);
service service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
PerformNormally(); PerformNormally();
@ -332,10 +321,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod] [TestMethod]
public void Revert_MustFailIfSessionStopUnsuccessful() public void Revert_MustFailIfSessionStopUnsuccessful()
{ {
service service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true));
.Setup(s => s.StopSession(It.IsAny<Guid>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceFailed += null));
PerformNormally(); PerformNormally();
@ -377,6 +363,20 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
Assert.AreEqual(OperationResult.Success, result); Assert.AreEqual(OperationResult.Success, result);
} }
[TestMethod]
public void Revert_MustNotStopSessionIfNoSessionRunning()
{
sessionContext.Current = null;
service.SetupGet(s => s.IsConnected).Returns(true);
service.Setup(s => s.Disconnect()).Returns(true);
var result = sut.Revert();
service.Verify(s => s.StopSession(It.IsAny<Guid>()), Times.Never);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod] [TestMethod]
public void Revert_MustNotDisconnnectIfNotConnected() public void Revert_MustNotDisconnnectIfNotConnected()
{ {
@ -390,10 +390,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{ {
service.SetupGet(s => s.IsConnected).Returns(true); service.SetupGet(s => s.IsConnected).Returns(true);
service.Setup(s => s.Connect(null, true)).Returns(true); service.Setup(s => s.Connect(null, true)).Returns(true);
service service.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>()))
.Returns(new CommunicationResult(true))
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStarted += null));
sut.Perform(); sut.Perform();
} }

View file

@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Runtime.Operations; using SafeExamBrowser.Runtime.Operations;
@ -52,33 +53,41 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
session.ClientAuthenticationToken = token; session.ClientAuthenticationToken = token;
sut.Perform(); var result = sut.Perform();
configuration.Verify(c => c.InitializeSessionConfiguration(), Times.Once); configuration.Verify(c => c.InitializeSessionConfiguration(), Times.Once);
runtimeHost.VerifySet(r => r.AuthenticationToken = token, Times.Once);
Assert.AreEqual(OperationResult.Success, result);
Assert.IsNull(sessionContext.Current);
} }
[TestMethod] [TestMethod]
public void MustInitializeConfigurationOnRepeat() public void MustInitializeConfigurationOnRepeat()
{ {
var currentSession = new SessionConfiguration();
var token = Guid.NewGuid(); var token = Guid.NewGuid();
session.ClientAuthenticationToken = token; session.ClientAuthenticationToken = token;
sessionContext.Current = currentSession;
sut.Repeat(); var result = sut.Repeat();
configuration.Verify(c => c.InitializeSessionConfiguration(), Times.Once); configuration.Verify(c => c.InitializeSessionConfiguration(), Times.Once);
runtimeHost.VerifySet(r => r.AuthenticationToken = token, Times.Once);
Assert.AreEqual(OperationResult.Success, result);
Assert.AreSame(currentSession,sessionContext.Current);
} }
[TestMethod] [TestMethod]
public void MustDoNothingOnRevert() public void MustDoNothingOnRevert()
{ {
sut.Revert(); var result = sut.Revert();
configuration.VerifyNoOtherCalls(); configuration.VerifyNoOtherCalls();
logger.VerifyNoOtherCalls(); logger.VerifyNoOtherCalls();
runtimeHost.VerifyNoOtherCalls(); runtimeHost.VerifyNoOtherCalls();
Assert.AreEqual(OperationResult.Success, result);
} }
} }
} }

View file

@ -1,32 +0,0 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Communication.Proxies;
using SafeExamBrowser.Logging;
namespace SafeExamBrowser.Runtime.Communication
{
internal class ProxyFactory : IProxyFactory
{
private IProxyObjectFactory factory;
private ILogger logger;
public ProxyFactory(IProxyObjectFactory factory, ILogger logger)
{
this.factory = factory;
this.logger = logger;
}
public IClientProxy CreateClientProxy(string address)
{
return new ClientProxy(address, factory, new ModuleLogger(logger, nameof(ClientProxy)));
}
}
}

View file

@ -8,6 +8,7 @@
using System; using System;
using SafeExamBrowser.Communication.Hosts; using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
@ -18,7 +19,7 @@ namespace SafeExamBrowser.Runtime.Communication
internal class RuntimeHost : BaseHost, IRuntimeHost internal class RuntimeHost : BaseHost, IRuntimeHost
{ {
public bool AllowConnection { get; set; } public bool AllowConnection { get; set; }
public Guid AuthenticationToken { private get; set; } public Guid? AuthenticationToken { private get; set; }
public event CommunicationEventHandler ClientDisconnected; public event CommunicationEventHandler ClientDisconnected;
public event CommunicationEventHandler ClientReady; public event CommunicationEventHandler ClientReady;
@ -26,10 +27,6 @@ namespace SafeExamBrowser.Runtime.Communication
public event CommunicationEventHandler<MessageBoxReplyEventArgs> MessageBoxReplyReceived; public event CommunicationEventHandler<MessageBoxReplyEventArgs> MessageBoxReplyReceived;
public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived; public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested; public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
public event CommunicationEventHandler ServiceDisconnected;
public event CommunicationEventHandler ServiceFailed;
public event CommunicationEventHandler ServiceSessionStarted;
public event CommunicationEventHandler ServiceSessionStopped;
public event CommunicationEventHandler ShutdownRequested; public event CommunicationEventHandler ShutdownRequested;
public RuntimeHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms) public RuntimeHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms)
@ -38,7 +35,7 @@ namespace SafeExamBrowser.Runtime.Communication
protected override bool OnConnect(Guid? token = null) protected override bool OnConnect(Guid? token = null)
{ {
var authenticated = AuthenticationToken == token; var authenticated = AuthenticationToken.HasValue && AuthenticationToken == token;
var accepted = AllowConnection && authenticated; var accepted = AllowConnection && authenticated;
if (accepted) if (accepted)
@ -49,9 +46,12 @@ namespace SafeExamBrowser.Runtime.Communication
return accepted; return accepted;
} }
protected override void OnDisconnect() protected override void OnDisconnect(Interlocutor interlocutor)
{ {
ClientDisconnected?.Invoke(); if (interlocutor == Interlocutor.Client)
{
ClientDisconnected?.Invoke();
}
} }
protected override Response OnReceive(Message message) protected override Response OnReceive(Message message)

View file

@ -17,6 +17,7 @@ using SafeExamBrowser.Configuration.Cryptography;
using SafeExamBrowser.Configuration.DataCompression; using SafeExamBrowser.Configuration.DataCompression;
using SafeExamBrowser.Configuration.DataFormats; using SafeExamBrowser.Configuration.DataFormats;
using SafeExamBrowser.Configuration.DataResources; using SafeExamBrowser.Configuration.DataResources;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
@ -65,9 +66,9 @@ namespace SafeExamBrowser.Runtime
var desktopFactory = new DesktopFactory(ModuleLogger(nameof(DesktopFactory))); var desktopFactory = new DesktopFactory(ModuleLogger(nameof(DesktopFactory)));
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods); var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger); var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), ModuleLogger(nameof(ProxyFactory)));
var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS); var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS);
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy))); var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
var sessionContext = new SessionContext(); var sessionContext = new SessionContext();
var uiFactory = new UserInterfaceFactory(text); var uiFactory = new UserInterfaceFactory(text);

View file

@ -6,7 +6,9 @@
* 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 System.Threading; using System.Threading;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
@ -111,6 +113,7 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Info("Starting new client process..."); logger.Info("Starting new client process...");
runtimeHost.AllowConnection = true; runtimeHost.AllowConnection = true;
runtimeHost.AuthenticationToken = Context.Next.ClientAuthenticationToken;
runtimeHost.ClientReady += clientReadyEventHandler; runtimeHost.ClientReady += clientReadyEventHandler;
ClientProcess = processFactory.StartNew(executablePath, logFilePath, logLevel, runtimeHostUri, authenticationToken, uiMode); ClientProcess = processFactory.StartNew(executablePath, logFilePath, logLevel, runtimeHostUri, authenticationToken, uiMode);
ClientProcess.Terminated += clientTerminatedEventHandler; ClientProcess.Terminated += clientTerminatedEventHandler;
@ -119,6 +122,7 @@ namespace SafeExamBrowser.Runtime.Operations
clientReady = clientReadyEvent.WaitOne(timeout_ms); clientReady = clientReadyEvent.WaitOne(timeout_ms);
runtimeHost.AllowConnection = false; runtimeHost.AllowConnection = false;
runtimeHost.AuthenticationToken = default(Guid?);
runtimeHost.ClientReady -= clientReadyEventHandler; runtimeHost.ClientReady -= clientReadyEventHandler;
ClientProcess.Terminated -= clientTerminatedEventHandler; ClientProcess.Terminated -= clientTerminatedEventHandler;
@ -145,7 +149,7 @@ namespace SafeExamBrowser.Runtime.Operations
var success = false; var success = false;
logger.Info("Client has been successfully started and initialized. Creating communication proxy for client host..."); logger.Info("Client has been successfully started and initialized. Creating communication proxy for client host...");
ClientProxy = proxyFactory.CreateClientProxy(Context.Next.AppConfig.ClientAddress); ClientProxy = proxyFactory.CreateClientProxy(Context.Next.AppConfig.ClientAddress, Interlocutor.Runtime);
if (ClientProxy.Connect(Context.Next.ClientAuthenticationToken)) if (ClientProxy.Connect(Context.Next.ClientAuthenticationToken))
{ {

View file

@ -6,8 +6,9 @@
* 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 System.Security.AccessControl;
using System.Threading; using System.Threading;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
@ -46,7 +47,7 @@ namespace SafeExamBrowser.Runtime.Operations
public override OperationResult Perform() public override OperationResult Perform()
{ {
logger.Info($"Initializing service session..."); logger.Info($"Initializing service...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
var success = TryEstablishConnection(); var success = TryEstablishConnection();
@ -61,7 +62,7 @@ namespace SafeExamBrowser.Runtime.Operations
public override OperationResult Repeat() public override OperationResult Repeat()
{ {
logger.Info($"Initializing new service session..."); logger.Info($"Initializing service...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
var success = false; var success = false;
@ -85,14 +86,18 @@ namespace SafeExamBrowser.Runtime.Operations
public override OperationResult Revert() public override OperationResult Revert()
{ {
logger.Info("Finalizing service session..."); logger.Info("Finalizing service...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession); StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession);
var success = true; var success = true;
if (service.IsConnected) if (service.IsConnected)
{ {
success &= TryStopSession(); if (Context.Current != null)
{
success &= TryStopSession();
}
success &= TryTerminateConnection(); success &= TryTerminateConnection();
} }
@ -137,36 +142,18 @@ namespace SafeExamBrowser.Runtime.Operations
private bool TryTerminateConnection() private bool TryTerminateConnection()
{ {
var serviceEvent = new AutoResetEvent(false); var disconnected = service.Disconnect();
var serviceEventHandler = new CommunicationEventHandler(() => serviceEvent.Set());
runtimeHost.ServiceDisconnected += serviceEventHandler; if (disconnected)
var success = service.Disconnect();
if (success)
{ {
logger.Info("Successfully disconnected from service. Waiting for service to disconnect..."); logger.Info("Successfully disconnected from service.");
success = serviceEvent.WaitOne(timeout_ms);
if (success)
{
logger.Info("Service disconnected successfully.");
}
else
{
logger.Error($"Service failed to disconnect within {timeout_ms / 1000} seconds!");
}
} }
else else
{ {
logger.Error("Failed to disconnect from service!"); logger.Error("Failed to disconnect from service!");
} }
runtimeHost.ServiceDisconnected -= serviceEventHandler; return disconnected;
return success;
} }
private bool TryStartSession() private bool TryStartSession()
@ -174,18 +161,10 @@ namespace SafeExamBrowser.Runtime.Operations
var configuration = new ServiceConfiguration var configuration = new ServiceConfiguration
{ {
AppConfig = Context.Next.AppConfig, AppConfig = Context.Next.AppConfig,
AuthenticationToken = Context.Next.ServiceAuthenticationToken,
SessionId = Context.Next.SessionId, SessionId = Context.Next.SessionId,
Settings = Context.Next.Settings Settings = Context.Next.Settings
}; };
var failure = false; var started = false;
var success = false;
var serviceEvent = new AutoResetEvent(false);
var failureEventHandler = new CommunicationEventHandler(() => { failure = true; serviceEvent.Set(); });
var successEventHandler = new CommunicationEventHandler(() => { success = true; serviceEvent.Set(); });
runtimeHost.ServiceFailed += failureEventHandler;
runtimeHost.ServiceSessionStarted += successEventHandler;
logger.Info("Starting new service session..."); logger.Info("Starting new service session...");
@ -193,16 +172,12 @@ namespace SafeExamBrowser.Runtime.Operations
if (communication.Success) if (communication.Success)
{ {
serviceEvent.WaitOne(timeout_ms); started = TryWaitForServiceEvent(Context.Next.AppConfig.ServiceEventName);
if (success) if (started)
{ {
logger.Info("Successfully started new service session."); logger.Info("Successfully started new service session.");
} }
else if (failure)
{
logger.Error("An error occurred while attempting to start a new service session! Please check the service log for further information.");
}
else else
{ {
logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!"); logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!");
@ -213,22 +188,12 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Error("Failed to communicate session start to service!"); logger.Error("Failed to communicate session start to service!");
} }
runtimeHost.ServiceFailed -= failureEventHandler; return started;
runtimeHost.ServiceSessionStarted -= successEventHandler;
return success;
} }
private bool TryStopSession() private bool TryStopSession()
{ {
var failure = false; var stopped = false;
var success = false;
var serviceEvent = new AutoResetEvent(false);
var failureEventHandler = new CommunicationEventHandler(() => { failure = true; serviceEvent.Set(); });
var successEventHandler = new CommunicationEventHandler(() => { success = true; serviceEvent.Set(); });
runtimeHost.ServiceFailed += failureEventHandler;
runtimeHost.ServiceSessionStopped += successEventHandler;
logger.Info("Stopping current service session..."); logger.Info("Stopping current service session...");
@ -236,16 +201,12 @@ namespace SafeExamBrowser.Runtime.Operations
if (communication.Success) if (communication.Success)
{ {
serviceEvent.WaitOne(timeout_ms); stopped = TryWaitForServiceEvent(Context.Current.AppConfig.ServiceEventName);
if (success) if (stopped)
{ {
logger.Info("Successfully stopped service session."); logger.Info("Successfully stopped service session.");
} }
else if (failure)
{
logger.Error("An error occurred while attempting to stop the current service session! Please check the service log for further information.");
}
else else
{ {
logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!"); logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!");
@ -256,10 +217,31 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Error("Failed to communicate session stop to service!"); logger.Error("Failed to communicate session stop to service!");
} }
runtimeHost.ServiceFailed -= failureEventHandler; return stopped;
runtimeHost.ServiceSessionStopped -= successEventHandler; }
return success; private bool TryWaitForServiceEvent(string eventName)
{
var serviceEvent = default(EventWaitHandle);
var startTime = DateTime.Now;
do
{
if (EventWaitHandle.TryOpenExisting(eventName, EventWaitHandleRights.Synchronize, out serviceEvent))
{
break;
}
} while (startTime.AddMilliseconds(timeout_ms) > DateTime.Now);
if (serviceEvent != default(EventWaitHandle))
{
using (serviceEvent)
{
return serviceEvent.WaitOne(timeout_ms);
}
}
return false;
} }
} }
} }

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.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
@ -63,7 +62,6 @@ namespace SafeExamBrowser.Runtime.Operations
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSession); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSession);
Context.Next = configuration.InitializeSessionConfiguration(); Context.Next = configuration.InitializeSessionConfiguration();
runtimeHost.AuthenticationToken = Context.Next.ClientAuthenticationToken;
logger.Info($" -> Client-ID: {Context.Next.AppConfig.ClientId}"); logger.Info($" -> Client-ID: {Context.Next.AppConfig.ClientId}");
logger.Info($" -> Runtime-ID: {Context.Next.AppConfig.RuntimeId}"); logger.Info($" -> Runtime-ID: {Context.Next.AppConfig.RuntimeId}");

View file

@ -1,4 +1,12 @@
using SafeExamBrowser.Contracts.Core.OperationModel; /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.Core.OperationModel.Events;
namespace SafeExamBrowser.Runtime.Operations namespace SafeExamBrowser.Runtime.Operations

View file

@ -103,7 +103,6 @@
<Compile Include="Operations\SessionActivationOperation.cs" /> <Compile Include="Operations\SessionActivationOperation.cs" />
<Compile Include="Operations\SessionOperation.cs" /> <Compile Include="Operations\SessionOperation.cs" />
<Compile Include="Operations\SessionInitializationOperation.cs" /> <Compile Include="Operations\SessionInitializationOperation.cs" />
<Compile Include="Communication\ProxyFactory.cs" />
<Compile Include="Communication\RuntimeHost.cs" /> <Compile Include="Communication\RuntimeHost.cs" />
<Compile Include="CompositionRoot.cs" /> <Compile Include="CompositionRoot.cs" />
<Compile Include="Properties\AssemblyInfo.cs"> <Compile Include="Properties\AssemblyInfo.cs">

View file

@ -11,12 +11,14 @@ using Moq;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Service.UnitTests namespace SafeExamBrowser.Service.UnitTests
{ {
[TestClass] [TestClass]
public class ServiceControllerTests public class ServiceControllerTests
{ {
private Mock<ILogger> logger;
private Mock<IOperationSequence> bootstrapSequence; private Mock<IOperationSequence> bootstrapSequence;
private SessionContext sessionContext; private SessionContext sessionContext;
private Mock<IRepeatableOperationSequence> sessionSequence; private Mock<IRepeatableOperationSequence> sessionSequence;
@ -26,12 +28,13 @@ namespace SafeExamBrowser.Service.UnitTests
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
{ {
logger = new Mock<ILogger>();
bootstrapSequence = new Mock<IOperationSequence>(); bootstrapSequence = new Mock<IOperationSequence>();
sessionContext = new SessionContext(); sessionContext = new SessionContext();
sessionSequence = new Mock<IRepeatableOperationSequence>(); sessionSequence = new Mock<IRepeatableOperationSequence>();
serviceHost = new Mock<IServiceHost>(); serviceHost = new Mock<IServiceHost>();
sut = new ServiceController(bootstrapSequence.Object, sessionSequence.Object, serviceHost.Object, sessionContext); sut = new ServiceController(logger.Object, bootstrapSequence.Object, sessionSequence.Object, serviceHost.Object, sessionContext);
} }
[TestMethod] [TestMethod]
@ -39,7 +42,7 @@ namespace SafeExamBrowser.Service.UnitTests
{ {
bootstrapSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success); bootstrapSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success);
sessionSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success); sessionSequence.Setup(b => b.TryPerform()).Returns(OperationResult.Success);
sessionContext.Current = null; sessionContext.Configuration = null;
var success = sut.TryStart(); var success = sut.TryStart();
@ -66,7 +69,7 @@ namespace SafeExamBrowser.Service.UnitTests
bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order); bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order);
sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order); sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order);
sessionContext.Current = new ServiceConfiguration(); sessionContext.Configuration = new ServiceConfiguration();
sut.Terminate(); sut.Terminate();
@ -94,7 +97,7 @@ namespace SafeExamBrowser.Service.UnitTests
bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order); bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order);
sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order); sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order);
sessionContext.Current = null; sessionContext.Configuration = null;
sut.Terminate(); sut.Terminate();

View file

@ -8,7 +8,9 @@
using System; using System;
using SafeExamBrowser.Communication.Hosts; using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -16,28 +18,68 @@ namespace SafeExamBrowser.Service.Communication
{ {
internal class ServiceHost : BaseHost, IServiceHost internal class ServiceHost : BaseHost, IServiceHost
{ {
private readonly object @lock = new object();
private bool allowConnection;
public bool AllowConnection
{
get { lock (@lock) { return allowConnection; } }
set { lock (@lock) { allowConnection = value; } }
}
public event CommunicationEventHandler<SessionStartEventArgs> SessionStartRequested;
public event CommunicationEventHandler<SessionStopEventArgs> SessionStopRequested;
internal ServiceHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms) internal ServiceHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms)
{ {
AllowConnection = true;
} }
protected override bool OnConnect(Guid? token) protected override bool OnConnect(Guid? token)
{ {
throw new NotImplementedException(); lock (@lock)
{
var allow = AllowConnection;
if (allow)
{
AllowConnection = false;
}
return allow;
}
} }
protected override void OnDisconnect() protected override void OnDisconnect(Interlocutor interlocutor)
{ {
throw new NotImplementedException(); if (interlocutor == Interlocutor.Runtime)
{
lock (@lock)
{
AllowConnection = true;
}
}
} }
protected override Response OnReceive(Message message) protected override Response OnReceive(Message message)
{ {
throw new NotImplementedException(); switch (message)
{
case SessionStartMessage m:
SessionStartRequested?.InvokeAsync(new SessionStartEventArgs { Configuration = m.Configuration });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case SessionStopMessage m:
SessionStopRequested?.InvokeAsync(new SessionStopEventArgs { SessionId = m.SessionId });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }
protected override Response OnReceive(SimpleMessagePurport message) protected override Response OnReceive(SimpleMessagePurport message)
{ {
throw new NotImplementedException(); return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }
} }
} }

View file

@ -10,6 +10,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using SafeExamBrowser.Communication.Hosts; using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Communication.Proxies;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Service; using SafeExamBrowser.Contracts.Service;
@ -17,6 +18,7 @@ using SafeExamBrowser.Core.OperationModel;
using SafeExamBrowser.Core.Operations; using SafeExamBrowser.Core.Operations;
using SafeExamBrowser.Logging; using SafeExamBrowser.Logging;
using SafeExamBrowser.Service.Communication; using SafeExamBrowser.Service.Communication;
using SafeExamBrowser.Service.Operations;
namespace SafeExamBrowser.Service namespace SafeExamBrowser.Service
{ {
@ -33,25 +35,26 @@ namespace SafeExamBrowser.Service
InitializeLogging(); InitializeLogging();
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), new ModuleLogger(logger, nameof(ProxyFactory)));
var serviceHost = new ServiceHost(SERVICE_ADDRESS, new HostObjectFactory(), new ModuleLogger(logger, nameof(ServiceHost)), FIVE_SECONDS); var serviceHost = new ServiceHost(SERVICE_ADDRESS, new HostObjectFactory(), new ModuleLogger(logger, nameof(ServiceHost)), FIVE_SECONDS);
var sessionContext = new SessionContext(); var sessionContext = new SessionContext();
var bootstrapOperations = new Queue<IOperation>(); var bootstrapOperations = new Queue<IOperation>();
var sessionOperations = new Queue<IRepeatableOperation>(); var sessionOperations = new Queue<IOperation>();
// TODO: bootstrapOperations.Enqueue(new RestoreOperation()); // TODO: bootstrapOperations.Enqueue(new RestoreOperation());
bootstrapOperations.Enqueue(new CommunicationHostOperation(serviceHost, logger)); bootstrapOperations.Enqueue(new CommunicationHostOperation(serviceHost, logger));
bootstrapOperations.Enqueue(new ServiceEventCleanupOperation(logger, sessionContext));
// sessionOperations.Enqueue(new RuntimeConnectionOperation()); sessionOperations.Enqueue(new SessionInitializationOperation(logger, serviceHost, sessionContext));
// sessionOperations.Enqueue(new LogOperation()); // TODO: sessionOperations.Enqueue(new RegistryOperation());
// sessionOperations.Enqueue(new RegistryOperation()); // sessionOperations.Enqueue(new WindowsUpdateOperation());
// sessionOperations.Enqueue(new WindowsUpdateOperation()); sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext));
// sessionOperations.Enqueue(new SessionActivationOperation());
var bootstrapSequence = new OperationSequence(logger, bootstrapOperations); var bootstrapSequence = new OperationSequence(logger, bootstrapOperations);
var sessionSequence = new RepeatableOperationSequence(logger, sessionOperations); var sessionSequence = new OperationSequence(logger, sessionOperations);
ServiceController = new ServiceController(bootstrapSequence, sessionSequence, serviceHost, sessionContext); ServiceController = new ServiceController(logger, bootstrapSequence, sessionSequence, serviceHost, sessionContext);
} }
internal void LogStartupInformation() internal void LogStartupInformation()
@ -62,7 +65,6 @@ namespace SafeExamBrowser.Service
internal void LogShutdownInformation() internal void LogShutdownInformation()
{ {
logger?.Log(string.Empty);
logger?.Log($"# Service terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); logger?.Log($"# Service terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
} }

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Service.Operations
{
internal class ServiceEventCleanupOperation : IOperation
{
private ILogger logger;
private SessionContext sessionContext;
public event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public event StatusChangedEventHandler StatusChanged { add { } remove { } }
public ServiceEventCleanupOperation(ILogger logger, SessionContext sessionContext)
{
this.logger = logger;
this.sessionContext = sessionContext;
}
public OperationResult Perform()
{
return OperationResult.Success;
}
public OperationResult Revert()
{
if (sessionContext.EventWaitHandle != null)
{
logger.Info("Closing service event...");
sessionContext.EventWaitHandle.Close();
logger.Info("Service event successfully closed.");
}
return OperationResult.Success;
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Service.Operations
{
internal class SessionActivationOperation : SessionOperation
{
private ILogger logger;
public SessionActivationOperation(ILogger logger, SessionContext sessionContext) : base(sessionContext)
{
this.logger = logger;
}
public override OperationResult Perform()
{
var success = Context.EventWaitHandle.Set();
if (success)
{
logger.Info("Successfully informed runtime about new session activation.");
}
else
{
logger.Error("Failed to inform runtime about new session activation!");
}
return success ? OperationResult.Success : OperationResult.Failed;
}
public override OperationResult Revert()
{
return OperationResult.Success;
}
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Service.Operations
{
internal class SessionInitializationOperation : SessionOperation
{
private ILogger logger;
private IServiceHost serviceHost;
public SessionInitializationOperation(ILogger logger, IServiceHost serviceHost, SessionContext sessionContext) : base(sessionContext)
{
this.logger = logger;
this.serviceHost = serviceHost;
}
public override OperationResult Perform()
{
logger.Info("Initializing new session...");
serviceHost.AllowConnection = false;
logger.Info($" -> Client-ID: {Context.Configuration.AppConfig.ClientId}");
logger.Info($" -> Runtime-ID: {Context.Configuration.AppConfig.RuntimeId}");
logger.Info($" -> Session-ID: {Context.Configuration.SessionId}");
InitializeEventWaitHandle();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Finalizing current session...");
var success = Context.EventWaitHandle?.Set() == true;
if (success)
{
logger.Info("Successfully informed runtime about session termination.");
}
else
{
logger.Error("Failed to inform runtime about session termination!");
}
Context.Configuration = null;
serviceHost.AllowConnection = true;
return success ? OperationResult.Success : OperationResult.Failed;
}
private void InitializeEventWaitHandle()
{
var securityIdentifier = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
var accessRule = new EventWaitHandleAccessRule(securityIdentifier, EventWaitHandleRights.Synchronize, AccessControlType.Allow);
var security = new EventWaitHandleSecurity();
security.AddAccessRule(accessRule);
if (Context.EventWaitHandle != null)
{
logger.Info("Closing service event from previous session...");
Context.EventWaitHandle.Close();
logger.Info("Service event successfully closed.");
}
logger.Info("Attempting to create new service event...");
Context.EventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Context.Configuration.AppConfig.ServiceEventName, out _, security);
logger.Info("Service event successfully created.");
}
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
namespace SafeExamBrowser.Service.Operations
{
/// <summary>
/// The base implementation to be used for all operations in the session operation sequence.
/// </summary>
internal abstract class SessionOperation : IOperation
{
protected SessionContext Context { get; private set; }
public event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public event StatusChangedEventHandler StatusChanged { add { } remove { } }
public SessionOperation(SessionContext sessionContext)
{
Context = sessionContext;
}
public abstract OperationResult Perform();
public abstract OperationResult Revert();
}
}

View file

@ -64,6 +64,10 @@
<Compile Include="Installer.cs"> <Compile Include="Installer.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
<Compile Include="Operations\ServiceEventCleanupOperation.cs" />
<Compile Include="Operations\SessionActivationOperation.cs" />
<Compile Include="Operations\SessionInitializationOperation.cs" />
<Compile Include="Operations\SessionOperation.cs" />
<Compile Include="Service.cs"> <Compile Include="Service.cs">
<SubType>Component</SubType> <SubType>Component</SubType>
</Compile> </Compile>
@ -92,8 +96,6 @@
<Name>SafeExamBrowser.Logging</Name> <Name>SafeExamBrowser.Logging</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup />
<Folder Include="Operations\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

@ -6,23 +6,27 @@
* 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.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Service; using SafeExamBrowser.Contracts.Service;
namespace SafeExamBrowser.Service namespace SafeExamBrowser.Service
{ {
internal class ServiceController : IServiceController internal class ServiceController : IServiceController
{ {
private readonly ILogger logger;
private IOperationSequence bootstrapSequence; private IOperationSequence bootstrapSequence;
private IRepeatableOperationSequence sessionSequence; private IOperationSequence sessionSequence;
private IServiceHost serviceHost; private IServiceHost serviceHost;
private SessionContext sessionContext; private SessionContext sessionContext;
private ServiceConfiguration Session private ServiceConfiguration Session
{ {
get { return sessionContext.Current; } get { return sessionContext.Configuration; }
} }
private bool SessionIsRunning private bool SessionIsRunning
@ -31,11 +35,13 @@ namespace SafeExamBrowser.Service
} }
public ServiceController( public ServiceController(
ILogger logger,
IOperationSequence bootstrapSequence, IOperationSequence bootstrapSequence,
IRepeatableOperationSequence sessionSequence, IOperationSequence sessionSequence,
IServiceHost serviceHost, IServiceHost serviceHost,
SessionContext sessionContext) SessionContext sessionContext)
{ {
this.logger = logger;
this.bootstrapSequence = bootstrapSequence; this.bootstrapSequence = bootstrapSequence;
this.sessionSequence = sessionSequence; this.sessionSequence = sessionSequence;
this.serviceHost = serviceHost; this.serviceHost = serviceHost;
@ -44,22 +50,130 @@ namespace SafeExamBrowser.Service
public bool TryStart() public bool TryStart()
{ {
logger.Info("Initiating startup procedure...");
var result = bootstrapSequence.TryPerform(); var result = bootstrapSequence.TryPerform();
var success = result == OperationResult.Success; var success = result == OperationResult.Success;
if (success)
{
RegisterEvents();
logger.Info("Service successfully initialized.");
logger.Log(string.Empty);
}
else
{
logger.Info("Service startup aborted!");
logger.Log(string.Empty);
}
return success; return success;
} }
public void Terminate() public void Terminate()
{ {
var result = default(OperationResult); DeregisterEvents();
if (SessionIsRunning) if (SessionIsRunning)
{ {
result = sessionSequence.TryRevert(); StopSession();
} }
result = bootstrapSequence.TryRevert(); logger.Log(string.Empty);
logger.Info("Initiating termination procedure...");
var result = bootstrapSequence.TryRevert();
var success = result == OperationResult.Success;
if (success)
{
logger.Info("Service successfully terminated.");
logger.Log(string.Empty);
}
else
{
logger.Info("Service termination failed!");
logger.Log(string.Empty);
}
}
private void StartSession()
{
logger.Info(AppendDivider("Session Start Procedure"));
var result = sessionSequence.TryPerform();
if (result == OperationResult.Success)
{
logger.Info(AppendDivider("Session Running"));
}
else
{
logger.Info(AppendDivider("Session Start Failed"));
}
}
private void StopSession()
{
logger.Info(AppendDivider("Session Stop Procedure"));
var result = sessionSequence.TryRevert();
if (result == OperationResult.Success)
{
logger.Info(AppendDivider("Session Terminated"));
}
else
{
logger.Info(AppendDivider("Session Stop Failed"));
}
}
private void RegisterEvents()
{
serviceHost.SessionStartRequested += ServiceHost_SessionStartRequested;
serviceHost.SessionStopRequested += ServiceHost_SessionStopRequested;
}
private void DeregisterEvents()
{
serviceHost.SessionStartRequested -= ServiceHost_SessionStartRequested;
serviceHost.SessionStopRequested -= ServiceHost_SessionStopRequested;
}
private void ServiceHost_SessionStartRequested(SessionStartEventArgs args)
{
if (!SessionIsRunning)
{
sessionContext.Configuration = args.Configuration;
StartSession();
}
else
{
logger.Warn("Received session start request, even though a session is already running!");
}
}
private void ServiceHost_SessionStopRequested(SessionStopEventArgs args)
{
if (SessionIsRunning)
{
StopSession();
}
else
{
logger.Warn("Received session stop request, even though no session is currently running!");
}
}
private string AppendDivider(string message)
{
var dashesLeft = new String('-', 48 - message.Length / 2 - message.Length % 2);
var dashesRight = new String('-', 48 - message.Length / 2);
return $"### {dashesLeft} {message} {dashesRight} ###";
} }
} }
} }

View file

@ -6,6 +6,7 @@
* 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.Threading;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Service namespace SafeExamBrowser.Service
@ -18,11 +19,11 @@ namespace SafeExamBrowser.Service
/// <summary> /// <summary>
/// The configuration of the currently active session. /// The configuration of the currently active session.
/// </summary> /// </summary>
internal ServiceConfiguration Current { get; set; } internal ServiceConfiguration Configuration { get; set; }
/// <summary> /// <summary>
/// The configuration of the next session to be activated. /// The global inter-process event used for status synchronization with the runtime component.
/// </summary> /// </summary>
internal ServiceConfiguration Next { get; set; } internal EventWaitHandle EventWaitHandle { get; set; }
} }
} }

View file

@ -20,14 +20,15 @@ test_script:
- .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.I18n.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.I18n.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml" - .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.I18n.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.I18n.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml"
- .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.Logging.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.Logging.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml" - .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.Logging.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.Logging.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml"
- .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.Runtime.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.Runtime.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml" - .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.Runtime.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.Runtime.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml"
- .\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe -register:user -target:"vstest.console.exe" -targetargs:"/logger:Appveyor .\SafeExamBrowser.Service.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.Service.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml"
after_test: after_test:
- "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%" - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%"
- pip install codecov - pip install codecov
- codecov -f "coverage.xml" - codecov -f "coverage.xml"
artifacts: artifacts:
- path: SafeExamBrowser.Runtime\bin\$(platform)\$(configuration) - path: SafeExamBrowser.Runtime\bin\$(platform)\$(configuration)
name: '$(appveyor_build_version)_Base_Application' name: '$(appveyor_build_version)_Application'
type: zip type: zip
- path: SafeExamBrowser.Service\bin\$(platform)\$(configuration) - path: SafeExamBrowser.Service\bin\$(platform)\$(configuration)
name: '$(appveyor_build_version)_Base_Service' name: '$(appveyor_build_version)_Service'
type: zip type: zip