diff --git a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs index 94f5fe32..f07feb48 100644 --- a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs +++ b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs @@ -25,7 +25,7 @@ namespace SafeExamBrowser.Client.UnitTests private Mock taskbarMock; private Mock windowMonitorMock; - private IClientController sut; + //private IClientController sut; [TestInitialize] public void Initialize() diff --git a/SafeExamBrowser.Client/Behaviour/ClientController.cs b/SafeExamBrowser.Client/Behaviour/ClientController.cs index 73f6d78d..8453c6d8 100644 --- a/SafeExamBrowser.Client/Behaviour/ClientController.cs +++ b/SafeExamBrowser.Client/Behaviour/ClientController.cs @@ -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() diff --git a/SafeExamBrowser.Client/Behaviour/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Client/Behaviour/Operations/ConfigurationOperation.cs index 886df7c0..5d56cde1 100644 --- a/SafeExamBrowser.Client/Behaviour/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Client/Behaviour/Operations/ConfigurationOperation.cs @@ -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."); diff --git a/SafeExamBrowser.Client/Behaviour/Operations/RuntimeConnectionOperation.cs b/SafeExamBrowser.Client/Behaviour/Operations/RuntimeConnectionOperation.cs index 37904411..8775d1f7 100644 --- a/SafeExamBrowser.Client/Behaviour/Operations/RuntimeConnectionOperation.cs +++ b/SafeExamBrowser.Client/Behaviour/Operations/RuntimeConnectionOperation.cs @@ -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..."); } } diff --git a/SafeExamBrowser.Client/Communication/ClientHost.cs b/SafeExamBrowser.Client/Communication/ClientHost.cs index 0db76b9d..f4c3edf3 100644 --- a/SafeExamBrowser.Client/Communication/ClientHost.cs +++ b/SafeExamBrowser.Client/Communication/ClientHost.cs @@ -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; } } } diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index 5cb033f0..d3b2502c 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -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) diff --git a/SafeExamBrowser.Configuration/ClientConfiguration.cs b/SafeExamBrowser.Configuration/ClientConfiguration.cs index 95731ab6..672b045b 100644 --- a/SafeExamBrowser.Configuration/ClientConfiguration.cs +++ b/SafeExamBrowser.Configuration/ClientConfiguration.cs @@ -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; } } diff --git a/SafeExamBrowser.Configuration/ConfigurationRepository.cs b/SafeExamBrowser.Configuration/ConfigurationRepository.cs index 15d2e8da..c8a348c7 100644 --- a/SafeExamBrowser.Configuration/ConfigurationRepository.cs +++ b/SafeExamBrowser.Configuration/ConfigurationRepository.cs @@ -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().Copyright, diff --git a/SafeExamBrowser.Configuration/RuntimeInfo.cs b/SafeExamBrowser.Configuration/RuntimeInfo.cs index c0cfbc89..b0cd2403 100644 --- a/SafeExamBrowser.Configuration/RuntimeInfo.cs +++ b/SafeExamBrowser.Configuration/RuntimeInfo.cs @@ -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; } diff --git a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj index bb05983f..58eb6f82 100644 --- a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj +++ b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj @@ -55,7 +55,7 @@ - + diff --git a/SafeExamBrowser.Configuration/SessionData.cs b/SafeExamBrowser.Configuration/Session.cs similarity index 72% rename from SafeExamBrowser.Configuration/SessionData.cs rename to SafeExamBrowser.Configuration/Session.cs index 508cc3fc..9291c398 100644 --- a/SafeExamBrowser.Configuration/SessionData.cs +++ b/SafeExamBrowser.Configuration/Session.cs @@ -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; } } } diff --git a/SafeExamBrowser.Contracts/Behaviour/IApplicationController.cs b/SafeExamBrowser.Contracts/Behaviour/IApplicationController.cs index e96314e3..32240cf1 100644 --- a/SafeExamBrowser.Contracts/Behaviour/IApplicationController.cs +++ b/SafeExamBrowser.Contracts/Behaviour/IApplicationController.cs @@ -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 diff --git a/SafeExamBrowser.Contracts/Behaviour/IRuntimeController.cs b/SafeExamBrowser.Contracts/Behaviour/IRuntimeController.cs index aaaf1a13..68a571cb 100644 --- a/SafeExamBrowser.Contracts/Behaviour/IRuntimeController.cs +++ b/SafeExamBrowser.Contracts/Behaviour/IRuntimeController.cs @@ -6,7 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - namespace SafeExamBrowser.Contracts.Behaviour { // TODO: Interface really needed?! diff --git a/SafeExamBrowser.Contracts/Communication/IClientHost.cs b/SafeExamBrowser.Contracts/Communication/IClientHost.cs index 8ff6a50c..e83846a5 100644 --- a/SafeExamBrowser.Contracts/Communication/IClientHost.cs +++ b/SafeExamBrowser.Contracts/Communication/IClientHost.cs @@ -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? + /// + /// The startup token used for initial authentication. + /// + Guid StartupToken { set; } } } diff --git a/SafeExamBrowser.Contracts/Communication/IClientProxy.cs b/SafeExamBrowser.Contracts/Communication/IClientProxy.cs index 65d8bee5..7a624000 100644 --- a/SafeExamBrowser.Contracts/Communication/IClientProxy.cs +++ b/SafeExamBrowser.Contracts/Communication/IClientProxy.cs @@ -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 { /// - /// Tries to connect to the client host. + /// Instructs the client to submit its authentication data. /// - bool Connect(Guid token); - - /// - /// Disconnects from the client host. - /// - void Disconnect(); + IAuthenticationResponse RequestAuthentication(); } } diff --git a/SafeExamBrowser.Contracts/Communication/ICommunication.cs b/SafeExamBrowser.Contracts/Communication/ICommunication.cs index 5009e8f0..956e9393 100644 --- a/SafeExamBrowser.Contracts/Communication/ICommunication.cs +++ b/SafeExamBrowser.Contracts/Communication/ICommunication.cs @@ -17,21 +17,20 @@ namespace SafeExamBrowser.Contracts.Communication public interface ICommunication { /// - /// 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. /// [OperationContract(IsInitiating = true)] - IConnectResponse Connect(Guid? token = null); + IConnectionResponse Connect(Guid? token = null); /// - /// 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. /// [OperationContract(IsInitiating = false, IsTerminating = true)] - void Disconnect(IMessage message); + IDisconnectionResponse Disconnect(IDisconnectionMessage message); /// - /// Sends a message to the host, optionally returning a response. If no response is expected, null will be returned. + /// Sends a message, optionally returning a response. If no response is expected, null will be returned. /// [OperationContract(IsInitiating = false)] IResponse Send(IMessage message); diff --git a/SafeExamBrowser.Contracts/Communication/ICommunicationHost.cs b/SafeExamBrowser.Contracts/Communication/ICommunicationHost.cs index 7004f4a1..2207d2e4 100644 --- a/SafeExamBrowser.Contracts/Communication/ICommunicationHost.cs +++ b/SafeExamBrowser.Contracts/Communication/ICommunicationHost.cs @@ -8,6 +8,8 @@ namespace SafeExamBrowser.Contracts.Communication { + public delegate void CommunicationEventHandler(); + public interface ICommunicationHost { /// diff --git a/SafeExamBrowser.Contracts/Communication/ICommunicationProxy.cs b/SafeExamBrowser.Contracts/Communication/ICommunicationProxy.cs new file mode 100644 index 00000000..5e7ea1bc --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/ICommunicationProxy.cs @@ -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 + { + /// + /// Tries to establish a connection. Returns true if successful, otherwise false. + /// + bool Connect(Guid? token = null); + + /// + /// Terminates an open connection. Returns true if successful, otherwise false. + /// + bool Disconnect(); + } +} diff --git a/SafeExamBrowser.Contracts/Communication/IRuntimeHost.cs b/SafeExamBrowser.Contracts/Communication/IRuntimeHost.cs index 0c3e0e00..7e3be964 100644 --- a/SafeExamBrowser.Contracts/Communication/IRuntimeHost.cs +++ b/SafeExamBrowser.Contracts/Communication/IRuntimeHost.cs @@ -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? + /// + /// The startup token used for initial authentication. + /// + Guid StartupToken { set; } + + /// + /// Event fired once the client has signaled that it is ready to operate. + /// + event CommunicationEventHandler ClientReady; } } diff --git a/SafeExamBrowser.Contracts/Communication/IRuntimeProxy.cs b/SafeExamBrowser.Contracts/Communication/IRuntimeProxy.cs index 510eb0c5..27dd9c7d 100644 --- a/SafeExamBrowser.Contracts/Communication/IRuntimeProxy.cs +++ b/SafeExamBrowser.Contracts/Communication/IRuntimeProxy.cs @@ -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 { /// - /// Tries to establish a connection with the runtime host, utilizing the specified authentication token. - /// - bool Connect(Guid token); - - /// - /// Disconnects from the runtime host. - /// - void Disconnect(); - - /// - /// Retrieves the application configuration from the runtime host. + /// Retrieves the application configuration from the runtime. /// IClientConfiguration GetConfiguration(); + + /// + /// Informs the runtime that the client is ready. + /// + void InformClientReady(); } } diff --git a/SafeExamBrowser.Contracts/Communication/IServiceProxy.cs b/SafeExamBrowser.Contracts/Communication/IServiceProxy.cs index ac49944a..4c51d731 100644 --- a/SafeExamBrowser.Contracts/Communication/IServiceProxy.cs +++ b/SafeExamBrowser.Contracts/Communication/IServiceProxy.cs @@ -11,7 +11,7 @@ using SafeExamBrowser.Contracts.Configuration.Settings; namespace SafeExamBrowser.Contracts.Communication { - public interface IServiceProxy + public interface IServiceProxy : ICommunicationProxy { /// /// 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 /// bool Ignore { set; } - /// - /// Tries to connect to the service host. - /// - bool Connect(); - - /// - /// Disconnects from the service host. - /// - void Disconnect(); - /// /// Instructs the service to start a new session according to the given parameters. /// diff --git a/SafeExamBrowser.Contracts/Communication/Messages/IDisconnectionMessage.cs b/SafeExamBrowser.Contracts/Communication/Messages/IDisconnectionMessage.cs new file mode 100644 index 00000000..6fdc559d --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Messages/IDisconnectionMessage.cs @@ -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 + { + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Messages/IMessage.cs b/SafeExamBrowser.Contracts/Communication/Messages/IMessage.cs index 8dd53011..2ce257b8 100644 --- a/SafeExamBrowser.Contracts/Communication/Messages/IMessage.cs +++ b/SafeExamBrowser.Contracts/Communication/Messages/IMessage.cs @@ -13,8 +13,8 @@ namespace SafeExamBrowser.Contracts.Communication.Messages public interface IMessage { /// - /// The communication token needed for authentication with the host. + /// The communication token needed for authentication. /// - Guid CommunicationToken { get; } + Guid CommunicationToken { get; set; } } } diff --git a/SafeExamBrowser.Contracts/Communication/Messages/ISimpleMessage.cs b/SafeExamBrowser.Contracts/Communication/Messages/ISimpleMessage.cs new file mode 100644 index 00000000..9ff65345 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Messages/ISimpleMessage.cs @@ -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 + { + /// + /// The purport of the message. + /// + Message Purport { get; } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Messages/Message.cs b/SafeExamBrowser.Contracts/Communication/Messages/Message.cs new file mode 100644 index 00000000..3223c403 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Messages/Message.cs @@ -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 + { + /// + /// Requests an interlocutor to submit data for authentication. + /// + Authenticate = 1, + + /// + /// Sent from the client to the runtime to indicate that the client is ready to operate. + /// + ClientIsReady, + + /// + /// Sent from the client to the runtime to ask for the client configuration. + /// + ConfigurationNeeded, + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Responses/IAuthenticationResponse.cs b/SafeExamBrowser.Contracts/Communication/Responses/IAuthenticationResponse.cs new file mode 100644 index 00000000..99644d75 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Responses/IAuthenticationResponse.cs @@ -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 + { + /// + /// The process identifier used for authentication. + /// + int ProcessId { get; } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Responses/IConfigurationResponse.cs b/SafeExamBrowser.Contracts/Communication/Responses/IConfigurationResponse.cs new file mode 100644 index 00000000..41bd32aa --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Responses/IConfigurationResponse.cs @@ -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 + { + /// + /// The configuration to be used by the client. + /// + IClientConfiguration Configuration { get; } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Responses/IConnectResponse.cs b/SafeExamBrowser.Contracts/Communication/Responses/IConnectionResponse.cs similarity index 69% rename from SafeExamBrowser.Contracts/Communication/Responses/IConnectResponse.cs rename to SafeExamBrowser.Contracts/Communication/Responses/IConnectionResponse.cs index 664973a7..b60284c5 100644 --- a/SafeExamBrowser.Contracts/Communication/Responses/IConnectResponse.cs +++ b/SafeExamBrowser.Contracts/Communication/Responses/IConnectionResponse.cs @@ -10,15 +10,15 @@ using System; namespace SafeExamBrowser.Contracts.Communication.Responses { - public interface IConnectResponse : IResponse + public interface IConnectionResponse : IResponse { /// - /// The communication token needed for authentication with the host. Is null if a connection was refused. + /// The communication token needed for authentication. Is null if a connection was refused. /// Guid? CommunicationToken { get; } /// - /// Indicates whether the host has accepted the connection request. + /// Indicates whether the connection request has been accepted. /// bool ConnectionEstablished { get; } } diff --git a/SafeExamBrowser.Contracts/Communication/Responses/IDisconnectionResponse.cs b/SafeExamBrowser.Contracts/Communication/Responses/IDisconnectionResponse.cs new file mode 100644 index 00000000..b8bd5a28 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Responses/IDisconnectionResponse.cs @@ -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 + { + /// + /// Indicates whether the connection has been terminated. + /// + bool ConnectionTerminated { get; } + } +} diff --git a/SafeExamBrowser.Contracts/Configuration/IClientConfiguration.cs b/SafeExamBrowser.Contracts/Configuration/IClientConfiguration.cs index 1036b7a2..07c0f176 100644 --- a/SafeExamBrowser.Contracts/Configuration/IClientConfiguration.cs +++ b/SafeExamBrowser.Contracts/Configuration/IClientConfiguration.cs @@ -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 { /// - /// The session data to be used by the client. + /// The unique identifier for the current session. /// - ISessionData SessionData { get; set; } + Guid SessionId { get; set; } /// /// The application settings to be used by the client. diff --git a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs index e0ee6615..60bc8b01 100644 --- a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs +++ b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs @@ -14,10 +14,10 @@ namespace SafeExamBrowser.Contracts.Configuration public interface IConfigurationRepository { /// - /// 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 null! /// - ISessionData CurrentSessionData { get; } + ISession CurrentSession { get; } /// /// 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; } /// - /// 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. /// IClientConfiguration BuildClientConfiguration(); /// /// Initializes all relevant data for a new session. /// - ISessionData InitializeSessionData(); + ISession InitializeSession(); /// /// Attempts to load settings from the specified path. diff --git a/SafeExamBrowser.Contracts/Configuration/IRuntimeInfo.cs b/SafeExamBrowser.Contracts/Configuration/IRuntimeInfo.cs index da2f96dd..9f94793d 100644 --- a/SafeExamBrowser.Contracts/Configuration/IRuntimeInfo.cs +++ b/SafeExamBrowser.Contracts/Configuration/IRuntimeInfo.cs @@ -40,10 +40,15 @@ namespace SafeExamBrowser.Contracts.Configuration /// string ClientAddress { get; } + /// + /// The executable path of the client compontent. + /// + string ClientExecutablePath { get; } + /// /// 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! /// /// Guid ClientId { get; } @@ -85,6 +90,9 @@ namespace SafeExamBrowser.Contracts.Configuration /// /// The unique identifier for the currently running runtime instance. + /// + /// TODO: Remove if unused! + /// /// Guid RuntimeId { get; } diff --git a/SafeExamBrowser.Contracts/Configuration/ISession.cs b/SafeExamBrowser.Contracts/Configuration/ISession.cs new file mode 100644 index 00000000..82460791 --- /dev/null +++ b/SafeExamBrowser.Contracts/Configuration/ISession.cs @@ -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 + { + /// + /// The process information of the client instance associated to this session. + /// + IProcess ClientProcess { get; set; } + + /// + /// The unique session identifier. + /// + Guid Id { get; } + + /// + /// The startup token used by the client and runtime components for initial authentication. + /// + Guid StartupToken { get; } + } +} diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index f905bc5f..3d0a3240 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -60,15 +60,22 @@ + + + + + + + - + - + @@ -131,10 +138,11 @@ + + + - - - + \ No newline at end of file diff --git a/SafeExamBrowser.Contracts/WindowsApi/IDesktop.cs b/SafeExamBrowser.Contracts/WindowsApi/IDesktop.cs new file mode 100644 index 00000000..dce2202e --- /dev/null +++ b/SafeExamBrowser.Contracts/WindowsApi/IDesktop.cs @@ -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 + { + /// + /// Retrieves the name of the currently active desktop. + /// + string CurrentName { get; } + } +} diff --git a/SafeExamBrowser.Contracts/Configuration/ISessionData.cs b/SafeExamBrowser.Contracts/WindowsApi/IProcess.cs similarity index 69% rename from SafeExamBrowser.Contracts/Configuration/ISessionData.cs rename to SafeExamBrowser.Contracts/WindowsApi/IProcess.cs index 7a6335a7..11abb31d 100644 --- a/SafeExamBrowser.Contracts/Configuration/ISessionData.cs +++ b/SafeExamBrowser.Contracts/WindowsApi/IProcess.cs @@ -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 { /// - /// The unique session identifier. + /// The process identifier. /// - Guid Id { get; } + int Id { get; } } } diff --git a/SafeExamBrowser.Contracts/WindowsApi/IProcessFactory.cs b/SafeExamBrowser.Contracts/WindowsApi/IProcessFactory.cs new file mode 100644 index 00000000..6cb1e1d9 --- /dev/null +++ b/SafeExamBrowser.Contracts/WindowsApi/IProcessFactory.cs @@ -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 + { + /// + /// Starts a new process on the currently active desktop. + /// + /// If the process could not be started. + IProcess StartNew(string path, params string[] args); + } +} diff --git a/SafeExamBrowser.Core/Communication/BaseHost.cs b/SafeExamBrowser.Core/Communication/BaseHost.cs index 0f34ba8b..777ae9f4 100644 --- a/SafeExamBrowser.Core/Communication/BaseHost.cs +++ b/SafeExamBrowser.Core/Communication/BaseHost.cs @@ -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."); diff --git a/SafeExamBrowser.Core/Communication/BaseProxy.cs b/SafeExamBrowser.Core/Communication/BaseProxy.cs index 1b10b338..e26e56bb 100644 --- a/SafeExamBrowser.Core/Communication/BaseProxy.cs +++ b/SafeExamBrowser.Core/Communication/BaseProxy.cs @@ -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}'"; diff --git a/SafeExamBrowser.Core/Communication/ClientProxy.cs b/SafeExamBrowser.Core/Communication/ClientProxy.cs new file mode 100644 index 00000000..8eca3588 --- /dev/null +++ b/SafeExamBrowser.Core/Communication/ClientProxy.cs @@ -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); + } + } +} diff --git a/SafeExamBrowser.Core/Communication/Messages/Message.cs b/SafeExamBrowser.Core/Communication/Messages/BaseMessage.cs similarity index 92% rename from SafeExamBrowser.Core/Communication/Messages/Message.cs rename to SafeExamBrowser.Core/Communication/Messages/BaseMessage.cs index 27c60c70..0c554bf2 100644 --- a/SafeExamBrowser.Core/Communication/Messages/Message.cs +++ b/SafeExamBrowser.Core/Communication/Messages/BaseMessage.cs @@ -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; } } diff --git a/SafeExamBrowser.Core/Communication/Messages/DisconnectionMessage.cs b/SafeExamBrowser.Core/Communication/Messages/DisconnectionMessage.cs new file mode 100644 index 00000000..65f6a533 --- /dev/null +++ b/SafeExamBrowser.Core/Communication/Messages/DisconnectionMessage.cs @@ -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 + { + } +} diff --git a/SafeExamBrowser.Core/Communication/Messages/SimpleMessage.cs b/SafeExamBrowser.Core/Communication/Messages/SimpleMessage.cs new file mode 100644 index 00000000..c19849be --- /dev/null +++ b/SafeExamBrowser.Core/Communication/Messages/SimpleMessage.cs @@ -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; } + } +} diff --git a/SafeExamBrowser.Core/Communication/Responses/ConnectionResponse.cs b/SafeExamBrowser.Core/Communication/Responses/ConnectionResponse.cs new file mode 100644 index 00000000..b66f437b --- /dev/null +++ b/SafeExamBrowser.Core/Communication/Responses/ConnectionResponse.cs @@ -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; } + } +} diff --git a/SafeExamBrowser.Core/Communication/Responses/DisconnectionResponse.cs b/SafeExamBrowser.Core/Communication/Responses/DisconnectionResponse.cs new file mode 100644 index 00000000..2465f339 --- /dev/null +++ b/SafeExamBrowser.Core/Communication/Responses/DisconnectionResponse.cs @@ -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; } + } +} diff --git a/SafeExamBrowser.Core/Communication/RuntimeProxy.cs b/SafeExamBrowser.Core/Communication/RuntimeProxy.cs index e96fb83d..8ef224e2 100644 --- a/SafeExamBrowser.Core/Communication/RuntimeProxy.cs +++ b/SafeExamBrowser.Core/Communication/RuntimeProxy.cs @@ -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); } } } diff --git a/SafeExamBrowser.Core/Communication/ServiceProxy.cs b/SafeExamBrowser.Core/Communication/ServiceProxy.cs index 0a79b14d..b122ae88 100644 --- a/SafeExamBrowser.Core/Communication/ServiceProxy.cs +++ b/SafeExamBrowser.Core/Communication/ServiceProxy.cs @@ -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 } diff --git a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj index 1a1944ed..715c98e0 100644 --- a/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj +++ b/SafeExamBrowser.Core/SafeExamBrowser.Core.csproj @@ -61,7 +61,12 @@ - + + + + + + @@ -87,8 +92,6 @@ SafeExamBrowser.Contracts - - - + \ No newline at end of file diff --git a/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ServiceConnectionOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ServiceConnectionOperationTests.cs index fb4c1644..ea143ad6 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ServiceConnectionOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ServiceConnectionOperationTests.cs @@ -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(); + service.Setup(s => s.Connect(null)).Throws(); configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Mandatory); sut.Perform(); - service.Setup(s => s.Connect()).Throws(); + service.Setup(s => s.Connect(null)).Throws(); 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(); 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(); + service.Setup(s => s.Connect(null)).Throws(); configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Mandatory); sut.Perform(); sut.Revert(); - service.Setup(s => s.Connect()).Throws(); + service.Setup(s => s.Connect(null)).Throws(); configuration.SetupGet(s => s.CurrentSettings.ServicePolicy).Returns(ServicePolicy.Optional); sut.Perform(); diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs index e697ad4a..5b038120 100644 --- a/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/KioskModeOperation.cs @@ -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; } } diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/ServiceConnectionOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/ServiceConnectionOperation.cs index db916801..58e58694 100644 --- a/SafeExamBrowser.Runtime/Behaviour/Operations/ServiceConnectionOperation.cs +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/ServiceConnectionOperation.cs @@ -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); } } } diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceEndOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceEndOperation.cs index 30173ad4..154f6145 100644 --- a/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceEndOperation.cs +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceEndOperation.cs @@ -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) { } diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceOperation.cs index d137f552..2331e96f 100644 --- a/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceOperation.cs +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceOperation.cs @@ -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!"); } } } diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceStartOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceStartOperation.cs index 91f953b8..1baf2def 100644 --- a/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceStartOperation.cs +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/SessionSequenceStartOperation.cs @@ -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) { } diff --git a/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs b/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs index 5f0636e4..e4818601 100644 --- a/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs +++ b/SafeExamBrowser.Runtime/Behaviour/RuntimeController.cs @@ -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) { diff --git a/SafeExamBrowser.Runtime/Communication/Responses/ConfigurationResponse.cs b/SafeExamBrowser.Runtime/Communication/Responses/ConfigurationResponse.cs new file mode 100644 index 00000000..5096c4e6 --- /dev/null +++ b/SafeExamBrowser.Runtime/Communication/Responses/ConfigurationResponse.cs @@ -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; } + } +} diff --git a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs index 06ea256f..e81b65a6 100644 --- a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs +++ b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs @@ -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; } } } diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index e938161b..be19cc49 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -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(); @@ -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() diff --git a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj index 6f9e793d..7bb8fe2b 100644 --- a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj +++ b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj @@ -93,6 +93,7 @@ + @@ -162,4 +163,7 @@ + + xcopy /E /Y "$(SolutionDir)SafeExamBrowser.Client\bin\$(PlatformName)\$(ConfigurationName)" "$(ProjectDir)bin\$(PlatformName)\$(ConfigurationName)" + \ No newline at end of file diff --git a/SafeExamBrowser.WindowsApi/Constants/Constant.cs b/SafeExamBrowser.WindowsApi/Constants/Constant.cs index b1bc13bb..f0dd00d6 100644 --- a/SafeExamBrowser.WindowsApi/Constants/Constant.cs +++ b/SafeExamBrowser.WindowsApi/Constants/Constant.cs @@ -32,6 +32,13 @@ namespace SafeExamBrowser.WindowsApi.Constants /// internal const int MIN_ALL = 419; + /// + /// 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. + /// + internal const int NORMAL_PRIORITY_CLASS = 0x20; + /// /// 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 diff --git a/SafeExamBrowser.WindowsApi/Desktop.cs b/SafeExamBrowser.WindowsApi/Desktop.cs new file mode 100644 index 00000000..e09b40d0 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Desktop.cs @@ -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; + } + } +} diff --git a/SafeExamBrowser.WindowsApi/Kernel32.cs b/SafeExamBrowser.WindowsApi/Kernel32.cs index acb5bd3c..dd5a42e3 100644 --- a/SafeExamBrowser.WindowsApi/Kernel32.cs +++ b/SafeExamBrowser.WindowsApi/Kernel32.cs @@ -17,6 +17,19 @@ namespace SafeExamBrowser.WindowsApi /// 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); diff --git a/SafeExamBrowser.WindowsApi/NativeMethods.cs b/SafeExamBrowser.WindowsApi/NativeMethods.cs index 64e08292..e4b9d441 100644 --- a/SafeExamBrowser.WindowsApi/NativeMethods.cs +++ b/SafeExamBrowser.WindowsApi/NativeMethods.cs @@ -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 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 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); diff --git a/SafeExamBrowser.WindowsApi/Process.cs b/SafeExamBrowser.WindowsApi/Process.cs new file mode 100644 index 00000000..1b6cd321 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Process.cs @@ -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); + } + } +} diff --git a/SafeExamBrowser.WindowsApi/ProcessFactory.cs b/SafeExamBrowser.WindowsApi/ProcessFactory.cs new file mode 100644 index 00000000..a6b18fb8 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/ProcessFactory.cs @@ -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); + } + } +} diff --git a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj index de8d93e6..b974fb73 100644 --- a/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj +++ b/SafeExamBrowser.WindowsApi/SafeExamBrowser.WindowsApi.csproj @@ -54,7 +54,10 @@ + + + @@ -69,7 +72,10 @@ + + + diff --git a/SafeExamBrowser.WindowsApi/Types/PROCESS_INFORMATION.cs b/SafeExamBrowser.WindowsApi/Types/PROCESS_INFORMATION.cs new file mode 100644 index 00000000..0fb63686 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Types/PROCESS_INFORMATION.cs @@ -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 +{ + /// + /// See http://pinvoke.net/default.aspx/Structures/PROCESS_INFORMATION.html. + /// See https://msdn.microsoft.com/en-us/library/ms684873(VS.85).aspx. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } +} diff --git a/SafeExamBrowser.WindowsApi/Types/SECURITY_ATTRIBUTES.cs b/SafeExamBrowser.WindowsApi/Types/SECURITY_ATTRIBUTES.cs new file mode 100644 index 00000000..dad5d43f --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Types/SECURITY_ATTRIBUTES.cs @@ -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 +{ + /// + /// 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. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct SECURITY_ATTRIBUTES + { + public int nLength; + public IntPtr lpSecurityDescriptor; + public int bInheritHandle; + } +} diff --git a/SafeExamBrowser.WindowsApi/Types/STARTUPINFO.cs b/SafeExamBrowser.WindowsApi/Types/STARTUPINFO.cs new file mode 100644 index 00000000..02962536 --- /dev/null +++ b/SafeExamBrowser.WindowsApi/Types/STARTUPINFO.cs @@ -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 +{ + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx. + /// + [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; + } +}