SEBWIN-219: Finished scaffolding for application startup sequence.

This commit is contained in:
dbuechel 2018-02-14 15:26:05 +01:00
parent 10202a807f
commit dda1b78ec5
69 changed files with 999 additions and 252 deletions

View file

@ -25,7 +25,7 @@ namespace SafeExamBrowser.Client.UnitTests
private Mock<ITaskbar> taskbarMock;
private Mock<IWindowMonitor> windowMonitorMock;
private IClientController sut;
//private IClientController sut;
[TestInitialize]
public void Initialize()

View file

@ -9,6 +9,7 @@
using System;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Behaviour.Operations;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
@ -21,6 +22,7 @@ namespace SafeExamBrowser.Client.Behaviour
private ILogger logger;
private IOperationSequence operations;
private IProcessMonitor processMonitor;
private IRuntimeProxy runtime;
private ITaskbar taskbar;
private IWindowMonitor windowMonitor;
@ -29,6 +31,7 @@ namespace SafeExamBrowser.Client.Behaviour
ILogger logger,
IOperationSequence operations,
IProcessMonitor processMonitor,
IRuntimeProxy runtime,
ITaskbar taskbar,
IWindowMonitor windowMonitor)
{
@ -36,6 +39,7 @@ namespace SafeExamBrowser.Client.Behaviour
this.logger = logger;
this.operations = operations;
this.processMonitor = processMonitor;
this.runtime = runtime;
this.taskbar = taskbar;
this.windowMonitor = windowMonitor;
}
@ -45,15 +49,29 @@ namespace SafeExamBrowser.Client.Behaviour
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted;
windowMonitor.WindowChanged -= WindowMonitor_WindowChanged;
// TODO
operations.TryRevert();
}
public bool TryStart()
{
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted;
windowMonitor.WindowChanged += WindowMonitor_WindowChanged;
return true;
// TODO
var success = operations.TryPerform();
if (success)
{
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted;
windowMonitor.WindowChanged += WindowMonitor_WindowChanged;
runtime.InformClientReady();
}
return success;
}
private void DisplayMonitor_DisplaySettingsChanged()

View file

@ -42,7 +42,7 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
var config = runtime.GetConfiguration();
configuration.RuntimeInfo = config.RuntimeInfo;
configuration.SessionData = config.SessionData;
configuration.SessionId = config.SessionId;
configuration.Settings = config.Settings;
logger.Info("Successfully retrieved the application configuration from the runtime.");

View file

@ -40,18 +40,20 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
try
{
connected = runtime.Connect(token);
logger.Info("Successfully connected to the runtime host.");
}
catch (Exception e)
{
logger.Error("An unexpected error occurred while trying to connect to the runtime host!", e);
logger.Error("An unexpected error occurred while trying to connect to the runtime!", e);
}
if (!connected)
if (connected)
{
logger.Info("Successfully connected to the runtime.");
}
else
{
Abort = true;
logger.Info("Failed to connect to the runtime. Aborting startup...");
logger.Error("Failed to connect to the runtime. Aborting startup...");
}
}

View file

@ -17,26 +17,32 @@ namespace SafeExamBrowser.Client.Communication
{
internal class ClientHost : BaseHost, IClientHost
{
public Guid StartupToken { private get; set; }
public ClientHost(string address, ILogger logger) : base(address, logger)
{
}
protected override IConnectResponse OnConnect(Guid? token)
protected override bool OnConnect(Guid? token)
{
// TODO
throw new NotImplementedException();
return StartupToken == token;
}
protected override void OnDisconnect(IMessage message)
protected override void OnDisconnect()
{
// TODO
throw new NotImplementedException();
}
protected override IResponse OnReceive(IMessage message)
{
// TODO
throw new NotImplementedException();
return null;
}
protected override IResponse OnReceive(Message message)
{
// TODO
return null;
}
}
}

View file

@ -87,7 +87,7 @@ namespace SafeExamBrowser.Client
var sequence = new OperationSequence(logger, operations);
ClientController = new ClientController(displayMonitor, logger, sequence, processMonitor, Taskbar, windowMonitor);
ClientController = new ClientController(displayMonitor, logger, sequence, processMonitor, runtimeProxy, Taskbar, windowMonitor);
}
private void Validate(string[] args)

View file

@ -15,7 +15,7 @@ namespace SafeExamBrowser.Configuration
[Serializable]
public class ClientConfiguration : IClientConfiguration
{
public ISessionData SessionData { get; set; }
public Guid SessionId { get; set; }
public ISettings Settings { get; set; }
public IRuntimeInfo RuntimeInfo { get; set; }
}

View file

