SEBWIN-301: Implemented basic service session procedure.
This commit is contained in:
parent
77a3b50ca9
commit
6b24554abc
61 changed files with 911 additions and 296 deletions
|
@ -105,7 +105,12 @@ namespace SafeExamBrowser.Client.UnitTests.Communication
|
|||
sut.AuthenticationToken = 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.IsTrue(response.ConnectionTerminated);
|
||||
|
@ -118,8 +123,13 @@ namespace SafeExamBrowser.Client.UnitTests.Communication
|
|||
{
|
||||
var token = sut.AuthenticationToken = Guid.NewGuid();
|
||||
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();
|
||||
|
||||
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 ReconfigurationDeniedMessage("") { 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 { };
|
||||
|
|
|
@ -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.Disconnect()).Returns(true);
|
||||
runtime.SetupGet(r => r.IsConnected).Returns(true);
|
||||
sut.Perform();
|
||||
|
||||
var result = sut.Revert();
|
||||
|
||||
runtime.Verify(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
|
||||
runtime.Verify(r => r.Disconnect(), Times.Once);
|
||||
runtime.VerifyGet(r => r.IsConnected, Times.Once);
|
||||
runtime.VerifyNoOtherCalls();
|
||||
|
||||
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.Disconnect()).Returns(false);
|
||||
runtime.SetupGet(r => r.IsConnected).Returns(true);
|
||||
sut.Perform();
|
||||
|
||||
var result = sut.Revert();
|
||||
|
||||
runtime.Verify(r => r.Connect(It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
|
||||
runtime.Verify(r => r.Disconnect(), Times.Once);
|
||||
runtime.VerifyGet(r => r.IsConnected, Times.Once);
|
||||
runtime.VerifyNoOtherCalls();
|
||||
|
||||
Assert.AreEqual(OperationResult.Failed, result);
|
||||
|
@ -97,6 +101,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
|
|||
{
|
||||
var result = sut.Revert();
|
||||
|
||||
runtime.VerifyGet(r => r.IsConnected, Times.Once);
|
||||
runtime.VerifyNoOtherCalls();
|
||||
|
||||
Assert.AreEqual(OperationResult.Success, result);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
using System;
|
||||
using SafeExamBrowser.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Events;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
|
@ -53,10 +54,13 @@ namespace SafeExamBrowser.Client.Communication
|
|||
return accepted;
|
||||
}
|
||||
|
||||
protected override void OnDisconnect()
|
||||
protected override void OnDisconnect(Interlocutor interlocutor)
|
||||
{
|
||||
RuntimeDisconnected?.Invoke();
|
||||
IsConnected = false;
|
||||
if (interlocutor == Interlocutor.Runtime)
|
||||
{
|
||||
RuntimeDisconnected?.Invoke();
|
||||
IsConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Response OnReceive(Message message)
|
||||
|
|
|
@ -20,6 +20,7 @@ using SafeExamBrowser.Communication.Proxies;
|
|||
using SafeExamBrowser.Configuration.Cryptography;
|
||||
using SafeExamBrowser.Contracts.Browser;
|
||||
using SafeExamBrowser.Contracts.Client;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
|
@ -97,7 +98,7 @@ namespace SafeExamBrowser.Client
|
|||
powerSupply = new PowerSupply(new ModuleLogger(logger, nameof(PowerSupply)), text);
|
||||
processMonitor = new ProcessMonitor(new ModuleLogger(logger, nameof(ProcessMonitor)), nativeMethods);
|
||||
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();
|
||||
terminationActivator = new TerminationActivator(new ModuleLogger(logger, nameof(TerminationActivator)));
|
||||
windowMonitor = new WindowMonitor(new ModuleLogger(logger, nameof(WindowMonitor)), nativeMethods);
|
||||
|
|
|
@ -17,7 +17,6 @@ namespace SafeExamBrowser.Client.Operations
|
|||
{
|
||||
internal class RuntimeConnectionOperation : IOperation
|
||||
{
|
||||
private bool connected;
|
||||
private ILogger logger;
|
||||
private IRuntimeProxy runtime;
|
||||
private Guid token;
|
||||
|
@ -37,7 +36,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
logger.Info("Initializing runtime connection...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeRuntimeConnection);
|
||||
|
||||
connected = runtime.Connect(token);
|
||||
var connected = runtime.Connect(token);
|
||||
|
||||
if (connected)
|
||||
{
|
||||
|
@ -56,7 +55,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
logger.Info("Closing runtime connection...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_CloseRuntimeConnection);
|
||||
|
||||
if (connected)
|
||||
if (runtime.IsConnected)
|
||||
{
|
||||
var success = runtime.Disconnect();
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using SafeExamBrowser.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
@ -17,7 +19,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
|
|||
internal class BaseHostStub : BaseHost
|
||||
{
|
||||
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<SimpleMessagePurport, Response> OnReceiveSimpleMessageStub { get; set; }
|
||||
|
||||
|
@ -27,7 +29,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
|
|||
|
||||
public Guid? GetCommunicationToken()
|
||||
{
|
||||
return CommunicationToken;
|
||||
return CommunicationToken.Any() ? CommunicationToken.First() : default(Guid?);
|
||||
}
|
||||
|
||||
protected override bool OnConnect(Guid? token)
|
||||
|
@ -35,9 +37,9 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
|
|||
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)
|
||||
|
|
|
@ -154,17 +154,19 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
|
|||
[TestMethod]
|
||||
public void MustCorrectlyHandleDisconnectionRequest()
|
||||
{
|
||||
var message = new DisconnectionMessage();
|
||||
var message = new DisconnectionMessage { Interlocutor = Interlocutor.Runtime };
|
||||
var disconnected = false;
|
||||
var interlocutor = Interlocutor.Unknown;
|
||||
|
||||
sut.OnConnectStub = (t) => { return true; };
|
||||
sut.OnDisconnectStub = () => disconnected = true;
|
||||
sut.OnDisconnectStub = (i) => { disconnected = true; interlocutor = i; };
|
||||
sut.Connect();
|
||||
|
||||
message.CommunicationToken = sut.GetCommunicationToken().Value;
|
||||
|
||||
var response = sut.Disconnect(message);
|
||||
|
||||
Assert.AreEqual(message.Interlocutor, interlocutor);
|
||||
Assert.IsTrue(disconnected);
|
||||
Assert.IsTrue(response.ConnectionTerminated);
|
||||
Assert.IsNull(sut.GetCommunicationToken());
|
||||
|
@ -176,7 +178,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Hosts
|
|||
var disconnected = false;
|
||||
|
||||
sut.OnConnectStub = (t) => { return true; };
|
||||
sut.OnDisconnectStub = () => disconnected = true;
|
||||
sut.OnDisconnectStub = (i) => disconnected = true;
|
||||
sut.Connect();
|
||||
|
||||
var response = sut.Disconnect(new DisconnectionMessage());
|
||||
|
|
|
@ -7,16 +7,17 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Communication.Proxies;
|
||||
|
||||
namespace SafeExamBrowser.Communication.UnitTests.Proxies
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ using System;
|
|||
using System.ServiceModel;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
@ -29,7 +30,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
|
|||
proxyObjectFactory = new Mock<IProxyObjectFactory>();
|
||||
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]
|
||||
|
|
|
@ -11,6 +11,7 @@ using System.ServiceModel;
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
@ -43,7 +44,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
|
|||
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ using System.ServiceModel;
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
|
@ -44,7 +45,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
|
|||
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ using SafeExamBrowser.Contracts.Communication.Data;
|
|||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
|
||||
namespace SafeExamBrowser.Communication.UnitTests.Proxies
|
||||
{
|
||||
|
@ -42,7 +43,7 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
|
|||
proxy.Setup(o => o.State).Returns(CommunicationState.Opened);
|
||||
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Returns(proxy.Object);
|
||||
|
||||
sut = new ServiceProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object);
|
||||
sut = new ServiceProxy("net.pipe://random/address/here", proxyObjectFactory.Object, logger.Object, default(Interlocutor));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ServiceModel;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
|
@ -30,7 +31,7 @@ namespace SafeExamBrowser.Communication.Hosts
|
|||
private Thread hostThread;
|
||||
private int timeout_ms;
|
||||
|
||||
protected Guid? CommunicationToken { get; private set; }
|
||||
protected IList<Guid> CommunicationToken { get; private set; }
|
||||
protected ILogger Logger { get; private set; }
|
||||
|
||||
public bool IsRunning
|
||||
|
@ -47,13 +48,14 @@ namespace SafeExamBrowser.Communication.Hosts
|
|||
public BaseHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms)
|
||||
{
|
||||
this.address = address;
|
||||
this.CommunicationToken = new List<Guid>();
|
||||
this.factory = factory;
|
||||
this.Logger = logger;
|
||||
this.timeout_ms = timeout_ms;
|
||||
}
|
||||
|
||||
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(SimpleMessagePurport message);
|
||||
|
||||
|
@ -61,15 +63,19 @@ namespace SafeExamBrowser.Communication.Hosts
|
|||
{
|
||||
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 connected = OnConnect(token);
|
||||
|
||||
if (connected)
|
||||
{
|
||||
response.CommunicationToken = CommunicationToken = Guid.NewGuid();
|
||||
var communicationToken = Guid.NewGuid();
|
||||
|
||||
response.CommunicationToken = communicationToken;
|
||||
response.ConnectionEstablished = true;
|
||||
|
||||
CommunicationToken.Add(communicationToken);
|
||||
}
|
||||
|
||||
Logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request.");
|
||||
|
@ -88,10 +94,10 @@ namespace SafeExamBrowser.Communication.Hosts
|
|||
|
||||
if (IsAuthorized(message?.CommunicationToken))
|
||||
{
|
||||
OnDisconnect();
|
||||
OnDisconnect(message.Interlocutor);
|
||||
|
||||
CommunicationToken = null;
|
||||
response.ConnectionTerminated = true;
|
||||
CommunicationToken.Remove(message.CommunicationToken);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
@ -166,7 +172,7 @@ namespace SafeExamBrowser.Communication.Hosts
|
|||
|
||||
private bool IsAuthorized(Guid? token)
|
||||
{
|
||||
return CommunicationToken == token;
|
||||
return token.HasValue && CommunicationToken.Contains(token.Value);
|
||||
}
|
||||
|
||||
private void TryStartHost(AutoResetEvent startedEvent, out Exception exception)
|
||||
|
|
|
@ -26,21 +26,23 @@ namespace SafeExamBrowser.Communication.Proxies
|
|||
private readonly object @lock = new object();
|
||||
|
||||
private string address;
|
||||
private Interlocutor owner;
|
||||
private IProxyObject proxy;
|
||||
private IProxyObjectFactory factory;
|
||||
private Guid? communicationToken;
|
||||
private Timer timer;
|
||||
|
||||
protected bool IsConnected { get { return communicationToken.HasValue; } }
|
||||
protected ILogger Logger { get; private set; }
|
||||
protected ILogger Logger { get; }
|
||||
public bool IsConnected => communicationToken.HasValue;
|
||||
|
||||
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.factory = factory;
|
||||
this.Logger = logger;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public virtual bool Connect(Guid? token = null, bool autoPing = true)
|
||||
|
@ -89,7 +91,11 @@ namespace SafeExamBrowser.Communication.Proxies
|
|||
|
||||
StopAutoPing();
|
||||
|
||||
var message = new DisconnectionMessage { CommunicationToken = communicationToken.Value };
|
||||
var message = new DisconnectionMessage
|
||||
{
|
||||
CommunicationToken = communicationToken.Value,
|
||||
Interlocutor = owner
|
||||
};
|
||||
var response = proxy.Disconnect(message);
|
||||
var success = response.ConnectionTerminated;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
@ -19,7 +20,7 @@ namespace SafeExamBrowser.Communication.Proxies
|
|||
/// </summary>
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
34
SafeExamBrowser.Communication/Proxies/ProxyFactory.cs
Normal file
34
SafeExamBrowser.Communication/Proxies/ProxyFactory.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
@ -19,7 +20,7 @@ namespace SafeExamBrowser.Communication.Proxies
|
|||
/// </summary>
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
@ -19,9 +21,8 @@ namespace SafeExamBrowser.Communication.Proxies
|
|||
public class ServiceProxy : BaseProxy, IServiceProxy
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// Implement service communication...
|
||||
// Send(new StartSessionMessage { Id = sessionId, Settings = settings });
|
||||
try
|
||||
{
|
||||
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)
|
||||
|
@ -65,10 +84,28 @@ namespace SafeExamBrowser.Communication.Proxies
|
|||
return new CommunicationResult(true);
|
||||
}
|
||||
|
||||
// Implement service communication...
|
||||
// Send(new StopSessionMessage { SessionId = sessionId });
|
||||
try
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Proxies\BaseProxy.cs" />
|
||||
<Compile Include="Proxies\ClientProxy.cs" />
|
||||
<Compile Include="Proxies\ProxyFactory.cs" />
|
||||
<Compile Include="Proxies\ProxyObjectFactory.cs" />
|
||||
<Compile Include="Proxies\RuntimeProxy.cs" />
|
||||
<Compile Include="Proxies\ServiceProxy.cs" />
|
||||
|
|
|
@ -278,7 +278,6 @@ namespace SafeExamBrowser.Configuration.UnitTests
|
|||
Assert.IsNull(configuration.Settings);
|
||||
Assert.IsInstanceOfType(configuration.AppConfig, typeof(AppConfig));
|
||||
Assert.IsInstanceOfType(configuration.ClientAuthenticationToken, typeof(Guid));
|
||||
Assert.IsInstanceOfType(configuration.ServiceAuthenticationToken, typeof(Guid));
|
||||
Assert.IsInstanceOfType(configuration.SessionId, typeof(Guid));
|
||||
}
|
||||
|
||||
|
@ -292,14 +291,18 @@ namespace SafeExamBrowser.Configuration.UnitTests
|
|||
var runtimeAddress = appConfig.RuntimeAddress;
|
||||
var runtimeId = appConfig.RuntimeId;
|
||||
var runtimeLogFilePath = appConfig.RuntimeLogFilePath;
|
||||
var serviceEventName = appConfig.ServiceEventName;
|
||||
|
||||
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.RuntimeAddress, runtimeAddress);
|
||||
Assert.AreEqual(configuration.AppConfig.RuntimeId, runtimeId);
|
||||
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]
|
||||
|
@ -312,10 +315,8 @@ namespace SafeExamBrowser.Configuration.UnitTests
|
|||
|
||||
Assert.AreNotEqual(firstSession.SessionId, secondSession.SessionId);
|
||||
Assert.AreNotEqual(firstSession.ClientAuthenticationToken, secondSession.ClientAuthenticationToken);
|
||||
Assert.AreNotEqual(firstSession.ServiceAuthenticationToken, secondSession.ServiceAuthenticationToken);
|
||||
Assert.AreNotEqual(secondSession.SessionId, thirdSession.SessionId);
|
||||
Assert.AreNotEqual(secondSession.ClientAuthenticationToken, thirdSession.ClientAuthenticationToken);
|
||||
Assert.AreNotEqual(secondSession.ServiceAuthenticationToken, thirdSession.ServiceAuthenticationToken);
|
||||
}
|
||||
|
||||
private void RegisterModules()
|
||||
|
|
|
@ -78,10 +78,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
|
|||
|
||||
appConfig.ClientId = Guid.NewGuid();
|
||||
appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}";
|
||||
appConfig.ServiceEventName = $@"Global\{nameof(SafeExamBrowser)}-{Guid.NewGuid()}";
|
||||
|
||||
configuration.AppConfig = appConfig.Clone();
|
||||
configuration.ClientAuthenticationToken = Guid.NewGuid();
|
||||
configuration.ServiceAuthenticationToken = Guid.NewGuid();
|
||||
configuration.SessionId = Guid.NewGuid();
|
||||
|
||||
return configuration;
|
||||
|
|
|
@ -16,5 +16,9 @@ namespace SafeExamBrowser.Contracts.Communication.Data
|
|||
[Serializable]
|
||||
public class DisconnectionMessage : Message
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies the component sending the message.
|
||||
/// </summary>
|
||||
public Interlocutor Interlocutor { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
|
|||
/// <summary>
|
||||
/// The token used for initial authentication.
|
||||
/// </summary>
|
||||
Guid AuthenticationToken { set; }
|
||||
Guid? AuthenticationToken { set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the client disconnected from the runtime.
|
||||
|
@ -56,26 +56,6 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
|
|||
/// </summary>
|
||||
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>
|
||||
/// Event fired when the client requests to shut down the application.
|
||||
/// </summary>
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Contracts.Communication.Events;
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Communication.Hosts
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -13,5 +15,19 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
|
|||
/// </summary>
|
||||
public interface IServiceHost : ICommunicationHost
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether another application component may establish a connection with the host.
|
||||
/// </summary>
|
||||
bool AllowConnection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the runtime requested to start a new session.
|
||||
/// </summary>
|
||||
event CommunicationEventHandler<SessionStartEventArgs> SessionStartRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the runtime requested to stop a running session.
|
||||
/// </summary>
|
||||
event CommunicationEventHandler<SessionStopEventArgs> SessionStopRequested;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ namespace SafeExamBrowser.Contracts.Communication
|
|||
[ServiceKnownType(typeof(ReconfigurationMessage))]
|
||||
[ServiceKnownType(typeof(ReconfigurationDeniedMessage))]
|
||||
[ServiceKnownType(typeof(ServiceConfiguration))]
|
||||
[ServiceKnownType(typeof(SessionStartMessage))]
|
||||
[ServiceKnownType(typeof(SessionStopMessage))]
|
||||
[ServiceKnownType(typeof(SimpleMessage))]
|
||||
[ServiceKnownType(typeof(SimpleResponse))]
|
||||
public interface ICommunication
|
||||
|
|
|
@ -17,6 +17,11 @@ namespace SafeExamBrowser.Contracts.Communication
|
|||
/// </summary>
|
||||
public interface ICommunicationProxy
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether a connection to the host has been established.
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the connection to the host was lost, e.g. if a ping request failed or a communication fault occurred.
|
||||
/// </summary>
|
||||
|
|
39
SafeExamBrowser.Contracts/Communication/Interlocutor.cs
Normal file
39
SafeExamBrowser.Contracts/Communication/Interlocutor.cs
Normal 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
|
||||
}
|
||||
}
|
|
@ -14,8 +14,8 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
|
|||
public interface IProxyFactory
|
||||
{
|
||||
/// <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>
|
||||
IClientProxy CreateClientProxy(string address);
|
||||
IClientProxy CreateClientProxy(string address, Interlocutor owner);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,6 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
|
|||
/// </summary>
|
||||
bool Ignore { set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether a connection to the communication host of the service has been established.
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Instructs the service to start a new session according to the given configuration.
|
||||
/// </summary>
|
||||
|
|
|
@ -116,6 +116,11 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// </summary>
|
||||
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>
|
||||
/// Creates a shallow clone.
|
||||
/// </summary>
|
||||
|
|
|
@ -21,11 +21,6 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// </summary>
|
||||
public AppConfig AppConfig { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The token used for initial authentication with the runtime.
|
||||
/// </summary>
|
||||
public Guid AuthenticationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier for the current session.
|
||||
/// </summary>
|
||||
|
|
|
@ -25,11 +25,6 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// </summary>
|
||||
public Guid ClientAuthenticationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The token used for initial communication authentication with the service.
|
||||
/// </summary>
|
||||
public Guid ServiceAuthenticationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique session identifier.
|
||||
/// </summary>
|
||||
|
|
|
@ -56,10 +56,15 @@
|
|||
<Compile Include="Browser\ProgressChangedEventHandler.cs" />
|
||||
<Compile Include="Communication\Data\MessageBoxReplyMessage.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\MessageBoxReplyEventArgs.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\Interlocutor.cs" />
|
||||
<Compile Include="Configuration\ClientConfiguration.cs" />
|
||||
<Compile Include="Configuration\Cryptography\EncryptionParameters.cs" />
|
||||
<Compile Include="Configuration\Cryptography\ICertificateStore.cs" />
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication
|
|||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustOnlyAllowConnectionIfTokenIsValid()
|
||||
public void MustAllowConnectionIfTokenIsValid()
|
||||
{
|
||||
var token = Guid.NewGuid();
|
||||
|
||||
|
@ -70,6 +70,19 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication
|
|||
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]
|
||||
public void MustOnlyAllowOneConcurrentConnection()
|
||||
{
|
||||
|
@ -94,7 +107,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication
|
|||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustCorrectlyDisconnect()
|
||||
public void MustCorrectlyDisconnectClient()
|
||||
{
|
||||
var disconnected = false;
|
||||
var token = Guid.NewGuid();
|
||||
|
@ -104,7 +117,12 @@ namespace SafeExamBrowser.Runtime.UnitTests.Communication
|
|||
sut.ClientDisconnected += () => disconnected = true;
|
||||
|
||||
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.IsTrue(disconnected);
|
||||
|
|
|
@ -10,6 +10,7 @@ using System;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
|
@ -63,7 +64,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
session.Settings = settings;
|
||||
sessionContext.Current = 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);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
|
@ -60,7 +61,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
sessionContext.ClientProxy = proxy.Object;
|
||||
sessionContext.Current = 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);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
|
@ -24,9 +25,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
[TestClass]
|
||||
public class ServiceOperationTests
|
||||
{
|
||||
private AppConfig appConfig;
|
||||
private Mock<ILogger> logger;
|
||||
private Mock<IRuntimeHost> runtimeHost;
|
||||
private Mock<IServiceProxy> service;
|
||||
private EventWaitHandle serviceEvent;
|
||||
private SessionConfiguration session;
|
||||
private SessionContext sessionContext;
|
||||
private Settings settings;
|
||||
|
@ -35,15 +38,22 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
var serviceEventName = $"{nameof(SafeExamBrowser)}-{nameof(ServiceOperationTests)}";
|
||||
|
||||
appConfig = new AppConfig();
|
||||
logger = new Mock<ILogger>();
|
||||
runtimeHost = new Mock<IRuntimeHost>();
|
||||
service = new Mock<IServiceProxy>();
|
||||
serviceEvent = new EventWaitHandle(false, EventResetMode.AutoReset, serviceEventName);
|
||||
session = new SessionConfiguration();
|
||||
sessionContext = new SessionContext();
|
||||
settings = new Settings();
|
||||
|
||||
appConfig.ServiceEventName = serviceEventName;
|
||||
sessionContext.Current = session;
|
||||
sessionContext.Current.AppConfig = appConfig;
|
||||
sessionContext.Next = session;
|
||||
sessionContext.Next.AppConfig = appConfig;
|
||||
session.Settings = settings;
|
||||
settings.ServicePolicy = ServicePolicy.Mandatory;
|
||||
|
||||
|
@ -71,10 +81,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
{
|
||||
service.SetupGet(s => s.IsConnected).Returns(true);
|
||||
service.Setup(s => s.Connect(null, true)).Returns(true);
|
||||
service
|
||||
.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>()))
|
||||
.Returns(new CommunicationResult(true))
|
||||
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStarted += null));
|
||||
service.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
|
||||
|
||||
var result = sut.Perform();
|
||||
|
||||
|
@ -88,10 +95,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
{
|
||||
service.SetupGet(s => s.IsConnected).Returns(true);
|
||||
service.Setup(s => s.Connect(null, true)).Returns(true);
|
||||
service
|
||||
.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>()))
|
||||
.Returns(new CommunicationResult(true))
|
||||
.Callback(() => runtimeHost.Raise(h => h.ServiceFailed += null));
|
||||
service.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>())).Returns(new CommunicationResult(true));
|
||||
|
||||
var result = sut.Perform();
|
||||
|
||||
|
@ -197,10 +201,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
[TestMethod]
|
||||
public void Repeat_MustStopCurrentAndStartNewSession()
|
||||
{
|
||||
service
|
||||
.Setup(s => s.StopSession(It.IsAny<Guid>()))
|
||||
.Returns(new CommunicationResult(true))
|
||||
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
|
||||
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
|
||||
|
||||
PerformNormally();
|
||||
|
||||
|
@ -222,10 +223,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
service.Reset();
|
||||
service.SetupGet(s => s.IsConnected).Returns(false);
|
||||
service.Setup(s => s.Connect(null, true)).Returns(true);
|
||||
service
|
||||
.Setup(s => s.StopSession(It.IsAny<Guid>()))
|
||||
.Returns(new CommunicationResult(true))
|
||||
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStopped += null));
|
||||
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true));
|
||||
|
||||
var result = sut.Repeat();
|
||||
|
||||
|
@ -251,58 +249,49 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Revert_MustDisconnect()
|
||||
{
|
||||
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()
|
||||
public void Repeat_MustFailIfSessionNotStoppedWithinTimeout()
|
||||
{
|
||||
const int TIMEOUT = 50;
|
||||
|
||||
var after = 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);
|
||||
|
||||
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();
|
||||
|
||||
before = DateTime.Now;
|
||||
var result = sut.Revert();
|
||||
var result = sut.Repeat();
|
||||
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.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]
|
||||
public void Revert_MustStopSessionIfConnected()
|
||||
{
|
||||
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));
|
||||
service.Setup(s => s.Disconnect()).Returns(true);
|
||||
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
|
||||
|
||||
PerformNormally();
|
||||
|
||||
|
@ -332,10 +321,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
[TestMethod]
|
||||
public void Revert_MustFailIfSessionStopUnsuccessful()
|
||||
{
|
||||
service
|
||||
.Setup(s => s.StopSession(It.IsAny<Guid>()))
|
||||
.Returns(new CommunicationResult(true))
|
||||
.Callback(() => runtimeHost.Raise(h => h.ServiceFailed += null));
|
||||
service.Setup(s => s.StopSession(It.IsAny<Guid>())).Returns(new CommunicationResult(true));
|
||||
|
||||
PerformNormally();
|
||||
|
||||
|
@ -377,6 +363,20 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
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]
|
||||
public void Revert_MustNotDisconnnectIfNotConnected()
|
||||
{
|
||||
|
@ -390,10 +390,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
{
|
||||
service.SetupGet(s => s.IsConnected).Returns(true);
|
||||
service.Setup(s => s.Connect(null, true)).Returns(true);
|
||||
service
|
||||
.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>()))
|
||||
.Returns(new CommunicationResult(true))
|
||||
.Callback(() => runtimeHost.Raise(h => h.ServiceSessionStarted += null));
|
||||
service.Setup(s => s.StartSession(It.IsAny<ServiceConfiguration>())).Returns(new CommunicationResult(true)).Callback(() => serviceEvent.Set());
|
||||
|
||||
sut.Perform();
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|||
using Moq;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Runtime.Operations;
|
||||
|
||||
|
@ -52,33 +53,41 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
session.ClientAuthenticationToken = token;
|
||||
|
||||
sut.Perform();
|
||||
var result = sut.Perform();
|
||||
|
||||
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]
|
||||
public void MustInitializeConfigurationOnRepeat()
|
||||
{
|
||||
var currentSession = new SessionConfiguration();
|
||||
var token = Guid.NewGuid();
|
||||
|
||||
session.ClientAuthenticationToken = token;
|
||||
sessionContext.Current = currentSession;
|
||||
|
||||
sut.Repeat();
|
||||
var result = sut.Repeat();
|
||||
|
||||
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]
|
||||
public void MustDoNothingOnRevert()
|
||||
{
|
||||
sut.Revert();
|
||||
var result = sut.Revert();
|
||||
|
||||
configuration.VerifyNoOtherCalls();
|
||||
logger.VerifyNoOtherCalls();
|
||||
runtimeHost.VerifyNoOtherCalls();
|
||||
|
||||
Assert.AreEqual(OperationResult.Success, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
using System;
|
||||
using SafeExamBrowser.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Events;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
|
@ -18,7 +19,7 @@ namespace SafeExamBrowser.Runtime.Communication
|
|||
internal class RuntimeHost : BaseHost, IRuntimeHost
|
||||
{
|
||||
public bool AllowConnection { get; set; }
|
||||
public Guid AuthenticationToken { private get; set; }
|
||||
public Guid? AuthenticationToken { private get; set; }
|
||||
|
||||
public event CommunicationEventHandler ClientDisconnected;
|
||||
public event CommunicationEventHandler ClientReady;
|
||||
|
@ -26,10 +27,6 @@ namespace SafeExamBrowser.Runtime.Communication
|
|||
public event CommunicationEventHandler<MessageBoxReplyEventArgs> MessageBoxReplyReceived;
|
||||
public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived;
|
||||
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 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)
|
||||
{
|
||||
var authenticated = AuthenticationToken == token;
|
||||
var authenticated = AuthenticationToken.HasValue && AuthenticationToken == token;
|
||||
var accepted = AllowConnection && authenticated;
|
||||
|
||||
if (accepted)
|
||||
|
@ -49,9 +46,12 @@ namespace SafeExamBrowser.Runtime.Communication
|
|||
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)
|
||||
|
|
|
@ -17,6 +17,7 @@ using SafeExamBrowser.Configuration.Cryptography;
|
|||
using SafeExamBrowser.Configuration.DataCompression;
|
||||
using SafeExamBrowser.Configuration.DataFormats;
|
||||
using SafeExamBrowser.Configuration.DataResources;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
using SafeExamBrowser.Contracts.I18n;
|
||||
|
@ -65,9 +66,9 @@ namespace SafeExamBrowser.Runtime
|
|||
var desktopFactory = new DesktopFactory(ModuleLogger(nameof(DesktopFactory)));
|
||||
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
|
||||
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 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 uiFactory = new UserInterfaceFactory(text);
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Events;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
|
@ -111,6 +113,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
logger.Info("Starting new client process...");
|
||||
runtimeHost.AllowConnection = true;
|
||||
runtimeHost.AuthenticationToken = Context.Next.ClientAuthenticationToken;
|
||||
runtimeHost.ClientReady += clientReadyEventHandler;
|
||||
ClientProcess = processFactory.StartNew(executablePath, logFilePath, logLevel, runtimeHostUri, authenticationToken, uiMode);
|
||||
ClientProcess.Terminated += clientTerminatedEventHandler;
|
||||
|
@ -119,6 +122,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
clientReady = clientReadyEvent.WaitOne(timeout_ms);
|
||||
|
||||
runtimeHost.AllowConnection = false;
|
||||
runtimeHost.AuthenticationToken = default(Guid?);
|
||||
runtimeHost.ClientReady -= clientReadyEventHandler;
|
||||
ClientProcess.Terminated -= clientTerminatedEventHandler;
|
||||
|
||||
|
@ -145,7 +149,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
var success = false;
|
||||
|
||||
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))
|
||||
{
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Security.AccessControl;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Contracts.Communication.Events;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
|
@ -46,7 +47,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
logger.Info($"Initializing service session...");
|
||||
logger.Info($"Initializing service...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
|
||||
|
||||
var success = TryEstablishConnection();
|
||||
|
@ -61,7 +62,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
logger.Info($"Initializing new service session...");
|
||||
logger.Info($"Initializing service...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
|
||||
|
||||
var success = false;
|
||||
|
@ -85,14 +86,18 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
logger.Info("Finalizing service session...");
|
||||
logger.Info("Finalizing service...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession);
|
||||
|
||||
var success = true;
|
||||
|
||||
if (service.IsConnected)
|
||||
{
|
||||
success &= TryStopSession();
|
||||
if (Context.Current != null)
|
||||
{
|
||||
success &= TryStopSession();
|
||||
}
|
||||
|
||||
success &= TryTerminateConnection();
|
||||
}
|
||||
|
||||
|
@ -137,36 +142,18 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
private bool TryTerminateConnection()
|
||||
{
|
||||
var serviceEvent = new AutoResetEvent(false);
|
||||
var serviceEventHandler = new CommunicationEventHandler(() => serviceEvent.Set());
|
||||
var disconnected = service.Disconnect();
|
||||
|
||||
runtimeHost.ServiceDisconnected += serviceEventHandler;
|
||||
|
||||
var success = service.Disconnect();
|
||||
|
||||
if (success)
|
||||
if (disconnected)
|
||||
{
|
||||
logger.Info("Successfully disconnected from service. Waiting for service to disconnect...");
|
||||
|
||||
success = serviceEvent.WaitOne(timeout_ms);
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Service disconnected successfully.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Service failed to disconnect within {timeout_ms / 1000} seconds!");
|
||||
}
|
||||
logger.Info("Successfully disconnected from service.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to disconnect from service!");
|
||||
}
|
||||
|
||||
runtimeHost.ServiceDisconnected -= serviceEventHandler;
|
||||
|
||||
return success;
|
||||
return disconnected;
|
||||
}
|
||||
|
||||
private bool TryStartSession()
|
||||
|
@ -174,18 +161,10 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
var configuration = new ServiceConfiguration
|
||||
{
|
||||
AppConfig = Context.Next.AppConfig,
|
||||
AuthenticationToken = Context.Next.ServiceAuthenticationToken,
|
||||
SessionId = Context.Next.SessionId,
|
||||
Settings = Context.Next.Settings
|
||||
};
|
||||
var failure = 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;
|
||||
var started = false;
|
||||
|
||||
logger.Info("Starting new service session...");
|
||||
|
||||
|
@ -193,16 +172,12 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
if (communication.Success)
|
||||
{
|
||||
serviceEvent.WaitOne(timeout_ms);
|
||||
started = TryWaitForServiceEvent(Context.Next.AppConfig.ServiceEventName);
|
||||
|
||||
if (success)
|
||||
if (started)
|
||||
{
|
||||
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
|
||||
{
|
||||
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!");
|
||||
}
|
||||
|
||||
runtimeHost.ServiceFailed -= failureEventHandler;
|
||||
runtimeHost.ServiceSessionStarted -= successEventHandler;
|
||||
|
||||
return success;
|
||||
return started;
|
||||
}
|
||||
|
||||
private bool TryStopSession()
|
||||
{
|
||||
var failure = 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;
|
||||
var stopped = false;
|
||||
|
||||
logger.Info("Stopping current service session...");
|
||||
|
||||
|
@ -236,16 +201,12 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
if (communication.Success)
|
||||
{
|
||||
serviceEvent.WaitOne(timeout_ms);
|
||||
stopped = TryWaitForServiceEvent(Context.Current.AppConfig.ServiceEventName);
|
||||
|
||||
if (success)
|
||||
if (stopped)
|
||||
{
|
||||
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
|
||||
{
|
||||
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!");
|
||||
}
|
||||
|
||||
runtimeHost.ServiceFailed -= failureEventHandler;
|
||||
runtimeHost.ServiceSessionStopped -= successEventHandler;
|
||||
return stopped;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
|
@ -63,7 +62,6 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSession);
|
||||
|
||||
Context.Next = configuration.InitializeSessionConfiguration();
|
||||
runtimeHost.AuthenticationToken = Context.Next.ClientAuthenticationToken;
|
||||
|
||||
logger.Info($" -> Client-ID: {Context.Next.AppConfig.ClientId}");
|
||||
logger.Info($" -> Runtime-ID: {Context.Next.AppConfig.RuntimeId}");
|
||||
|
|
|
@ -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;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
|
|
|
@ -103,7 +103,6 @@
|
|||
<Compile Include="Operations\SessionActivationOperation.cs" />
|
||||
<Compile Include="Operations\SessionOperation.cs" />
|
||||
<Compile Include="Operations\SessionInitializationOperation.cs" />
|
||||
<Compile Include="Communication\ProxyFactory.cs" />
|
||||
<Compile Include="Communication\RuntimeHost.cs" />
|
||||
<Compile Include="CompositionRoot.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
|
|
|
@ -11,12 +11,14 @@ using Moq;
|
|||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Service.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class ServiceControllerTests
|
||||
{
|
||||
private Mock<ILogger> logger;
|
||||
private Mock<IOperationSequence> bootstrapSequence;
|
||||
private SessionContext sessionContext;
|
||||
private Mock<IRepeatableOperationSequence> sessionSequence;
|
||||
|
@ -26,12 +28,13 @@ namespace SafeExamBrowser.Service.UnitTests
|
|||
[TestInitialize]
|
||||
public void Initialize()
|
||||
{
|
||||
logger = new Mock<ILogger>();
|
||||
bootstrapSequence = new Mock<IOperationSequence>();
|
||||
sessionContext = new SessionContext();
|
||||
sessionSequence = new Mock<IRepeatableOperationSequence>();
|
||||
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]
|
||||
|
@ -39,7 +42,7 @@ namespace SafeExamBrowser.Service.UnitTests
|
|||
{
|
||||
bootstrapSequence.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();
|
||||
|
||||
|
@ -66,7 +69,7 @@ namespace SafeExamBrowser.Service.UnitTests
|
|||
|
||||
bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order);
|
||||
sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order);
|
||||
sessionContext.Current = new ServiceConfiguration();
|
||||
sessionContext.Configuration = new ServiceConfiguration();
|
||||
|
||||
sut.Terminate();
|
||||
|
||||
|
@ -94,7 +97,7 @@ namespace SafeExamBrowser.Service.UnitTests
|
|||
|
||||
bootstrapSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => bootstrap = ++order);
|
||||
sessionSequence.Setup(b => b.TryRevert()).Returns(OperationResult.Success).Callback(() => session = ++order);
|
||||
sessionContext.Current = null;
|
||||
sessionContext.Configuration = null;
|
||||
|
||||
sut.Terminate();
|
||||
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
using System;
|
||||
using SafeExamBrowser.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Communication;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Communication.Events;
|
||||
using SafeExamBrowser.Contracts.Communication.Hosts;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
|
@ -16,28 +18,68 @@ namespace SafeExamBrowser.Service.Communication
|
|||
{
|
||||
internal class ServiceHost : BaseHost, IServiceHost
|
||||
{
|
||||
private readonly object @lock = new object();
|
||||
|
||||
private bool allowConnection;
|
||||
|
||||
public bool AllowConnection
|
||||
{
|
||||
get { lock (@lock) { return allowConnection; } }
|
||||
set { lock (@lock) { allowConnection = value; } }
|
||||
}
|
||||
|
||||
public event CommunicationEventHandler<SessionStartEventArgs> SessionStartRequested;
|
||||
public event CommunicationEventHandler<SessionStopEventArgs> SessionStopRequested;
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SafeExamBrowser.Communication.Hosts;
|
||||
using SafeExamBrowser.Communication.Proxies;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Contracts.Service;
|
||||
|
@ -17,6 +18,7 @@ using SafeExamBrowser.Core.OperationModel;
|
|||
using SafeExamBrowser.Core.Operations;
|
||||
using SafeExamBrowser.Logging;
|
||||
using SafeExamBrowser.Service.Communication;
|
||||
using SafeExamBrowser.Service.Operations;
|
||||
|
||||
namespace SafeExamBrowser.Service
|
||||
{
|
||||
|
@ -33,25 +35,26 @@ namespace SafeExamBrowser.Service
|
|||
|
||||
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 sessionContext = new SessionContext();
|
||||
|
||||
var bootstrapOperations = new Queue<IOperation>();
|
||||
var sessionOperations = new Queue<IRepeatableOperation>();
|
||||
var sessionOperations = new Queue<IOperation>();
|
||||
|
||||
// TODO: bootstrapOperations.Enqueue(new RestoreOperation());
|
||||
bootstrapOperations.Enqueue(new CommunicationHostOperation(serviceHost, logger));
|
||||
bootstrapOperations.Enqueue(new ServiceEventCleanupOperation(logger, sessionContext));
|
||||
|
||||
// sessionOperations.Enqueue(new RuntimeConnectionOperation());
|
||||
// sessionOperations.Enqueue(new LogOperation());
|
||||
// sessionOperations.Enqueue(new RegistryOperation());
|
||||
// sessionOperations.Enqueue(new WindowsUpdateOperation());
|
||||
// sessionOperations.Enqueue(new SessionActivationOperation());
|
||||
sessionOperations.Enqueue(new SessionInitializationOperation(logger, serviceHost, sessionContext));
|
||||
// TODO: sessionOperations.Enqueue(new RegistryOperation());
|
||||
// sessionOperations.Enqueue(new WindowsUpdateOperation());
|
||||
sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext));
|
||||
|
||||
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()
|
||||
|
@ -62,7 +65,6 @@ namespace SafeExamBrowser.Service
|
|||
|
||||
internal void LogShutdownInformation()
|
||||
{
|
||||
logger?.Log(string.Empty);
|
||||
logger?.Log($"# Service terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
32
SafeExamBrowser.Service/Operations/SessionOperation.cs
Normal file
32
SafeExamBrowser.Service/Operations/SessionOperation.cs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -64,6 +64,10 @@
|
|||
<Compile Include="Installer.cs">
|
||||
<SubType>Component</SubType>
|
||||
</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">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
|
@ -92,8 +96,6 @@
|
|||
<Name>SafeExamBrowser.Logging</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Operations\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -6,23 +6,27 @@
|
|||
* 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.Configuration;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
using SafeExamBrowser.Contracts.Service;
|
||||
|
||||
namespace SafeExamBrowser.Service
|
||||
{
|
||||
internal class ServiceController : IServiceController
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private IOperationSequence bootstrapSequence;
|
||||
private IRepeatableOperationSequence sessionSequence;
|
||||
private IOperationSequence sessionSequence;
|
||||
private IServiceHost serviceHost;
|
||||
private SessionContext sessionContext;
|
||||
|
||||
private ServiceConfiguration Session
|
||||
{
|
||||
get { return sessionContext.Current; }
|
||||
get { return sessionContext.Configuration; }
|
||||
}
|
||||
|
||||
private bool SessionIsRunning
|
||||
|
@ -31,11 +35,13 @@ namespace SafeExamBrowser.Service
|
|||
}
|
||||
|
||||
public ServiceController(
|
||||
ILogger logger,
|
||||
IOperationSequence bootstrapSequence,
|
||||
IRepeatableOperationSequence sessionSequence,
|
||||
IOperationSequence sessionSequence,
|
||||
IServiceHost serviceHost,
|
||||
SessionContext sessionContext)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.bootstrapSequence = bootstrapSequence;
|
||||
this.sessionSequence = sessionSequence;
|
||||
this.serviceHost = serviceHost;
|
||||
|
@ -44,22 +50,130 @@ namespace SafeExamBrowser.Service
|
|||
|
||||
public bool TryStart()
|
||||
{
|
||||
logger.Info("Initiating startup procedure...");
|
||||
|
||||
var result = bootstrapSequence.TryPerform();
|
||||
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;
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
var result = default(OperationResult);
|
||||
|
||||
DeregisterEvents();
|
||||
|
||||
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} ###";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
|
||||
namespace SafeExamBrowser.Service
|
||||
|
@ -18,11 +19,11 @@ namespace SafeExamBrowser.Service
|
|||
/// <summary>
|
||||
/// The configuration of the currently active session.
|
||||
/// </summary>
|
||||
internal ServiceConfiguration Current { get; set; }
|
||||
internal ServiceConfiguration Configuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The configuration of the next session to be activated.
|
||||
/// The global inter-process event used for status synchronization with the runtime component.
|
||||
/// </summary>
|
||||
internal ServiceConfiguration Next { get; set; }
|
||||
internal EventWaitHandle EventWaitHandle { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.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.Service.UnitTests\bin\%PLATFORM%\%CONFIGURATION%\SafeExamBrowser.Service.UnitTests.dll" -filter:"+[*]* -[*.UnitTests]* -[*Moq*]*" -mergebyhash -mergeoutput -output:"coverage.xml"
|
||||
after_test:
|
||||
- "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%"
|
||||
- pip install codecov
|
||||
- codecov -f "coverage.xml"
|
||||
artifacts:
|
||||
- path: SafeExamBrowser.Runtime\bin\$(platform)\$(configuration)
|
||||
name: '$(appveyor_build_version)_Base_Application'
|
||||
name: '$(appveyor_build_version)_Application'
|
||||
type: zip
|
||||
- path: SafeExamBrowser.Service\bin\$(platform)\$(configuration)
|
||||
name: '$(appveyor_build_version)_Base_Service'
|
||||
name: '$(appveyor_build_version)_Service'
|
||||
type: zip
|
||||
|
|
Loading…
Reference in a new issue