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<ITaskbar> taskbarMock;
private Mock<IWindowMonitor> windowMonitorMock; private Mock<IWindowMonitor> windowMonitorMock;
private IClientController sut; //private IClientController sut;
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()

View file

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

View file

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

View file

@ -40,18 +40,20 @@ namespace SafeExamBrowser.Client.Behaviour.Operations
try try
{ {
connected = runtime.Connect(token); connected = runtime.Connect(token);
logger.Info("Successfully connected to the runtime host.");
} }
catch (Exception e) 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; 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 internal class ClientHost : BaseHost, IClientHost
{ {
public Guid StartupToken { private get; set; }
public ClientHost(string address, ILogger logger) : base(address, logger) public ClientHost(string address, ILogger logger) : base(address, logger)
{ {
} }
protected override IConnectResponse OnConnect(Guid? token) protected override bool OnConnect(Guid? token)
{ {
// TODO return StartupToken == token;
throw new NotImplementedException();
} }
protected override void OnDisconnect(IMessage message) protected override void OnDisconnect()
{ {
// TODO // TODO
throw new NotImplementedException();
} }
protected override IResponse OnReceive(IMessage message) protected override IResponse OnReceive(IMessage message)
{ {
// TODO // 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); 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) private void Validate(string[] args)

View file

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

View file

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

View file

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

View file

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

View file

@ -8,12 +8,14 @@
using System; using System;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Configuration namespace SafeExamBrowser.Configuration
{ {
[Serializable] public class Session : ISession
public class SessionData : ISessionData
{ {
public IProcess ClientProcess { get; set; }
public Guid Id { 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.Taskbar; using SafeExamBrowser.Contracts.UserInterface.Taskbar;
namespace SafeExamBrowser.Contracts.Behaviour namespace SafeExamBrowser.Contracts.Behaviour

View file

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

View file

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

View file

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

View file

@ -8,6 +8,8 @@
namespace SafeExamBrowser.Contracts.Communication namespace SafeExamBrowser.Contracts.Communication
{ {
public delegate void CommunicationEventHandler();
public interface ICommunicationHost public interface ICommunicationHost
{ {
/// <summary> /// <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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
namespace SafeExamBrowser.Contracts.Communication namespace SafeExamBrowser.Contracts.Communication
{ {
public interface IRuntimeHost : ICommunicationHost 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Contracts.Communication namespace SafeExamBrowser.Contracts.Communication
{ {
public interface IRuntimeProxy public interface IRuntimeProxy : ICommunicationProxy
{ {
/// <summary> /// <summary>
/// Tries to establish a connection with the runtime host, utilizing the specified authentication token. /// Retrieves the application configuration from the runtime.
/// </summary>
bool Connect(Guid token);
/// <summary>
/// Disconnects from the runtime host.
/// </summary>
void Disconnect();
/// <summary>
/// Retrieves the application configuration from the runtime host.
/// </summary> /// </summary>
IClientConfiguration GetConfiguration(); 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 namespace SafeExamBrowser.Contracts.Communication
{ {
public interface IServiceProxy public interface IServiceProxy : ICommunicationProxy
{ {
/// <summary> /// <summary>
/// Instructs the proxy to ignore all operations or simulate a connection, where applicable. To be set e.g. when the service /// 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> /// </summary>
bool Ignore { set; } bool Ignore { set; }
/// <summary>
/// Tries to connect to the service host.
/// </summary>
bool Connect();
/// <summary>
/// Disconnects from the service host.
/// </summary>
void Disconnect();
/// <summary> /// <summary>
/// Instructs the service to start a new session according to the given parameters. /// Instructs the service to start a new session according to the given parameters.
/// </summary> /// </summary>

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 public interface IMessage
{ {
/// <summary> /// <summary>
/// The communication token needed for authentication with the host. /// The communication token needed for authentication.
/// </summary> /// </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 namespace SafeExamBrowser.Contracts.Communication.Responses
{ {
public interface IConnectResponse : IResponse public interface IConnectionResponse : IResponse
{ {
/// <summary> /// <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> /// </summary>
Guid? CommunicationToken { get; } Guid? CommunicationToken { get; }
/// <summary> /// <summary>
/// Indicates whether the host has accepted the connection request. /// Indicates whether the connection request has been accepted.
/// </summary> /// </summary>
bool ConnectionEstablished { get; } 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Contracts.Configuration namespace SafeExamBrowser.Contracts.Configuration
@ -13,9 +14,9 @@ namespace SafeExamBrowser.Contracts.Configuration
public interface IClientConfiguration public interface IClientConfiguration
{ {
/// <summary> /// <summary>
/// The session data to be used by the client. /// The unique identifier for the current session.
/// </summary> /// </summary>
ISessionData SessionData { get; set; } Guid SessionId { get; set; }
/// <summary> /// <summary>
/// The application settings to be used by the client. /// The application settings to be used by the client.

View file

@ -14,10 +14,10 @@ namespace SafeExamBrowser.Contracts.Configuration
public interface IConfigurationRepository public interface IConfigurationRepository
{ {
/// <summary> /// <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>! /// property will be <c>null</c>!
/// </summary> /// </summary>
ISessionData CurrentSessionData { get; } ISession CurrentSession { get; }
/// <summary> /// <summary>
/// Retrieves the current settings, i.e. the last ones which were loaded. If no settings have been loaded yet, this property will /// 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; } IRuntimeInfo RuntimeInfo { get; }
/// <summary> /// <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> /// </summary>
IClientConfiguration BuildClientConfiguration(); IClientConfiguration BuildClientConfiguration();
/// <summary> /// <summary>
/// Initializes all relevant data for a new session. /// Initializes all relevant data for a new session.
/// </summary> /// </summary>
ISessionData InitializeSessionData(); ISession InitializeSession();
/// <summary> /// <summary>
/// Attempts to load settings from the specified path. /// Attempts to load settings from the specified path.

View file

@ -40,10 +40,15 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary> /// </summary>
string ClientAddress { get; } string ClientAddress { get; }
/// <summary>
/// The executable path of the client compontent.
/// </summary>
string ClientExecutablePath { get; }
/// <summary> /// <summary>
/// The unique identifier for the currently running client instance. /// 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> /// </summary>
Guid ClientId { get; } Guid ClientId { get; }
@ -85,6 +90,9 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary> /// <summary>
/// The unique identifier for the currently running runtime instance. /// The unique identifier for the currently running runtime instance.
///
/// TODO: Remove if unused!
///
/// </summary> /// </summary>
Guid RuntimeId { get; } 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\ICommunication.cs" />
<Compile Include="Communication\IClientProxy.cs" /> <Compile Include="Communication\IClientProxy.cs" />
<Compile Include="Communication\ICommunicationHost.cs" /> <Compile Include="Communication\ICommunicationHost.cs" />
<Compile Include="Communication\ICommunicationProxy.cs" />
<Compile Include="Communication\IRuntimeHost.cs" /> <Compile Include="Communication\IRuntimeHost.cs" />
<Compile Include="Communication\IRuntimeProxy.cs" /> <Compile Include="Communication\IRuntimeProxy.cs" />
<Compile Include="Communication\IServiceProxy.cs" /> <Compile Include="Communication\IServiceProxy.cs" />
<Compile Include="Communication\Messages\IDisconnectionMessage.cs" />
<Compile Include="Communication\Messages\IMessage.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\IResponse.cs" />
<Compile Include="Communication\Responses\IConnectResponse.cs" /> <Compile Include="Communication\Responses\IConnectionResponse.cs" />
<Compile Include="Configuration\IClientConfiguration.cs" /> <Compile Include="Configuration\IClientConfiguration.cs" />
<Compile Include="Configuration\IRuntimeInfo.cs" /> <Compile Include="Configuration\IRuntimeInfo.cs" />
<Compile Include="Configuration\ISessionData.cs" /> <Compile Include="Configuration\ISession.cs" />
<Compile Include="Configuration\Settings\ConfigurationMode.cs" /> <Compile Include="Configuration\Settings\ConfigurationMode.cs" />
<Compile Include="Behaviour\INotificationController.cs" /> <Compile Include="Behaviour\INotificationController.cs" />
<Compile Include="Behaviour\Operations\IOperation.cs" /> <Compile Include="Behaviour\Operations\IOperation.cs" />
@ -131,10 +138,11 @@
<Compile Include="UserInterface\MessageBoxAction.cs" /> <Compile Include="UserInterface\MessageBoxAction.cs" />
<Compile Include="UserInterface\MessageBoxIcon.cs" /> <Compile Include="UserInterface\MessageBoxIcon.cs" />
<Compile Include="WindowsApi\IBounds.cs" /> <Compile Include="WindowsApi\IBounds.cs" />
<Compile Include="WindowsApi\IDesktop.cs" />
<Compile Include="WindowsApi\INativeMethods.cs" /> <Compile Include="WindowsApi\INativeMethods.cs" />
<Compile Include="WindowsApi\IProcess.cs" />
<Compile Include="WindowsApi\IProcessFactory.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup />
<Folder Include="Client\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System; namespace SafeExamBrowser.Contracts.WindowsApi
namespace SafeExamBrowser.Contracts.Configuration
{ {
public interface ISessionData public interface IProcess
{ {
/// <summary> /// <summary>
/// The unique session identifier. /// The process identifier.
/// </summary> /// </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.Messages;
using SafeExamBrowser.Contracts.Communication.Responses; using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Responses;
namespace SafeExamBrowser.Core.Communication namespace SafeExamBrowser.Core.Communication
{ {
@ -22,6 +23,9 @@ namespace SafeExamBrowser.Core.Communication
private ILogger logger; private ILogger logger;
private ServiceHost host; private ServiceHost host;
protected Guid? CommunicationToken { get; private set; }
protected ILogger Logger { get; private set; }
public bool IsRunning public bool IsRunning
{ {
get { return host?.State == CommunicationState.Opened; } get { return host?.State == CommunicationState.Opened; }
@ -33,33 +37,64 @@ namespace SafeExamBrowser.Core.Communication
this.logger = logger; this.logger = logger;
} }
protected abstract IConnectResponse OnConnect(Guid? token); protected abstract bool OnConnect(Guid? token);
protected abstract void OnDisconnect(IMessage message); protected abstract void OnDisconnect();
protected abstract IResponse OnReceive(IMessage message); 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; 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}'."); 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) public IResponse Send(IMessage message)
{ {
IResponse response = null;
logger.Debug($"Received message '{message}'."); 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}'."); logger.Debug($"Sending response '{response}'.");
@ -87,6 +122,11 @@ namespace SafeExamBrowser.Core.Communication
logger.Debug($"Terminated communication host for endpoint '{address}'."); logger.Debug($"Terminated communication host for endpoint '{address}'.");
} }
private bool IsAuthorized(Guid? token)
{
return CommunicationToken == token;
}
private void Host_Closed(object sender, EventArgs e) private void Host_Closed(object sender, EventArgs e)
{ {
logger.Debug("Communication host has been closed."); 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.Messages;
using SafeExamBrowser.Contracts.Communication.Responses; using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Messages;
namespace SafeExamBrowser.Core.Communication namespace SafeExamBrowser.Core.Communication
{ {
public abstract class BaseProxy : ICommunication public abstract class BaseProxy : ICommunicationProxy
{ {
private string address; private string address;
private ICommunication channel; private ICommunication channel;
private Guid? communicationToken;
protected Guid? CommunicationToken { get; private set; }
protected ILogger Logger { get; private set; } protected ILogger Logger { get; private set; }
public BaseProxy(string address, ILogger logger) public BaseProxy(string address, ILogger logger)
@ -29,7 +30,7 @@ namespace SafeExamBrowser.Core.Communication
this.Logger = logger; this.Logger = logger;
} }
public IConnectResponse Connect(Guid? token = null) public virtual bool Connect(Guid? token = null)
{ {
var endpoint = new EndpointAddress(address); var endpoint = new EndpointAddress(address);
@ -42,48 +43,47 @@ namespace SafeExamBrowser.Core.Communication
var response = channel.Connect(token); var response = channel.Connect(token);
CommunicationToken = response.CommunicationToken; communicationToken = response.CommunicationToken;
Logger.Debug($"Tried to connect to {address}, connection was {(response.ConnectionEstablished ? "established" : "refused")}."); 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; return response;
} }
public void Disconnect(IMessage message) protected IResponse Send(Message purport)
{ {
if (ChannelIsReady()) FailIfNotConnected(nameof(Send));
{
channel.Disconnect(message);
Logger.Debug($"Disconnected from {address}, transmitting {ToString(message)}.");
}
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) Logger.Debug($"Sent {ToString(message)}, got {ToString(response)}.");
{
if (ChannelIsReady())
{
var response = channel.Send(message);
Logger.Debug($"Sent {ToString(message)}, got {ToString(response)}."); return 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;
} }
private void BaseProxy_Closed(object sender, EventArgs e) private void BaseProxy_Closed(object sender, EventArgs e)
@ -111,6 +111,19 @@ namespace SafeExamBrowser.Core.Communication
Logger.Debug("Communication channel is opening..."); 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() private string GetChannelState()
{ {
return channel == null ? "null" : $"in state '{(channel as ICommunicationObject).State}'"; 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 namespace SafeExamBrowser.Core.Communication.Messages
{ {
[Serializable] [Serializable]
internal class Message : IMessage internal class BaseMessage : IMessage
{ {
public Guid CommunicationToken { get; set; } 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Messages;
namespace SafeExamBrowser.Core.Communication 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() public IClientConfiguration GetConfiguration()
{ {
// TODO return ((IConfigurationResponse) Send(Message.ConfigurationNeeded)).Configuration;
throw new NotImplementedException(); }
public void InformClientReady()
{
Send(Message.ClientIsReady);
} }
} }
} }

View file

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

View file

@ -61,7 +61,12 @@
<Compile Include="Behaviour\Operations\OperationSequence.cs" /> <Compile Include="Behaviour\Operations\OperationSequence.cs" />
<Compile Include="Communication\BaseProxy.cs" /> <Compile Include="Communication\BaseProxy.cs" />
<Compile Include="Communication\BaseHost.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\RuntimeProxy.cs" />
<Compile Include="Communication\ServiceProxy.cs" /> <Compile Include="Communication\ServiceProxy.cs" />
<Compile Include="Logging\DefaultLogFormatter.cs" /> <Compile Include="Logging\DefaultLogFormatter.cs" />
@ -87,8 +92,6 @@
<Name>SafeExamBrowser.Contracts</Name> <Name>SafeExamBrowser.Contracts</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup />
<Folder Include="Communication\Responses\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View file

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

View file

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

View file

@ -54,7 +54,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
if (mandatory && !connected) if (mandatory && !connected)
{ {
Abort = true; 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 else
{ {
@ -81,7 +81,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
} }
catch (Exception e) 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.Communication;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
internal class SessionSequenceEndOperation : SessionSequenceOperation 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/. * 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.Behaviour.Operations;
using SafeExamBrowser.Contracts.Communication; using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
internal abstract class SessionSequenceOperation : IOperation internal abstract class SessionSequenceOperation : IOperation
{ {
private bool sessionRunning; private bool sessionRunning;
private IClientProxy client;
private IConfigurationRepository configuration; private IConfigurationRepository configuration;
private ILogger logger; private ILogger logger;
private IServiceProxy serviceProxy; private IProcessFactory processFactory;
private ISessionData sessionData; private IRuntimeHost runtimeHost;
private IServiceProxy service;
private ISession session;
public bool Abort { get; private set; } public bool Abort { get; private set; }
public IProgressIndicator ProgressIndicator { private get; 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.configuration = configuration;
this.logger = logger; this.logger = logger;
this.serviceProxy = serviceProxy; this.processFactory = processFactory;
this.runtimeHost = runtimeHost;
this.service = service;
} }
public abstract void Perform(); public abstract void Perform();
@ -42,34 +57,80 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Info("Starting new session..."); logger.Info("Starting new session...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StartSession, true); ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StartSession, true);
sessionData = configuration.InitializeSessionData(); session = configuration.InitializeSession();
serviceProxy.StartSession(sessionData.Id, configuration.CurrentSettings); runtimeHost.StartupToken = session.StartupToken;
// TODO: service.StartSession(session.Id, configuration.CurrentSettings);
// - Create and connect to client
// - Verify session integrity and start event handling -> in runtime controller?
System.Threading.Thread.Sleep(5000);
sessionRunning = true; try
logger.Info($"Successfully started new session with identifier '{sessionData.Id}'."); {
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() protected void StopSession()
{ {
if (sessionRunning) if (sessionRunning)
{ {
logger.Info($"Stopping session with identifier '{sessionData.Id}'..."); logger.Info($"Stopping session with identifier '{session.Id}'...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopSession, true); ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopSession, true);
serviceProxy.StopSession(sessionData.Id); service.StopSession(session.Id);
// TODO: // TODO:
// - Terminate client (or does it terminate itself?) // - Terminate client (or does it terminate itself?)
// - Stop event handling and verify session termination -> in runtime controller?
System.Threading.Thread.Sleep(5000);
sessionRunning = false; 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.Communication;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Behaviour.Operations namespace SafeExamBrowser.Runtime.Behaviour.Operations
{ {
internal class SessionSequenceStartOperation : SessionSequenceOperation 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 bool sessionRunning;
private IClientProxy client;
private IConfigurationRepository configuration; private IConfigurationRepository configuration;
private ILogger logger; private ILogger logger;
private IOperationSequence bootstrapSequence; private IOperationSequence bootstrapSequence;
@ -29,29 +30,31 @@ namespace SafeExamBrowser.Runtime.Behaviour
private IRuntimeHost runtimeHost; private IRuntimeHost runtimeHost;
private IRuntimeInfo runtimeInfo; private IRuntimeInfo runtimeInfo;
private IRuntimeWindow runtimeWindow; private IRuntimeWindow runtimeWindow;
private IServiceProxy serviceProxy; private IServiceProxy service;
private ISplashScreen splashScreen; private ISplashScreen splashScreen;
private Action shutdown; private Action shutdown;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
public RuntimeController( public RuntimeController(
IClientProxy client,
IConfigurationRepository configuration, IConfigurationRepository configuration,
ILogger logger, ILogger logger,
IOperationSequence bootstrapSequence, IOperationSequence bootstrapSequence,
IOperationSequence sessionSequence, IOperationSequence sessionSequence,
IRuntimeHost runtimeHost, IRuntimeHost runtimeHost,
IRuntimeInfo runtimeInfo, IRuntimeInfo runtimeInfo,
IServiceProxy serviceProxy, IServiceProxy service,
Action shutdown, Action shutdown,
IUserInterfaceFactory uiFactory) IUserInterfaceFactory uiFactory)
{ {
this.client = client;
this.configuration = configuration; this.configuration = configuration;
this.logger = logger; this.logger = logger;
this.bootstrapSequence = bootstrapSequence; this.bootstrapSequence = bootstrapSequence;
this.sessionSequence = sessionSequence; this.sessionSequence = sessionSequence;
this.runtimeHost = runtimeHost; this.runtimeHost = runtimeHost;
this.runtimeInfo = runtimeInfo; this.runtimeInfo = runtimeInfo;
this.serviceProxy = serviceProxy; this.service = service;
this.shutdown = shutdown; this.shutdown = shutdown;
this.uiFactory = uiFactory; this.uiFactory = uiFactory;
} }
@ -103,10 +106,6 @@ namespace SafeExamBrowser.Runtime.Behaviour
logger.Log(string.Empty); logger.Log(string.Empty);
logger.Info("--- Initiating shutdown procedure ---"); 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(); var success = bootstrapSequence.TryRevert();
if (success) if (success)
@ -142,7 +141,6 @@ namespace SafeExamBrowser.Runtime.Behaviour
else else
{ {
uiFactory.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error); uiFactory.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error);
logger.Info($"Failed to start new session. Terminating application...");
if (!initial) 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;
using SafeExamBrowser.Contracts.Communication.Messages; using SafeExamBrowser.Contracts.Communication.Messages;
using SafeExamBrowser.Contracts.Communication.Responses; using SafeExamBrowser.Contracts.Communication.Responses;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication; using SafeExamBrowser.Core.Communication;
using SafeExamBrowser.Runtime.Communication.Responses;
namespace SafeExamBrowser.Runtime.Communication namespace SafeExamBrowser.Runtime.Communication
{ {
internal class RuntimeHost : BaseHost, IRuntimeHost 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 return StartupToken == token;
throw new NotImplementedException();
} }
protected override void OnDisconnect(IMessage message) protected override void OnDisconnect()
{ {
// TODO // TODO
throw new NotImplementedException();
} }
protected override IResponse OnReceive(IMessage message) protected override IResponse OnReceive(IMessage message)
{ {
// TODO // 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 args = Environment.GetCommandLineArgs();
var configuration = new ConfigurationRepository(); var configuration = new ConfigurationRepository();
var nativeMethods = new NativeMethods(); var nativeMethods = new NativeMethods();
Action shutdown = Application.Current.Shutdown;
logger = new Logger(); logger = new Logger();
runtimeInfo = configuration.RuntimeInfo; runtimeInfo = configuration.RuntimeInfo;
@ -48,7 +49,10 @@ namespace SafeExamBrowser.Runtime
var text = new Text(logger); var text = new Text(logger);
var uiFactory = new UserInterfaceFactory(text); 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 serviceProxy = new ServiceProxy(runtimeInfo.ServiceAddress, new ModuleLogger(logger, typeof(ServiceProxy)));
var bootstrapOperations = new Queue<IOperation>(); var bootstrapOperations = new Queue<IOperation>();
@ -57,16 +61,16 @@ namespace SafeExamBrowser.Runtime
bootstrapOperations.Enqueue(new I18nOperation(logger, text)); bootstrapOperations.Enqueue(new I18nOperation(logger, text));
bootstrapOperations.Enqueue(new CommunicationOperation(runtimeHost, logger)); 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 ConfigurationOperation(configuration, logger, runtimeInfo, text, uiFactory, args));
sessionOperations.Enqueue(new ServiceConnectionOperation(configuration, logger, serviceProxy, text)); sessionOperations.Enqueue(new ServiceConnectionOperation(configuration, logger, serviceProxy, text));
sessionOperations.Enqueue(new KioskModeOperation(logger, configuration)); 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 boostrapSequence = new OperationSequence(logger, bootstrapOperations);
var sessionSequence = new OperationSequence(logger, sessionOperations); 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() internal void LogStartupInformation()

View file

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

View file

@ -32,6 +32,13 @@ namespace SafeExamBrowser.WindowsApi.Constants
/// </summary> /// </summary>
internal const int MIN_ALL = 419; 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> /// <summary>
/// The callback function is not mapped into the address space of the process that generates the event. Because the hook function /// 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 /// 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> /// </summary>
internal class Kernel32 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)] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr GetModuleHandle(string lpModuleName); internal static extern IntPtr GetModuleHandle(string lpModuleName);

View file

@ -203,7 +203,7 @@ namespace SafeExamBrowser.WindowsApi
{ {
var handle = GetShellWindowHandle(); 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() public void PostCloseMessageToShell()
@ -245,10 +245,10 @@ namespace SafeExamBrowser.WindowsApi
public IntPtr RegisterSystemForegroundEvent(Action<IntPtr> callback) 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); callback(hwnd);
}; }
var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_FOREGROUND, Constant.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, eventProc, 0, 0, Constant.WINEVENT_OUTOFCONTEXT); 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) 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); callback(hwnd);
}; }
var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_CAPTURESTART, Constant.EVENT_SYSTEM_CAPTURESTART, IntPtr.Zero, eventProc, 0, 0, Constant.WINEVENT_OUTOFCONTEXT); 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> <ItemGroup>
<Compile Include="Constants\Constant.cs" /> <Compile Include="Constants\Constant.cs" />
<Compile Include="Constants\HookType.cs" /> <Compile Include="Constants\HookType.cs" />
<Compile Include="Desktop.cs" />
<Compile Include="Monitoring\MouseHook.cs" /> <Compile Include="Monitoring\MouseHook.cs" />
<Compile Include="Process.cs" />
<Compile Include="ProcessFactory.cs" />
<Compile Include="Types\Bounds.cs" /> <Compile Include="Types\Bounds.cs" />
<Compile Include="Types\EXECUTION_STATE.cs" /> <Compile Include="Types\EXECUTION_STATE.cs" />
<Compile Include="Types\KBDLLHOOKSTRUCT.cs" /> <Compile Include="Types\KBDLLHOOKSTRUCT.cs" />
@ -69,7 +72,10 @@
<Compile Include="Constants\SPIF.cs" /> <Compile Include="Constants\SPIF.cs" />
<Compile Include="Types\MSLLHOOKSTRUCT.cs" /> <Compile Include="Types\MSLLHOOKSTRUCT.cs" />
<Compile Include="Types\POINT.cs" /> <Compile Include="Types\POINT.cs" />
<Compile Include="Types\PROCESS_INFORMATION.cs" />
<Compile Include="Types\RECT.cs" /> <Compile Include="Types\RECT.cs" />
<Compile Include="Types\SECURITY_ATTRIBUTES.cs" />
<Compile Include="Types\STARTUPINFO.cs" />
<Compile Include="User32.cs" /> <Compile Include="User32.cs" />
</ItemGroup> </ItemGroup>
<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;
}
}