@ -18,7 +18,7 @@ namespace SafeExamBrowser.Configuration
{
private RuntimeInfo runtimeInfo;
public ISessionData CurrentSessionData { get; private set; }
public ISession CurrentSession { get; private set; }
public ISettings CurrentSettings { get; private set; }
public IRuntimeInfo RuntimeInfo
@ -34,15 +34,17 @@ namespace SafeExamBrowser.Configuration
}
}
public ISessionData InitializeSessionData()
public ISession InitializeSession()
{
var sessionData = new SessionData();
var session = new Session
{
Id = Guid.NewGuid(),
StartupToken = Guid.NewGuid()
};
sessionData.Id = Guid.NewGuid();
CurrentSession = session;
CurrentSessionData = sessionData;
return sessionData;
return session;
}
public IClientConfiguration BuildClientConfiguration()
@ -50,7 +52,7 @@ namespace SafeExamBrowser.Configuration
return new ClientConfiguration
{
RuntimeInfo = RuntimeInfo,
SessionData = CurrentSessionData,
SessionId = CurrentSession.Id,
Settings = CurrentSettings
};
}
@ -64,10 +66,11 @@ namespace SafeExamBrowser.Configuration
public ISettings LoadDefaultSettings()
{
var settings = new Settings.Settings();
// TODO
settings.ServicePolicy = ServicePolicy.Optional;
var settings = new Settings.Settings
{
// TODO
ServicePolicy = ServicePolicy.Optional
};
CurrentSettings = settings;
@ -93,6 +96,7 @@ namespace SafeExamBrowser.Configuration
BrowserLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Browser.txt"),
ClientId = Guid.NewGuid(),
ClientAddress = $"{baseAddress}/client/{clientId}",
ClientExecutablePath = Path.Combine(Path.GetDirectoryName(executable.Location), $"{nameof(SafeExamBrowser)}.Client.exe"),
ClientLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Client.txt"),
DefaultSettingsFileName = "SebClientSettings.seb",
ProgramCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright,

View file

@ -19,6 +19,7 @@ namespace SafeExamBrowser.Configuration
public string BrowserCachePath { get; set; }
public string BrowserLogFile { get; set; }
public string ClientAddress { get; set; }
public string ClientExecutablePath { get; set; }
public Guid ClientId { get; set; }
public string ClientLogFile { get; set; }
public string DefaultSettingsFileName { get; set; }

View file

@ -55,7 +55,7 @@
<ItemGroup>
<Compile Include="ClientConfiguration.cs" />
<Compile Include="RuntimeInfo.cs" />
<Compile Include="SessionData.cs" />
<Compile Include="Session.cs" />
<Compile Include="Settings\BrowserSettings.cs" />
<Compile Include="Settings\KeyboardSettings.cs" />
<Compile Include="Settings\MouseSettings.cs" />

View file

@ -8,12 +8,14 @@
using System;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Configuration
{
[Serializable]
public class SessionData : ISessionData
public class Session : ISession
{
public IProcess ClientProcess { get; set; }
public Guid Id { get; set; }
public Guid StartupToken { get; set; }
}
}

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Taskbar;
namespace SafeExamBrowser.Contracts.Behaviour

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Behaviour
{
// TODO: Interface really needed?!

View file

@ -6,10 +6,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Contracts.Communication
{
public interface IClientHost : ICommunicationHost
{
// TODO: Necessary?
/// <summary>
/// The startup token used for initial authentication.
/// </summary>
Guid StartupToken { set; }
}
}

View file

@ -6,20 +6,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Communication.Responses;
namespace SafeExamBrowser.Contracts.Communication
{
public interface IClientProxy
public interface IClientProxy : ICommunicationProxy
{
/// <summary>
/// Tries to connect to the client host.
/// Instructs the client to submit its authentication data.
/// </summary>
bool Connect(Guid token);
/// <summary>
/// Disconnects from the client host.
/// </summary>
void Disconnect();
IAuthenticationResponse RequestAuthentication();
}
}

View file

@ -17,21 +17,20 @@ namespace SafeExamBrowser.Contracts.Communication
public interface ICommunication
{
/// <summary>
/// Initiates a connection to the host and must thus be called before any other opertion. To authenticate itself to the host, the
/// client can specify a security token. If the connection request was successful, a new session will be created by the host and
/// the client will subsequently be allowed to start communicating with the host.
/// Initiates a connection and must thus be called before any other opertion. Where applicable, an authentication token should be
/// specified. Returns a response indicating whether the connection request was successful or not.
/// </summary>
[OperationContract(IsInitiating = true)]
IConnectResponse Connect(Guid? token = null);
IConnectionResponse Connect(Guid? token = null);
/// <summary>
/// Closes the connection to the host and instructs it to terminate the communication session.
/// Closes a connection. Returns a response indicating whether the disconnection request was successful or not.
/// </summary>
[OperationContract(IsInitiating = false, IsTerminating = true)]
void Disconnect(IMessage message);
IDisconnectionResponse Disconnect(IDisconnectionMessage message);
/// <summary>
/// Sends a message to the host, optionally returning a response. If no response is expected, <c>null</c> will be returned.
/// Sends a message, optionally returning a response. If no response is expected, <c>null</c> will be returned.
/// </summary>
[OperationContract(IsInitiating = false)]
IResponse Send(IMessage message);

View file

@ -8,6 +8,8 @@
namespace SafeExamBrowser.Contracts.Communication
{
public delegate void CommunicationEventHandler();
public interface ICommunicationHost
{
/// <summary>

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Contracts.Communication
{
public interface ICommunicationProxy
{
/// <summary>
/// Tries to establish a connection. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool Connect(Guid? token = null);
/// <summary>
/// Terminates an open connection. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool Disconnect();
}
}

View file

@ -6,10 +6,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Contracts.Communication
{
public interface IRuntimeHost : ICommunicationHost
{
// TODO: Necessary?
/// <summary>
/// The startup token used for initial authentication.
/// </summary>
Guid StartupToken { set; }
/// <summary>
/// Event fired once the client has signaled that it is ready to operate.
/// </summary>
event CommunicationEventHandler ClientReady;
}
}

View file

@ -6,26 +6,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Contracts.Communication
{
public interface IRuntimeProxy
public interface IRuntimeProxy : ICommunicationProxy
{
/// <summary>
/// Tries to establish a connection with the runtime host, utilizing the specified authentication token.
/// </summary>
bool Connect(Guid token);
/// <summary>
/// Disconnects from the runtime host.
/// </summary>
void Disconnect();
/// <summary>
/// Retrieves the application configuration from the runtime host.
/// Retrieves the application configuration from the runtime.
/// </summary>
IClientConfiguration GetConfiguration();
/// <summary>
/// Informs the runtime that the client is ready.
/// </summary>
void InformClientReady();
}
}

View file

@ -11,7 +11,7 @@ using SafeExamBrowser.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Contracts.Communication
{
public interface IServiceProxy
public interface IServiceProxy : ICommunicationProxy
{
/// <summary>
/// Instructs the proxy to ignore all operations or simulate a connection, where applicable. To be set e.g. when the service
@ -19,16 +19,6 @@ namespace SafeExamBrowser.Contracts.Communication
/// </summary>
bool Ignore { set; }
/// <summary>
/// Tries to connect to the service host.
/// </summary>
bool Connect();
/// <summary>
/// Disconnects from the service host.
/// </summary>
void Disconnect();
/// <summary>
/// Instructs the service to start a new session according to the given parameters.
/// </summary>

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Communication.Messages
{
public interface IDisconnectionMessage : IMessage
{
}
}

View file

@ -13,8 +13,8 @@ namespace SafeExamBrowser.Contracts.Communication.Messages
public interface IMessage
{
/// <summary>
/// The communication token needed for authentication with the host.
/// The communication token needed for authentication.
/// </summary>
Guid CommunicationToken { get; }
Guid CommunicationToken { get; set; }
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Communication.Messages
{
public interface ISimpleMessage : IMessage
{
/// <summary>
/// The purport of the message.
/// </summary>
Message Purport { get; }
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Communication.Messages
{
public enum Message
{
/// <summary>
/// Requests an interlocutor to submit data for authentication.
/// </summary>
Authenticate = 1,
/// <summary>
/// Sent from the client to the runtime to indicate that the client is ready to operate.
/// </summary>
ClientIsReady,
/// <summary>
/// Sent from the client to the runtime to ask for the client configuration.
/// </summary>
ConfigurationNeeded,
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Communication.Responses
{
public interface IAuthenticationResponse : IResponse
{
/// <summary>
/// The process identifier used for authentication.
/// </summary>
int ProcessId { get; }
}
}

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Contracts.Communication.Responses
{
public interface IConfigurationResponse : IResponse
{
/// <summary>
/// The configuration to be used by the client.
/// </summary>
IClientConfiguration Configuration { get; }
}
}

View file

@ -10,15 +10,15 @@ using System;
namespace SafeExamBrowser.Contracts.Communication.Responses
{
public interface IConnectResponse : IResponse
public interface IConnectionResponse : IResponse
{
/// <summary>
/// The communication token needed for authentication with the host. Is <c>null</c> if a connection was refused.
/// The communication token needed for authentication. Is <c>null</c> if a connection was refused.
/// </summary>
Guid? CommunicationToken { get; }
/// <summary>
/// Indicates whether the host has accepted the connection request.
/// Indicates whether the connection request has been accepted.
/// </summary>
bool ConnectionEstablished { get; }
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Communication.Responses
{
public interface IDisconnectionResponse : IResponse
{
/// <summary>
/// Indicates whether the connection has been terminated.
/// </summary>
bool ConnectionTerminated { get; }
}
}

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Contracts.Configuration
@ -13,9 +14,9 @@ namespace SafeExamBrowser.Contracts.Configuration
public interface IClientConfiguration
{
/// <summary>
/// The session data to be used by the client.
/// The unique identifier for the current session.
/// </summary>
ISessionData SessionData { get; set; }
Guid SessionId { get; set; }
/// <summary>
/// The application settings to be used by the client.

View file

@ -14,10 +14,10 @@ namespace SafeExamBrowser.Contracts.Configuration
public interface IConfigurationRepository
{
/// <summary>
/// Retrieves the current session data, i.e. the last ones which were initialized. If no session has been initialized yet, this
/// Retrieves the current session, i.e. the last one which was initialized. If no session has been initialized yet, this
/// property will be <c>null</c>!
/// </summary>
ISessionData CurrentSessionData { get; }
ISession CurrentSession { get; }
/// <summary>
/// Retrieves the current settings, i.e. the last ones which were loaded. If no settings have been loaded yet, this property will
@ -31,14 +31,14 @@ namespace SafeExamBrowser.Contracts.Configuration
IRuntimeInfo RuntimeInfo { get; }
/// <summary>
/// Builds a configuration for the client component, given the currently loaded settings, session data and runtime information.
/// Builds a configuration for the client component, given the currently loaded settings, session and runtime information.
/// </summary>
IClientConfiguration BuildClientConfiguration();
/// <summary>
/// Initializes all relevant data for a new session.
/// </summary>
ISessionData InitializeSessionData();
ISession InitializeSession();
/// <summary>
/// Attempts to load settings from the specified path.

View file

@ -40,10 +40,15 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary>
string ClientAddress { get; }
/// <summary>
/// The executable path of the client compontent.
/// </summary>
string ClientExecutablePath { get; }
/// <summary>
/// The unique identifier for the currently running client instance.
///
/// TODO: Will need to be updated for each new client instance!
/// TODO: Will need to be updated for each new client instance! -> Remove if unused!
///
/// </summary>
Guid ClientId { get; }
@ -85,6 +90,9 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary>
/// The unique identifier for the currently running runtime instance.
///
/// TODO: Remove if unused!
///
/// </summary>
Guid RuntimeId { get; }

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Contracts.Configuration
{
public interface ISession
{
/// <summary>
/// The process information of the client instance associated to this session.
/// </summary>
IProcess ClientProcess { get; set; }
/// <summary>
/// The unique session identifier.
/// </summary>
Guid Id { get; }
/// <summary>
/// The startup token used by the client and runtime components for initial authentication.
/// </summary>
Guid StartupToken { get; }
}
}

View file

@ -60,15 +60,22 @@
<Compile Include="Communication\ICommunication.cs" />
<Compile Include="Communication\IClientProxy.cs" />
<Compile Include="Communication\ICommunicationHost.cs" />
<Compile Include="Communication\ICommunicationProxy.cs" />
<Compile Include="Communication\IRuntimeHost.cs" />
<Compile Include="Communication\IRuntimeProxy.cs" />
<Compile Include="Communication\IServiceProxy.cs" />
<Compile Include="Communication\Messages\IDisconnectionMessage.cs" />
<Compile Include="Communication\Messages\IMessage.cs" />
<Compile Include="Communication\Messages\ISimpleMessage.cs" />
<Compile Include="Communication\Messages\Message.cs" />
<Compile Include="Communication\Responses\IAuthenticationResponse.cs" />
<Compile Include="Communication\Responses\IConfigurationResponse.cs" />
<Compile Include="Communication\Responses\IDisconnectionResponse.cs" />
<Compile Include="Communication\Responses\IResponse.cs" />
<Compile Include="Communication\Responses\IConnectResponse.cs" />
<Compile Include="Communication\Responses\IConnectionResponse.cs" />
<Compile Include="Configuration\IClientConfiguration.cs" />
<Compile Include="Configuration\IRuntimeInfo.cs" />
<Compile Include="Configuration\ISessionData.cs" />
<Compile Include="Configuration\ISession.cs" />
<Compile Include="Configuration\Settings\ConfigurationMode.cs" />
<Compile Include="Behaviour\INotificationController.cs" />
<Compile Include="Behaviour\Operations\IOperation.cs" />
@ -131,10 +138,11 @@
<Compile Include="UserInterface\MessageBoxAction.cs" />
<Compile Include="UserInterface\MessageBoxIcon.cs" />
<Compile Include="WindowsApi\IBounds.cs" />
<Compile Include="WindowsApi\IDesktop.cs" />
<Compile Include="WindowsApi\INativeMethods.cs" />
<Compile Include="WindowsApi\IProcess.cs" />
<Compile Include="WindowsApi\IProcessFactory.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Client\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.WindowsApi
{
public interface IDesktop
{
/// <summary>
/// Retrieves the name of the currently active desktop.
/// </summary>
string CurrentName { get; }
}
}

View file

@ -6,15 +6,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Contracts.Configuration
namespace SafeExamBrowser.Contracts.WindowsApi
{
public interface ISessionData
public interface IProcess
{
/// <summary>
/// The unique session identifier.
/// The process identifier.
/// </summary>
Guid Id { get; }
int Id { get; }
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.WindowsApi
{
public interface IProcessFactory
{
/// <summary>
/// Starts a new process on the currently active desktop.
/// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">If the process could not be started.</exception>
IProcess StartNew(string path, params string[] args);
}
}

View file

@ -12,6 +12,7 @@ using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Responses;
namespace SafeExamBrowser.Core.Communication
{
@ -22,6 +23,9 @@ namespace SafeExamBrowser.Core.Communication
private ILogger logger;
private ServiceHost host;
protected Guid? CommunicationToken { get; private set; }
protected ILogger Logger { get; private set; }
public bool IsRunning
{
get { return host?.State == CommunicationState.Opened; }
@ -33,33 +37,64 @@ namespace SafeExamBrowser.Core.Communication
this.logger = logger;
}
protected abstract IConnectResponse OnConnect(Guid? token);
protected abstract void OnDisconnect(IMessage message);
protected abstract bool OnConnect(Guid? token);
protected abstract void OnDisconnect();
protected abstract IResponse OnReceive(IMessage message);
protected abstract IResponse OnReceive(Message message);
public IConnectResponse Connect(Guid? token = null)
public IConnectionResponse Connect(Guid? token = null)
{
logger.Debug($"Received connection request with token '{token}'.");
logger.Debug($"Received connection request with authentication token '{token}'.");
var response = OnConnect(token);
var response = new ConnectionResponse();
var connected = OnConnect(token);
logger.Debug($"{(response.ConnectionEstablished ? "Accepted" : "Denied")} connection request.");
if (connected)
{
response.CommunicationToken = CommunicationToken = Guid.NewGuid();
response.ConnectionEstablished = true;
}
logger.Debug($"{(connected ? "Accepted" : "Denied")} connection request.");
return response;
}
public void Disconnect(IMessage message)
public IDisconnectionResponse Disconnect(IDisconnectionMessage message)
{
var response = new DisconnectionResponse();
// TODO: Compare with ToString in BaseProxy - needed?
logger.Debug($"Received disconnection request with message '{message}'.");
OnDisconnect(message);
if (IsAuthorized(message?.CommunicationToken))
{
OnDisconnect();
CommunicationToken = null;
response.ConnectionTerminated = true;
}
return response;
}
public IResponse Send(IMessage message)
{
IResponse response = null;
logger.Debug($"Received message '{message}'.");
var response = OnReceive(message);
if (IsAuthorized(message?.CommunicationToken))
{
if (message is ISimpleMessage)
{
response = OnReceive((message as ISimpleMessage).Purport);
}
else
{
response = OnReceive(message);
}
}
logger.Debug($"Sending response '{response}'.");
@ -87,6 +122,11 @@ namespace SafeExamBrowser.Core.Communication
logger.Debug($"Terminated communication host for endpoint '{address}'.");
}
private bool IsAuthorized(Guid? token)
{
return CommunicationToken == token;
}
private void Host_Closed(object sender, EventArgs e)
{
logger.Debug("Communication host has been closed.");

View file

@ -12,15 +12,16 @@ using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Messages;
namespace SafeExamBrowser.Core.Communication
{
public abstract class BaseProxy : ICommunication
public abstract class BaseProxy : ICommunicationProxy
{
private string address;
private ICommunication channel;
private Guid? communicationToken;
protected Guid? CommunicationToken { get; private set; }
protected ILogger Logger { get; private set; }
public BaseProxy(string address, ILogger logger)
@ -29,7 +30,7 @@ namespace SafeExamBrowser.Core.Communication
this.Logger = logger;
}
public IConnectResponse Connect(Guid? token = null)
public virtual bool Connect(Guid? token = null)
{
var endpoint = new EndpointAddress(address);
@ -42,48 +43,47 @@ namespace SafeExamBrowser.Core.Communication
var response = channel.Connect(token);
CommunicationToken = response.CommunicationToken;
communicationToken = response.CommunicationToken;
Logger.Debug($"Tried to connect to {address}, connection was {(response.ConnectionEstablished ? "established" : "refused")}.");
return response.ConnectionEstablished;
}
public virtual bool Disconnect()
{
FailIfNotConnected(nameof(Disconnect));
var message = new DisconnectionMessage { CommunicationToken = communicationToken.Value };
var response = channel.Disconnect(message);
Logger.Debug($"{(response.ConnectionTerminated ? "Disconnected" : "Failed to disconnect")} from {address}.");
return response.ConnectionTerminated;
}
protected IResponse Send(IMessage message)
{
FailIfNotConnected(nameof(Send));
message.CommunicationToken = communicationToken.Value;
var response = channel.Send(message);
Logger.Debug($"Sent {ToString(message)}, got {ToString(response)}.");
return response;
}
public void Disconnect(IMessage message)
protected IResponse Send(Message purport)
{
if (ChannelIsReady())
{
channel.Disconnect(message);
Logger.Debug($"Disconnected from {address}, transmitting {ToString(message)}.");
}
FailIfNotConnected(nameof(Send));
throw new CommunicationException($"Tried to disconnect from host, but channel was {GetChannelState()}!");
}
var message = new SimpleMessage { Purport = purport };
var response = channel.Send(message);
public IResponse Send(IMessage message)
{
if (ChannelIsReady())
{
var response = channel.Send(message);
Logger.Debug($"Sent {ToString(message)}, got {ToString(response)}.");
Logger.Debug($"Sent {ToString(message)}, got {ToString(response)}.");
return response;
}
throw new CommunicationException($"Tried to send {ToString(message)}, but channel was {GetChannelState()}!");
}
protected void FailIfNotConnected(string operationName)
{
if (!CommunicationToken.HasValue)
{
throw new InvalidOperationException($"Cannot perform '{operationName}' before being connected to endpoint!");
}
}
private bool ChannelIsReady()
{
return channel != null && (channel as ICommunicationObject).State == CommunicationState.Opened;
return response;
}
private void BaseProxy_Closed(object sender, EventArgs e)
@ -111,6 +111,19 @@ namespace SafeExamBrowser.Core.Communication
Logger.Debug("Communication channel is opening...");
}
private void FailIfNotConnected(string operationName)
{
if (!communicationToken.HasValue)
{
throw new InvalidOperationException($"Cannot perform '{operationName}' before being connected to endpoint!");
}
if (channel == null || (channel as ICommunicationObject).State != CommunicationState.Opened)
{
throw new CommunicationException($"Tried to perform {operationName}, but channel was {GetChannelState()}!");
}
}
private string GetChannelState()
{
return channel == null ? "null" : $"in state '{(channel as ICommunicationObject).State}'";

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Core.Communication
{
public class ClientProxy : BaseProxy, IClientProxy
{
public ClientProxy(string address, ILogger logger) : base(address, logger)
{
}
public IAuthenticationResponse RequestAuthentication()
{
return (IAuthenticationResponse) Send(Message.ClientIsReady);
}
}
}

View file

@ -12,7 +12,7 @@ using SafeExamBrowser.Contracts.Communication.Messages;
namespace SafeExamBrowser.Core.Communication.Messages
{
[Serializable]
internal class Message : IMessage
internal class BaseMessage : IMessage
{
public Guid CommunicationToken { get; set; }
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Communication.Messages;
namespace SafeExamBrowser.Core.Communication.Messages
{
[Serializable]
internal class DisconnectionMessage : BaseMessage, IDisconnectionMessage
{
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Communication.Messages;
namespace SafeExamBrowser.Core.Communication.Messages
{
[Serializable]
internal class SimpleMessage : BaseMessage, ISimpleMessage
{
public Message Purport { get; set; }
}
}

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Communication.Responses;
namespace SafeExamBrowser.Core.Communication.Responses
{
[Serializable]
internal class ConnectionResponse : IConnectionResponse
{
public Guid? CommunicationToken { get; set; }
public bool ConnectionEstablished { get; set; }
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Communication.Responses;
namespace SafeExamBrowser.Core.Communication.Responses
{
[Serializable]
internal class DisconnectionResponse : IDisconnectionResponse
{
public bool ConnectionTerminated { get; set; }
}
}

View file

@ -6,11 +6,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Messages;
namespace SafeExamBrowser.Core.Communication
{
@ -20,22 +20,14 @@ namespace SafeExamBrowser.Core.Communication
{
}
public bool Connect(Guid token)
{
return base.Connect(token).ConnectionEstablished;
}
public void Disconnect()
{
FailIfNotConnected(nameof(Disconnect));
base.Disconnect(new Message { CommunicationToken = CommunicationToken.Value });
}
public IClientConfiguration GetConfiguration()
{
// TODO
throw new NotImplementedException();
return ((IConfigurationResponse) Send(Message.ConfigurationNeeded)).Configuration;
}
public void InformClientReady()
{
Send(Message.ClientIsReady);
}
}
}

View file

@ -10,7 +10,6 @@ using System;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Messages;
namespace SafeExamBrowser.Core.Communication
{
@ -22,26 +21,24 @@ namespace SafeExamBrowser.Core.Communication
{
}
public bool Connect()
public override bool Connect(Guid? token)
{
if (IgnoreOperation(nameof(Connect)))
{
return false;
}
return base.Connect().ConnectionEstablished;
return base.Connect();
}
public void Disconnect()
public override bool Disconnect()
{
if (IgnoreOperation(nameof(Disconnect)))
{
return;
return true;
}
FailIfNotConnected(nameof(Disconnect));
Disconnect(new Message { CommunicationToken = CommunicationToken.Value });
return base.Disconnect();
}
public void StartSession(Guid sessionId, ISettings settings)
@ -51,8 +48,6 @@ namespace SafeExamBrowser.Core.Communication
return;
}
FailIfNotConnected(nameof(StartSession));
// TODO: Send(new StartSessionMessage { Id = sessionId, Settings = settings });
}
@ -63,8 +58,6 @@ namespace SafeExamBrowser.Core.Communication
return;
}
FailIfNotConnected(nameof(StopSession));
// TODO
}

View file

@ -61,7 +61,12 @@
<Compile Include="Behaviour\Operations\OperationSequence.cs" />
<Compile Include="Communication\BaseProxy.cs" />
<Compile Include="Communication\BaseHost.cs" />
<Compile Include="Communication\Messages\Message.cs" />
<Compile Include="Communication\ClientProxy.cs" />
<Compile Include="Communication\Messages\DisconnectionMessage.cs" />
<Compile Include="Communication\Messages\BaseMessage.cs" />
<Compile Include="Communication\Messages\SimpleMessage.cs" />
<Compile Include="Communication\Responses\ConnectionResponse.cs" />
<Compile Include="Communication\Responses\DisconnectionResponse.cs" />
<Compile Include="Communication\RuntimeProxy.cs" />
<Compile Include="Communication\ServiceProxy.cs" />
<Compile Include="Logging\DefaultLogFormatter.cs" />
@ -87,8 +92,6 @@
<Name>SafeExamBrowser.Contracts</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Communication\Responses\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -44,38 +44,38 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestMethod]
public void MustConnectToService()
{
service.Setup(s => s.Connect()).Returns(true);
service.Setup(s => s.Connect(null)).Returns(true);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Mandatory);
sut.Perform();
service.Setup(s => s.Connect()).Returns(true);
service.Setup(s => s.Connect(null)).Returns(true);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Optional);
sut.Perform();
service.Verify(s => s.Connect(), Times.Exactly(2));
service.Verify(s => s.Connect(null), Times.Exactly(2));
}
[TestMethod]
public void MustNotFailIfServiceNotAvailable()
{
service.Setup(s => s.Connect()).Returns(false);
service.Setup(s => s.Connect(null)).Returns(false);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Mandatory);
sut.Perform();
service.Setup(s => s.Connect()).Returns(false);
service.Setup(s => s.Connect(null)).Returns(false);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Optional);
sut.Perform();
service.Setup(s => s.Connect()).Throws<Exception>();
service.Setup(s => s.Connect(null)).Throws<Exception>();
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Mandatory);
sut.Perform();
service.Setup(s => s.Connect()).Throws<Exception>();
service.Setup(s => s.Connect(null)).Throws<Exception>();
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Optional);
sut.Perform();
@ -84,7 +84,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestMethod]
public void MustAbortIfServiceMandatoryAndNotAvailable()
{
service.Setup(s => s.Connect()).Returns(false);
service.Setup(s => s.Connect(null)).Returns(false);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Mandatory);
sut.Perform();
@ -95,7 +95,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestMethod]
public void MustNotAbortIfServiceOptionalAndNotAvailable()
{
service.Setup(s => s.Connect()).Returns(false);
service.Setup(s => s.Connect(null)).Returns(false);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Optional);
sut.Perform();
@ -107,13 +107,13 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestMethod]
public void MustDisconnectWhenReverting()
{
service.Setup(s => s.Connect()).Returns(true);
service.Setup(s => s.Connect(null)).Returns(true);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Mandatory);
sut.Perform();
sut.Revert();
service.Setup(s => s.Connect()).Returns(true);
service.Setup(s => s.Connect(null)).Returns(true);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Optional);
sut.Perform();
@ -125,7 +125,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestMethod]
public void MustNotFailWhenDisconnecting()
{
service.Setup(s => s.Connect()).Returns(true);
service.Setup(s => s.Connect(null)).Returns(true);
service.Setup(s => s.Disconnect()).Throws<Exception>();
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Optional);
@ -138,25 +138,25 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestMethod]
public void MustNotDisconnnectIfNotAvailable()
{
service.Setup(s => s.Connect()).Returns(false);
service.Setup(s => s.Connect(null)).Returns(false);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Mandatory);
sut.Perform();
sut.Revert();
service.Setup(s => s.Connect()).Returns(false);
service.Setup(s => s.Connect(null)).Returns(false);
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Optional);
sut.Perform();
sut.Revert();
service.Setup(s => s.Connect()).Throws<Exception>();
service.Setup(s => s.Connect(null)).Throws<Exception>();
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Mandatory);
sut.Perform();
sut.Revert();
service.Setup(s => s.Connect()).Throws<Exception>();
service.Setup(s => s.Connect(null)).Throws<Exception>();
configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Optional);
sut.Perform();

View file

@ -37,13 +37,14 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Info($"Initializing kiosk mode '{kioskMode}'...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeKioskMode);
if (kioskMode == KioskMode.CreateNewDesktop)
switch (kioskMode)
{
CreateNewDesktop();
}
else
{
DisableExplorerShell();
case KioskMode.CreateNewDesktop:
CreateNewDesktop();
break;
case KioskMode.DisableExplorerShell:
DisableExplorerShell();
break;
}
}
@ -57,13 +58,14 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Info($"Reverting kiosk mode '{kioskMode}'...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_RevertKioskMode);
if (kioskMode == KioskMode.CreateNewDesktop)
switch (kioskMode)
{
CloseNewDesktop();
}
else
{
RestartExplorerShell();
case KioskMode.CreateNewDesktop:
CloseNewDesktop();
break;
case KioskMode.DisableExplorerShell:
RestartExplorerShell();
break;
}
}

View file

@ -54,7 +54,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
if (mandatory && !connected)
{
Abort = true;
logger.Info("Aborting startup because the service is mandatory but not available!");
logger.Error("Aborting startup because the service is mandatory but not available!");
}
else
{
@ -81,7 +81,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
}
catch (Exception e)
{
logger.Error("Failed to disconnect from service host!", e);
logger.Error("Failed to disconnect from the service!", e);
}
}
}

View file

@ -9,12 +9,19 @@
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
internal class SessionSequenceEndOperation : SessionSequenceOperation
{
public SessionSequenceEndOperation(IConfigurationRepository configuration, ILogger logger, IServiceProxy serviceProxy) : base(configuration, logger, serviceProxy)
public SessionSequenceEndOperation(
IClientProxy client,
IConfigurationRepository configuration,
ILogger logger,
IProcessFactory processFactory,
IRuntimeHost runtimeHost,
IServiceProxy service) : base(client, configuration, logger, processFactory, runtimeHost, service)
{
}

View file

@ -6,31 +6,46 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Threading;
using SafeExamBrowser.Contracts.Behaviour.Operations;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
internal abstract class SessionSequenceOperation : IOperation
{
private bool sessionRunning;
private IClientProxy client;
private IConfigurationRepository configuration;
private ILogger logger;
private IServiceProxy serviceProxy;
private ISessionData sessionData;
private IProcessFactory processFactory;
private IRuntimeHost runtimeHost;
private IServiceProxy service;
private ISession session;
public bool Abort { get; private set; }
public IProgressIndicator ProgressIndicator { private get; set; }
public SessionSequenceOperation(IConfigurationRepository configuration, ILogger logger, IServiceProxy serviceProxy)
public SessionSequenceOperation(
IClientProxy client,
IConfigurationRepository configuration,
ILogger logger,
IProcessFactory processFactory,
IRuntimeHost runtimeHost,
IServiceProxy service)
{
this.client = client;
this.configuration = configuration;
this.logger = logger;
this.serviceProxy = serviceProxy;
this.processFactory = processFactory;
this.runtimeHost = runtimeHost;
this.service = service;
}
public abstract void Perform();
@ -42,34 +57,80 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Info("Starting new session...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StartSession, true);
sessionData = configuration.InitializeSessionData();
serviceProxy.StartSession(sessionData.Id, configuration.CurrentSettings);
session = configuration.InitializeSession();
runtimeHost.StartupToken = session.StartupToken;
// TODO:
// - Create and connect to client
// - Verify session integrity and start event handling -> in runtime controller?
System.Threading.Thread.Sleep(5000);
service.StartSession(session.Id, configuration.CurrentSettings);
sessionRunning = true;
logger.Info($"Successfully started new session with identifier '{sessionData.Id}'.");
try
{
StartClient();
}
catch (Exception e)
{
service.StopSession(session.Id);
logger.Error("Failed to start client!", e);
}
if (sessionRunning)
{
logger.Info($"Successfully started new session with identifier '{session.Id}'.");
}
else
{
Abort = true;
logger.Info($"Failed to start new session! Aborting...");
}
}
protected void StopSession()
{
if (sessionRunning)
{
logger.Info($"Stopping session with identifier '{sessionData.Id}'...");
logger.Info($"Stopping session with identifier '{session.Id}'...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopSession, true);
serviceProxy.StopSession(sessionData.Id);
service.StopSession(session.Id);
// TODO:
// - Terminate client (or does it terminate itself?)
// - Stop event handling and verify session termination -> in runtime controller?
System.Threading.Thread.Sleep(5000);
sessionRunning = false;
logger.Info($"Successfully stopped session with identifier '{sessionData.Id}'.");
logger.Info($"Successfully stopped session with identifier '{session.Id}'.");
}
}
private void StartClient()
{
var clientReady = new AutoResetEvent(false);
var clientReadyHandler = new CommunicationEventHandler(() => clientReady.Set());
var clientExecutable = configuration.RuntimeInfo.ClientExecutablePath;
var hostUri = configuration.RuntimeInfo.RuntimeAddress;
var token = session.StartupToken.ToString("D");
runtimeHost.ClientReady += clientReadyHandler;
session.ClientProcess = processFactory.StartNew(clientExecutable, hostUri, token);
clientReady.WaitOne();
runtimeHost.ClientReady -= clientReadyHandler;
if (client.Connect(session.StartupToken))
{
var response = client.RequestAuthentication();
// TODO: Further integrity checks necessary?
if (session.ClientProcess.Id == response.ProcessId)
{
sessionRunning = true;
}
else
{
logger.Error("Failed to verify client integrity!");
}
}
else
{
logger.Error("Failed to connect to client!");
}
}
}

View file

@ -9,12 +9,19 @@
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations
{
internal class SessionSequenceStartOperation : SessionSequenceOperation
{
public SessionSequenceStartOperation(IConfigurationRepository configuration, ILogger logger, IServiceProxy serviceProxy) : base(configuration, logger, serviceProxy)
public SessionSequenceStartOperation(
IClientProxy client,
IConfigurationRepository configuration,
ILogger logger,
IProcessFactory processFactory,
IRuntimeHost runtimeHost,
IServiceProxy service) : base(client, configuration, logger, processFactory, runtimeHost, service)
{
}

View file

@ -22,6 +22,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
{
private bool sessionRunning;
private IClientProxy client;
private IConfigurationRepository configuration;
private ILogger logger;
private IOperationSequence bootstrapSequence;
@ -29,29 +30,31 @@ namespace SafeExamBrowser.Runtime.Behaviour
private IRuntimeHost runtimeHost;
private IRuntimeInfo runtimeInfo;
private IRuntimeWindow runtimeWindow;
private IServiceProxy serviceProxy;
private IServiceProxy service;
private ISplashScreen splashScreen;
private Action shutdown;
private IUserInterfaceFactory uiFactory;
public RuntimeController(
IClientProxy client,
IConfigurationRepository configuration,
ILogger logger,
IOperationSequence bootstrapSequence,
IOperationSequence sessionSequence,
IRuntimeHost runtimeHost,
IRuntimeInfo runtimeInfo,
IServiceProxy serviceProxy,
IServiceProxy service,
Action shutdown,
IUserInterfaceFactory uiFactory)
{
this.client = client;
this.configuration = configuration;
this.logger = logger;
this.bootstrapSequence = bootstrapSequence;
this.sessionSequence = sessionSequence;
this.runtimeHost = runtimeHost;
this.runtimeInfo = runtimeInfo;
this.serviceProxy = serviceProxy;
this.service = service;
this.shutdown = shutdown;
this.uiFactory = uiFactory;
}
@ -103,10 +106,6 @@ namespace SafeExamBrowser.Runtime.Behaviour
logger.Log(string.Empty);
logger.Info("--- Initiating shutdown procedure ---");
// TODO:
// - Disconnect from service
// - Terminate runtime communication host
// - Revert kiosk mode (or do that when stopping session?)
var success = bootstrapSequence.TryRevert();
if (success)
@ -142,7 +141,6 @@ namespace SafeExamBrowser.Runtime.Behaviour
else
{
uiFactory.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error);
logger.Info($"Failed to start new session. Terminating application...");
if (!initial)
{

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Runtime.Communication.Responses
{
[Serializable]
internal class ConfigurationResponse : IConfigurationResponse
{
public IClientConfiguration Configuration { get; set; }
}
}

View file

@ -10,33 +10,54 @@ using System;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication;
using SafeExamBrowser.Runtime.Communication.Responses;
namespace SafeExamBrowser.Runtime.Communication
{
internal class RuntimeHost : BaseHost, IRuntimeHost
{
public RuntimeHost(string address, ILogger logger) : base(address, logger)
private IConfigurationRepository configuration;
public Guid StartupToken { private get; set; }
public event CommunicationEventHandler ClientReady;
public RuntimeHost(string address, IConfigurationRepository configuration, ILogger logger) : base(address, logger)
{
this.configuration = configuration;
}
protected override IConnectResponse OnConnect(Guid? token = null)
protected override bool OnConnect(Guid? token = null)
{
// TODO
throw new NotImplementedException();
return StartupToken == token;
}
protected override void OnDisconnect(IMessage message)
protected override void OnDisconnect()
{
// TODO
throw new NotImplementedException();
}
protected override IResponse OnReceive(IMessage message)
{
// TODO
throw new NotImplementedException();
return null;
}
protected override IResponse OnReceive(Message message)
{
switch (message)
{
case Message.ClientIsReady:
ClientReady?.Invoke();
break;
case Message.ConfigurationNeeded:
return new ConfigurationResponse { Configuration = configuration.BuildClientConfiguration() };
}
return null;
}
}
}

View file

@ -39,6 +39,7 @@ namespace SafeExamBrowser.Runtime
var args = Environment.GetCommandLineArgs();
var configuration = new ConfigurationRepository();
var nativeMethods = new NativeMethods();
Action shutdown = Application.Current.Shutdown;
logger = new Logger();
runtimeInfo = configuration.RuntimeInfo;
@ -48,7 +49,10 @@ namespace SafeExamBrowser.Runtime
var text = new Text(logger);
var uiFactory = new UserInterfaceFactory(text);
var runtimeHost = new RuntimeHost(runtimeInfo.RuntimeAddress, new ModuleLogger(logger, typeof(RuntimeHost)));
var desktop = new Desktop(new ModuleLogger(logger, typeof(Desktop)));
var processFactory = new ProcessFactory(desktop, new ModuleLogger(logger, typeof(ProcessFactory)));
var clientProxy = new ClientProxy(runtimeInfo.ClientAddress, new ModuleLogger(logger, typeof(ClientProxy)));
var runtimeHost = new RuntimeHost(runtimeInfo.RuntimeAddress, configuration, new ModuleLogger(logger, typeof(RuntimeHost)));
var serviceProxy = new ServiceProxy(runtimeInfo.ServiceAddress, new ModuleLogger(logger, typeof(ServiceProxy)));
var bootstrapOperations = new Queue<IOperation>();
@ -57,16 +61,16 @@ namespace SafeExamBrowser.Runtime
bootstrapOperations.Enqueue(new I18nOperation(logger, text));
bootstrapOperations.Enqueue(new CommunicationOperation(runtimeHost, logger));
sessionOperations.Enqueue(new SessionSequenceStartOperation(configuration, logger, serviceProxy));
sessionOperations.Enqueue(new SessionSequenceStartOperation(clientProxy, configuration, logger, processFactory, runtimeHost, serviceProxy));
sessionOperations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeInfo, text, uiFactory, args));
sessionOperations.Enqueue(new ServiceConnectionOperation(configuration, logger, serviceProxy, text));
sessionOperations.Enqueue(new KioskModeOperation(logger, configuration));
sessionOperations.Enqueue(new SessionSequenceEndOperation(configuration, logger, serviceProxy));
sessionOperations.Enqueue(new SessionSequenceEndOperation(clientProxy, configuration, logger, processFactory, runtimeHost, serviceProxy));
var boostrapSequence = new OperationSequence(logger, bootstrapOperations);
var sessionSequence = new OperationSequence(logger, sessionOperations);
RuntimeController = new RuntimeController(configuration, logger, boostrapSequence, sessionSequence, runtimeHost, runtimeInfo, serviceProxy, Application.Current.Shutdown, uiFactory);
RuntimeController = new RuntimeController(clientProxy, configuration, logger, boostrapSequence, sessionSequence, runtimeHost, runtimeInfo, serviceProxy, shutdown, uiFactory);
}
internal void LogStartupInformation()

View file

@ -93,6 +93,7 @@
<Compile Include="Behaviour\Operations\SessionSequenceEndOperation.cs" />
<Compile Include="Behaviour\Operations\SessionSequenceOperation.cs" />
<Compile Include="Behaviour\Operations\SessionSequenceStartOperation.cs" />
<Compile Include="Communication\Responses\ConfigurationResponse.cs" />
<Compile Include="Communication\RuntimeHost.cs" />
<Compile Include="CompositionRoot.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
@ -162,4 +163,7 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>xcopy /E /Y "$(SolutionDir)SafeExamBrowser.Client\bin\$(PlatformName)\$(ConfigurationName)" "$(ProjectDir)bin\$(PlatformName)\$(ConfigurationName)"</PostBuildEvent>
</PropertyGroup>
</Project>

View file

@ -32,6 +32,13 @@ namespace SafeExamBrowser.WindowsApi.Constants
/// </summary>
internal const int MIN_ALL = 419;
/// <summary>
/// Specifies the default priority class for processes, i.e. a process with no special scheduling needs.
///
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686219(v=vs.85).aspx.
/// </summary>
internal const int NORMAL_PRIORITY_CLASS = 0x20;
/// <summary>
/// The callback function is not mapped into the address space of the process that generates the event. Because the hook function
/// is called across process boundaries, the system must queue events. Although this method is asynchronous, events are guaranteed

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.WindowsApi
{
public class Desktop : IDesktop
{
private ILogger logger;
// TODO!
public string CurrentName => "Default";
public Desktop(ILogger logger)
{
this.logger = logger;
}
}
}

View file

@ -17,6 +17,19 @@ namespace SafeExamBrowser.WindowsApi
/// </summary>
internal class Kernel32
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
[In] ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr GetModuleHandle(string lpModuleName);

View file

@ -203,7 +203,7 @@ namespace SafeExamBrowser.WindowsApi
{
var handle = GetShellWindowHandle();
User32.SendMessage(handle, Constant.WM_COMMAND, (IntPtr)Constant.MIN_ALL, IntPtr.Zero);
User32.SendMessage(handle, Constant.WM_COMMAND, (IntPtr) Constant.MIN_ALL, IntPtr.Zero);
}
public void PostCloseMessageToShell()
@ -245,10 +245,10 @@ namespace SafeExamBrowser.WindowsApi
public IntPtr RegisterSystemForegroundEvent(Action<IntPtr> callback)
{
EventProc eventProc = (IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) =>
void eventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
callback(hwnd);
};
}
var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_FOREGROUND, Constant.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, eventProc, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);
@ -262,10 +262,10 @@ namespace SafeExamBrowser.WindowsApi
public IntPtr RegisterSystemCaptureStartEvent(Action<IntPtr> callback)
{
EventProc eventProc = (IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) =>
void eventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
callback(hwnd);
};
}
var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_CAPTURESTART, Constant.EVENT_SYSTEM_CAPTURESTART, IntPtr.Zero, eventProc, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.WindowsApi
{
internal class Process : IProcess
{
private System.Diagnostics.Process process;
public int Id
{
get { return process.Id; }
}
public Process(int id)
{
process = System.Diagnostics.Process.GetProcessById(id);
}
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi
{
public class ProcessFactory : IProcessFactory
{
private IDesktop desktop;
private ILogger logger;
public ProcessFactory(IDesktop desktop, ILogger logger)
{
this.desktop = desktop;
this.logger = logger;
}
public IProcess StartNew(string path, params string[] args)
{
var processInfo = new PROCESS_INFORMATION();
var processAttributes = new SECURITY_ATTRIBUTES();
var threadAttributes = new SECURITY_ATTRIBUTES();
var startupInfo = new STARTUPINFO();
startupInfo.cb = Marshal.SizeOf(startupInfo);
startupInfo.lpDesktop = desktop.CurrentName;
var success = Kernel32.CreateProcess(
null,
path,
ref processAttributes,
ref threadAttributes,
true,
Constant.NORMAL_PRIORITY_CLASS,
IntPtr.Zero,
null,
ref startupInfo,
out processInfo);
if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
return new Process(processInfo.dwProcessId);
}
}
}

View file

@ -54,7 +54,10 @@
<ItemGroup>
<Compile Include="Constants\Constant.cs" />
<Compile Include="Constants\HookType.cs" />
<Compile Include="Desktop.cs" />
<Compile Include="Monitoring\MouseHook.cs" />
<Compile Include="Process.cs" />
<Compile Include="ProcessFactory.cs" />
<Compile Include="Types\Bounds.cs" />
<Compile Include="Types\EXECUTION_STATE.cs" />
<Compile Include="Types\KBDLLHOOKSTRUCT.cs" />
@ -69,7 +72,10 @@
<Compile Include="Constants\SPIF.cs" />
<Compile Include="Types\MSLLHOOKSTRUCT.cs" />
<Compile Include="Types\POINT.cs" />
<Compile Include="Types\PROCESS_INFORMATION.cs" />
<Compile Include="Types\RECT.cs" />
<Compile Include="Types\SECURITY_ATTRIBUTES.cs" />
<Compile Include="Types\STARTUPINFO.cs" />
<Compile Include="User32.cs" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See http://pinvoke.net/default.aspx/Structures/PROCESS_INFORMATION.html.
/// See https://msdn.microsoft.com/en-us/library/ms684873(VS.85).aspx.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See http://pinvoke.net/default.aspx/Structures/SECURITY_ATTRIBUTES.html.
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa379560(v=vs.85).aspx.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Runtime.InteropServices;
namespace SafeExamBrowser.WindowsApi.Types
{
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx.
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
}