From 95ad176047f32781246703d2855bc5dd552906ef Mon Sep 17 00:00:00 2001 From: dbuechel Date: Fri, 12 Oct 2018 11:16:59 +0200 Subject: [PATCH] SEBWIN-221: Fixed implementation of session sequence to correctly handle initialization, transition and termination of sessions by introducing the SessionContext. --- .../Proxies/ServiceProxy.cs | 2 +- .../ConfigurationRepository.cs | 162 +++++++++--------- .../ResourceLoader.cs | 2 +- .../SafeExamBrowser.Configuration.csproj | 2 +- ...SessionData.cs => SessionConfiguration.cs} | 11 +- .../Events/ClientConfigurationEventArgs.cs | 23 +++ .../Communication/Hosts/IRuntimeHost.cs | 5 + .../Configuration/IConfigurationRepository.cs | 37 +--- .../Configuration/ISessionConfiguration.cs | 38 ++++ .../Configuration/ISessionData.cs | 50 ------ .../SafeExamBrowser.Contracts.csproj | 3 +- .../Communication/RuntimeHost.cs | 23 +-- SafeExamBrowser.Runtime/CompositionRoot.cs | 24 +-- .../Operations/ClientOperation.cs | 41 ++--- .../Operations/ClientTerminationOperation.cs | 7 +- .../Operations/ConfigurationOperation.cs | 45 +++-- .../Operations/KioskModeOperation.cs | 91 +++++----- .../KioskModeTerminationOperation.cs | 21 +-- .../Operations/ServiceOperation.cs | 25 ++- .../Operations/SessionActivationOperation.cs | 63 +++++++ .../SessionInitializationOperation.cs | 32 ++-- .../Operations/SessionOperation.cs | 25 +++ SafeExamBrowser.Runtime/RuntimeController.cs | 146 ++++++++++------ .../SafeExamBrowser.Runtime.csproj | 3 + SafeExamBrowser.Runtime/SessionContext.cs | 45 +++++ 25 files changed, 551 insertions(+), 375 deletions(-) rename SafeExamBrowser.Configuration/{SessionData.cs => SessionConfiguration.cs} (59%) create mode 100644 SafeExamBrowser.Contracts/Communication/Events/ClientConfigurationEventArgs.cs create mode 100644 SafeExamBrowser.Contracts/Configuration/ISessionConfiguration.cs delete mode 100644 SafeExamBrowser.Contracts/Configuration/ISessionData.cs create mode 100644 SafeExamBrowser.Runtime/Operations/SessionActivationOperation.cs create mode 100644 SafeExamBrowser.Runtime/Operations/SessionOperation.cs create mode 100644 SafeExamBrowser.Runtime/SessionContext.cs diff --git a/SafeExamBrowser.Communication/Proxies/ServiceProxy.cs b/SafeExamBrowser.Communication/Proxies/ServiceProxy.cs index 47b3c1d7..95352342 100644 --- a/SafeExamBrowser.Communication/Proxies/ServiceProxy.cs +++ b/SafeExamBrowser.Communication/Proxies/ServiceProxy.cs @@ -74,7 +74,7 @@ namespace SafeExamBrowser.Communication.Proxies { if (Ignore) { - Logger.Debug($"Skipping {operationName} because the ignore flag is set."); + Logger.Debug($"Skipping '{operationName}' operation because the ignore flag is set."); } return Ignore; diff --git a/SafeExamBrowser.Configuration/ConfigurationRepository.cs b/SafeExamBrowser.Configuration/ConfigurationRepository.cs index c6381bcd..b52d4134 100644 --- a/SafeExamBrowser.Configuration/ConfigurationRepository.cs +++ b/SafeExamBrowser.Configuration/ConfigurationRepository.cs @@ -25,86 +25,15 @@ namespace SafeExamBrowser.Configuration private AppConfig appConfig; - public ISessionData CurrentSession { get; private set; } - public Settings CurrentSettings { get; private set; } - public string ReconfigurationFilePath { get; set; } - - public AppConfig AppConfig - { - get - { - if (appConfig == null) - { - InitializeAppConfig(); - } - - return appConfig; - } - } - public ConfigurationRepository(string executablePath, string programCopyright, string programTitle, string programVersion) { - this.executablePath = executablePath ?? throw new ArgumentNullException(nameof(executablePath)); - this.programCopyright = programCopyright ?? throw new ArgumentNullException(nameof(programCopyright)); - this.programTitle = programTitle ?? throw new ArgumentNullException(nameof(programTitle)); - this.programVersion = programVersion ?? throw new ArgumentNullException(nameof(programVersion)); + this.executablePath = executablePath ?? string.Empty; + this.programCopyright = programCopyright ?? string.Empty; + this.programTitle = programTitle ?? string.Empty; + this.programVersion = programVersion ?? string.Empty; } - public ClientConfiguration BuildClientConfiguration() - { - return new ClientConfiguration - { - AppConfig = AppConfig, - SessionId = CurrentSession.Id, - Settings = CurrentSettings - }; - } - - public void InitializeSessionConfiguration() - { - CurrentSession = new SessionData - { - Id = Guid.NewGuid(), - NewDesktop = CurrentSession?.NewDesktop, - OriginalDesktop = CurrentSession?.OriginalDesktop, - StartupToken = Guid.NewGuid() - }; - - UpdateAppConfig(); - } - - public LoadStatus LoadSettings(Uri resource, string settingsPassword = null, string adminPassword = null) - { - // TODO: Implement loading mechanism - - LoadDefaultSettings(); - - return LoadStatus.Success; - } - - public void LoadDefaultSettings() - { - // TODO: Implement default settings - - CurrentSettings = new Settings(); - - CurrentSettings.KioskMode = KioskMode.None; - CurrentSettings.ServicePolicy = ServicePolicy.Optional; - - CurrentSettings.Browser.StartUrl = "https://www.safeexambrowser.org/testing"; - CurrentSettings.Browser.AllowAddressBar = true; - CurrentSettings.Browser.AllowBackwardNavigation = true; - CurrentSettings.Browser.AllowDeveloperConsole = true; - CurrentSettings.Browser.AllowForwardNavigation = true; - CurrentSettings.Browser.AllowReloading = true; - CurrentSettings.Browser.AllowDownloads = true; - - CurrentSettings.Taskbar.AllowApplicationLog = true; - CurrentSettings.Taskbar.AllowKeyboardLayout = true; - CurrentSettings.Taskbar.AllowWirelessNetwork = true; - } - - private void InitializeAppConfig() + public AppConfig InitializeAppConfig() { var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser)); var startTime = DateTime.Now; @@ -134,12 +63,89 @@ namespace SafeExamBrowser.Configuration appConfig.SebUriScheme = "seb"; appConfig.SebUriSchemeSecure = "sebs"; appConfig.ServiceAddress = $"{BASE_ADDRESS}/service"; + + return appConfig; + } + + public ISessionConfiguration InitializeSessionConfiguration() + { + var configuration = new SessionConfiguration(); + + UpdateAppConfig(); + + configuration.AppConfig = CloneAppConfig(); + configuration.Id = Guid.NewGuid(); + configuration.StartupToken = Guid.NewGuid(); + + return configuration; + } + + public LoadStatus TryLoadSettings(Uri resource, out Settings settings, string adminPassword = null, string settingsPassword = null) + { + // TODO: Implement loading mechanism + + settings = LoadDefaultSettings(); + + return LoadStatus.Success; + } + + public Settings LoadDefaultSettings() + { + // TODO: Implement default settings + + var settings = new Settings(); + + settings.KioskMode = new Random().Next(10) < 5 ? KioskMode.CreateNewDesktop : KioskMode.DisableExplorerShell; + settings.ServicePolicy = ServicePolicy.Optional; + + settings.Browser.StartUrl = "https://www.safeexambrowser.org/testing"; + settings.Browser.AllowAddressBar = true; + settings.Browser.AllowBackwardNavigation = true; + settings.Browser.AllowDeveloperConsole = true; + settings.Browser.AllowForwardNavigation = true; + settings.Browser.AllowReloading = true; + settings.Browser.AllowDownloads = true; + + settings.Taskbar.AllowApplicationLog = true; + settings.Taskbar.AllowKeyboardLayout = true; + settings.Taskbar.AllowWirelessNetwork = true; + + return settings; + } + + private AppConfig CloneAppConfig() + { + return new AppConfig + { + AppDataFolder = appConfig.AppDataFolder, + ApplicationStartTime = appConfig.ApplicationStartTime, + BrowserCachePath = appConfig.BrowserCachePath, + BrowserLogFile = appConfig.BrowserLogFile, + ClientAddress = appConfig.ClientAddress, + ClientExecutablePath = appConfig.ClientExecutablePath, + ClientId = appConfig.ClientId, + ClientLogFile = appConfig.ClientLogFile, + ConfigurationFileExtension = appConfig.ConfigurationFileExtension, + DefaultSettingsFileName = appConfig.DefaultSettingsFileName, + DownloadDirectory = appConfig.DownloadDirectory, + LogLevel = appConfig.LogLevel, + ProgramCopyright = appConfig.ProgramCopyright, + ProgramDataFolder = appConfig.ProgramDataFolder, + ProgramTitle = appConfig.ProgramTitle, + ProgramVersion = appConfig.ProgramVersion, + RuntimeAddress = appConfig.RuntimeAddress, + RuntimeId = appConfig.RuntimeId, + RuntimeLogFile = appConfig.RuntimeLogFile, + SebUriScheme = appConfig.SebUriScheme, + SebUriSchemeSecure = appConfig.SebUriSchemeSecure, + ServiceAddress = appConfig.ServiceAddress + }; } private void UpdateAppConfig() { - AppConfig.ClientId = Guid.NewGuid(); - AppConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}"; + appConfig.ClientId = Guid.NewGuid(); + appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}"; } } } diff --git a/SafeExamBrowser.Configuration/ResourceLoader.cs b/SafeExamBrowser.Configuration/ResourceLoader.cs index f6b353d7..6bca4974 100644 --- a/SafeExamBrowser.Configuration/ResourceLoader.cs +++ b/SafeExamBrowser.Configuration/ResourceLoader.cs @@ -15,7 +15,7 @@ namespace SafeExamBrowser.Configuration { public bool IsHtmlResource(Uri resource) { - // TODO + // TODO: Implement resource loader return false; } } diff --git a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj index 53eef41a..0947634c 100644 --- a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj +++ b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj @@ -53,9 +53,9 @@ - + diff --git a/SafeExamBrowser.Configuration/SessionData.cs b/SafeExamBrowser.Configuration/SessionConfiguration.cs similarity index 59% rename from SafeExamBrowser.Configuration/SessionData.cs rename to SafeExamBrowser.Configuration/SessionConfiguration.cs index c256d342..24a93cef 100644 --- a/SafeExamBrowser.Configuration/SessionData.cs +++ b/SafeExamBrowser.Configuration/SessionConfiguration.cs @@ -7,19 +7,16 @@ */ using System; -using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Configuration; -using SafeExamBrowser.Contracts.WindowsApi; +using SafeExamBrowser.Contracts.Configuration.Settings; namespace SafeExamBrowser.Configuration { - public class SessionData : ISessionData + internal class SessionConfiguration : ISessionConfiguration { - public IClientProxy ClientProxy { get; set; } - public IProcess ClientProcess { get; set; } + public AppConfig AppConfig { get; set; } public Guid Id { get; set; } - public IDesktop NewDesktop { get; set; } - public IDesktop OriginalDesktop { get; set; } + public Settings Settings { get; set; } public Guid StartupToken { get; set; } } } diff --git a/SafeExamBrowser.Contracts/Communication/Events/ClientConfigurationEventArgs.cs b/SafeExamBrowser.Contracts/Communication/Events/ClientConfigurationEventArgs.cs new file mode 100644 index 00000000..045ceb59 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Events/ClientConfigurationEventArgs.cs @@ -0,0 +1,23 @@ +/* + * 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.Events +{ + /// + /// The event arguments used for the client configuration event fired by the . + /// + public class ClientConfigurationEventArgs : CommunicationEventArgs + { + /// + /// The configuration to be sent to the client. + /// + public ClientConfiguration ClientConfiguration { get; set; } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs b/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs index e9e8011d..63d99315 100644 --- a/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs +++ b/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs @@ -31,6 +31,11 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts /// event CommunicationEventHandler ClientReady; + /// + /// Event fired when the client requested its configuration data. + /// + event CommunicationEventHandler ClientConfigurationNeeded; + /// /// Event fired when the client submitted a password entered by the user. /// diff --git a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs index 670ac24e..319f7a8b 100644 --- a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs +++ b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs @@ -16,46 +16,25 @@ namespace SafeExamBrowser.Contracts.Configuration public interface IConfigurationRepository { /// - /// The global configuration information for the currently running application instance. + /// Initializes the global configuration information for the currently running application instance. /// - AppConfig AppConfig { get; } + AppConfig InitializeAppConfig(); /// - /// Retrieves the current session data, i.e. the last one which was initialized. If no session has been initialized yet, this - /// property will be null! + /// Initializes all relevant configuration data for a new session. /// - ISessionData CurrentSession { get; } - - /// - /// Retrieves the current settings, i.e. the last ones which were loaded. If no settings have been loaded yet, this property will - /// be null! - /// - Settings.Settings CurrentSettings { get; } - - /// - /// The path of the settings file to be used when reconfiguring the application. - /// - string ReconfigurationFilePath { get; set; } - - /// - /// Builds a configuration for the client component, given the currently loaded settings, session and runtime information. - /// - ClientConfiguration BuildClientConfiguration(); - - /// - /// Initializes all relevant data for a new session. - /// - void InitializeSessionConfiguration(); + ISessionConfiguration InitializeSessionConfiguration(); /// /// Attempts to load settings from the specified resource, using the optional passwords. Returns a - /// indicating the result of the operation. + /// indicating the result of the operation. As long as the result is not , the declared + /// will be null! /// - LoadStatus LoadSettings(Uri resource, string adminPassword = null, string settingsPassword = null); + LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, string adminPassword = null, string settingsPassword = null); /// /// Loads the default settings. /// - void LoadDefaultSettings(); + Settings.Settings LoadDefaultSettings(); } } diff --git a/SafeExamBrowser.Contracts/Configuration/ISessionConfiguration.cs b/SafeExamBrowser.Contracts/Configuration/ISessionConfiguration.cs new file mode 100644 index 00000000..65c570f5 --- /dev/null +++ b/SafeExamBrowser.Contracts/Configuration/ISessionConfiguration.cs @@ -0,0 +1,38 @@ +/* + * 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.Configuration +{ + /// + /// Holds all session-related configuration data. + /// + public interface ISessionConfiguration + { + /// + /// The active application configuration for this session. + /// + AppConfig AppConfig { get; } + + /// + /// The unique session identifier. + /// + Guid Id { get; } + + /// + /// The settings used for this session. + /// + Settings.Settings Settings { get; set; } + + /// + /// The startup token used by the client and runtime components for initial authentication. + /// + Guid StartupToken { get; } + } +} diff --git a/SafeExamBrowser.Contracts/Configuration/ISessionData.cs b/SafeExamBrowser.Contracts/Configuration/ISessionData.cs deleted file mode 100644 index 5b7a27b0..00000000 --- a/SafeExamBrowser.Contracts/Configuration/ISessionData.cs +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.Proxies; -using SafeExamBrowser.Contracts.WindowsApi; - -namespace SafeExamBrowser.Contracts.Configuration -{ - /// - /// Holds all session-related configuration and runtime data. - /// - public interface ISessionData - { - /// - /// The communication proxy for the client instance associated to this session. - /// - IClientProxy ClientProxy { get; set; } - - /// - /// The process information of the client instance associated to this session. - /// - IProcess ClientProcess { get; set; } - - /// - /// The unique session identifier. - /// - Guid Id { get; } - - /// - /// The new desktop, if is active for this session. - /// - IDesktop NewDesktop { get; set; } - - /// - /// The original desktop, if is active for this session. - /// - IDesktop OriginalDesktop { get; set; } - - /// - /// 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 28401664..9224aafd 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -52,6 +52,7 @@ + @@ -109,7 +110,7 @@ - + diff --git a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs index b6a934c2..b937ca6c 100644 --- a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs +++ b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs @@ -11,7 +11,6 @@ using SafeExamBrowser.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; -using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Logging; namespace SafeExamBrowser.Runtime.Communication @@ -19,24 +18,18 @@ namespace SafeExamBrowser.Runtime.Communication internal class RuntimeHost : BaseHost, IRuntimeHost { private bool allowConnection = true; - private IConfigurationRepository configuration; public Guid StartupToken { private get; set; } public event CommunicationEventHandler ClientDisconnected; public event CommunicationEventHandler ClientReady; + public event CommunicationEventHandler ClientConfigurationNeeded; public event CommunicationEventHandler PasswordReceived; public event CommunicationEventHandler ReconfigurationRequested; public event CommunicationEventHandler ShutdownRequested; - public RuntimeHost( - string address, - IConfigurationRepository configuration, - IHostObjectFactory factory, - ILogger logger, - int timeout_ms) : base(address, factory, logger, timeout_ms) + public RuntimeHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms) { - this.configuration = configuration; } protected override bool OnConnect(Guid? token = null) @@ -87,8 +80,7 @@ namespace SafeExamBrowser.Runtime.Communication ClientReady?.Invoke(); return new SimpleResponse(SimpleResponsePurport.Acknowledged); case SimpleMessagePurport.ConfigurationNeeded: - // TODO: Not the job of the host, fire event or alike! - return new ConfigurationResponse { Configuration = configuration.BuildClientConfiguration() }; + return HandleConfigurationRequest(); case SimpleMessagePurport.RequestShutdown: ShutdownRequested?.Invoke(); return new SimpleResponse(SimpleResponsePurport.Acknowledged); @@ -96,5 +88,14 @@ namespace SafeExamBrowser.Runtime.Communication return new SimpleResponse(SimpleResponsePurport.UnknownMessage); } + + private Response HandleConfigurationRequest() + { + var args = new ClientConfigurationEventArgs(); + + ClientConfigurationNeeded?.Invoke(args); + + return new ConfigurationResponse { Configuration = args.ClientConfiguration }; + } } } diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 6588e256..6f52867c 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -49,7 +49,7 @@ namespace SafeExamBrowser.Runtime var nativeMethods = new NativeMethods(); logger = new Logger(); - appConfig = configuration.AppConfig; + appConfig = configuration.InitializeAppConfig(); systemInfo = new SystemInfo(); InitializeLogging(); @@ -61,8 +61,9 @@ namespace SafeExamBrowser.Runtime var processFactory = new ProcessFactory(new ModuleLogger(logger, nameof(ProcessFactory))); var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger); var resourceLoader = new ResourceLoader(); - var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, configuration, new HostObjectFactory(), new ModuleLogger(logger, nameof(RuntimeHost)), FIVE_SECONDS); + var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), new ModuleLogger(logger, nameof(RuntimeHost)), FIVE_SECONDS); var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(ServiceProxy))); + var sessionContext = new SessionContext(); var uiFactory = new UserInterfaceFactory(text); var bootstrapOperations = new Queue(); @@ -71,18 +72,19 @@ namespace SafeExamBrowser.Runtime bootstrapOperations.Enqueue(new I18nOperation(logger, text, textResource)); bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger)); - sessionOperations.Enqueue(new ConfigurationOperation(appConfig, configuration, logger, resourceLoader, args)); - sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, FIFTEEN_SECONDS)); - sessionOperations.Enqueue(new KioskModeTerminationOperation(configuration, desktopFactory, explorerShell, logger, processFactory)); - sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost)); - sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy)); - sessionOperations.Enqueue(new KioskModeOperation(configuration, desktopFactory, explorerShell, logger, processFactory)); - sessionOperations.Enqueue(new ClientOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, FIFTEEN_SECONDS)); + sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost, sessionContext)); + sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, logger, resourceLoader, sessionContext)); + sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, FIFTEEN_SECONDS)); + sessionOperations.Enqueue(new KioskModeTerminationOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext)); + sessionOperations.Enqueue(new ServiceOperation(logger, serviceProxy, sessionContext)); + sessionOperations.Enqueue(new KioskModeOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext)); + sessionOperations.Enqueue(new ClientOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, FIFTEEN_SECONDS)); + sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext)); var bootstrapSequence = new OperationSequence(logger, bootstrapOperations); var sessionSequence = new RepeatableOperationSequence(logger, sessionOperations); - RuntimeController = new RuntimeController(appConfig, configuration, logger, messageBox, bootstrapSequence, sessionSequence, runtimeHost, serviceProxy, shutdown, text, uiFactory); + RuntimeController = new RuntimeController(appConfig, logger, messageBox, bootstrapSequence, sessionSequence, runtimeHost, serviceProxy, sessionContext, shutdown, text, uiFactory); } internal void LogStartupInformation() @@ -103,7 +105,7 @@ namespace SafeExamBrowser.Runtime logger?.Log($"# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}"); } - private ConfigurationRepository BuildConfigurationRepository() + private IConfigurationRepository BuildConfigurationRepository() { var executable = Assembly.GetExecutingAssembly(); var programCopyright = executable.GetCustomAttribute().Copyright; diff --git a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs index 83632123..7def87c3 100644 --- a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs @@ -10,7 +10,6 @@ using System.Threading; using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; -using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.I18n; @@ -20,40 +19,38 @@ using SafeExamBrowser.Contracts.WindowsApi.Events; namespace SafeExamBrowser.Runtime.Operations { - internal class ClientOperation : IRepeatableOperation + internal class ClientOperation : SessionOperation { private readonly int timeout_ms; - private IConfigurationRepository configuration; private ILogger logger; private IProcessFactory processFactory; private IProxyFactory proxyFactory; private IRuntimeHost runtimeHost; - public event ActionRequiredEventHandler ActionRequired { add { } remove { } } - public event StatusChangedEventHandler StatusChanged; - private IProcess ClientProcess { - get { return configuration.CurrentSession.ClientProcess; } - set { configuration.CurrentSession.ClientProcess = value; } + get { return Context.ClientProcess; } + set { Context.ClientProcess = value; } } private IClientProxy ClientProxy { - get { return configuration.CurrentSession.ClientProxy; } - set { configuration.CurrentSession.ClientProxy = value; } + get { return Context.ClientProxy; } + set { Context.ClientProxy = value; } } + public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public override event StatusChangedEventHandler StatusChanged; + public ClientOperation( - IConfigurationRepository configuration, ILogger logger, IProcessFactory processFactory, IProxyFactory proxyFactory, IRuntimeHost runtimeHost, - int timeout_ms) + SessionContext sessionContext, + int timeout_ms) : base(sessionContext) { - this.configuration = configuration; this.logger = logger; this.processFactory = processFactory; this.proxyFactory = proxyFactory; @@ -61,7 +58,7 @@ namespace SafeExamBrowser.Runtime.Operations this.timeout_ms = timeout_ms; } - public virtual OperationResult Perform() + public override OperationResult Perform() { StatusChanged?.Invoke(TextKey.OperationStatus_StartClient); @@ -79,12 +76,12 @@ namespace SafeExamBrowser.Runtime.Operations return success ? OperationResult.Success : OperationResult.Failed; } - public virtual OperationResult Repeat() + public override OperationResult Repeat() { return Perform(); } - public virtual OperationResult Revert() + public override OperationResult Revert() { var success = true; @@ -103,10 +100,10 @@ namespace SafeExamBrowser.Runtime.Operations var clientReadyEvent = new AutoResetEvent(false); var clientReadyEventHandler = new CommunicationEventHandler(() => clientReadyEvent.Set()); - var clientExecutable = configuration.AppConfig.ClientExecutablePath; - var clientLogFile = $"{'"' + configuration.AppConfig.ClientLogFile + '"'}"; - var hostUri = configuration.AppConfig.RuntimeAddress; - var token = configuration.CurrentSession.StartupToken.ToString("D"); + var clientExecutable = Context.Next.AppConfig.ClientExecutablePath; + var clientLogFile = $"{'"' + Context.Next.AppConfig.ClientLogFile + '"'}"; + var hostUri = Context.Next.AppConfig.RuntimeAddress; + var token = Context.Next.StartupToken.ToString("D"); logger.Info("Starting new client process..."); runtimeHost.ClientReady += clientReadyEventHandler; @@ -124,9 +121,9 @@ namespace SafeExamBrowser.Runtime.Operations } logger.Info("Client has been successfully started and initialized. Creating communication proxy for client host..."); - ClientProxy = proxyFactory.CreateClientProxy(configuration.AppConfig.ClientAddress); + ClientProxy = proxyFactory.CreateClientProxy(Context.Next.AppConfig.ClientAddress); - if (!ClientProxy.Connect(configuration.CurrentSession.StartupToken)) + if (!ClientProxy.Connect(Context.Next.StartupToken)) { logger.Error("Failed to connect to client!"); diff --git a/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs b/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs index af3d9e0d..20fcd710 100644 --- a/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ClientTerminationOperation.cs @@ -8,22 +8,21 @@ using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Proxies; -using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.WindowsApi; namespace SafeExamBrowser.Runtime.Operations { - internal class ClientTerminationOperation : ClientOperation, IRepeatableOperation + internal class ClientTerminationOperation : ClientOperation { public ClientTerminationOperation( - IConfigurationRepository configuration, ILogger logger, IProcessFactory processFactory, IProxyFactory proxyFactory, IRuntimeHost runtimeHost, - int timeout_ms) : base(configuration, logger, processFactory, proxyFactory, runtimeHost, timeout_ms) + SessionContext sessionContext, + int timeout_ms) : base(logger, processFactory, proxyFactory, runtimeHost, sessionContext, timeout_ms) { } diff --git a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs index b0f6e9fa..3270588e 100644 --- a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs @@ -19,32 +19,30 @@ using SafeExamBrowser.Runtime.Operations.Events; namespace SafeExamBrowser.Runtime.Operations { - internal class ConfigurationOperation : IRepeatableOperation + internal class ConfigurationOperation : SessionOperation { + private string[] commandLineArgs; private IConfigurationRepository configuration; private ILogger logger; private IResourceLoader resourceLoader; - private AppConfig appConfig; - private string[] commandLineArgs; - public event ActionRequiredEventHandler ActionRequired; - public event StatusChangedEventHandler StatusChanged; + public override event ActionRequiredEventHandler ActionRequired; + public override event StatusChangedEventHandler StatusChanged; public ConfigurationOperation( - AppConfig appConfig, + string[] commandLineArgs, IConfigurationRepository configuration, ILogger logger, IResourceLoader resourceLoader, - string[] commandLineArgs) + SessionContext sessionContext) : base(sessionContext) { - this.appConfig = appConfig; + this.commandLineArgs = commandLineArgs; this.logger = logger; this.configuration = configuration; this.resourceLoader = resourceLoader; - this.commandLineArgs = commandLineArgs; } - public OperationResult Perform() + public override OperationResult Perform() { logger.Info("Initializing application configuration..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration); @@ -69,12 +67,12 @@ namespace SafeExamBrowser.Runtime.Operations return OperationResult.Success; } - public OperationResult Repeat() + public override OperationResult Repeat() { logger.Info("Initializing new application configuration..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration); - var isValidUri = TryValidateSettingsUri(configuration.ReconfigurationFilePath, out Uri uri); + var isValidUri = TryValidateSettingsUri(Context.ReconfigurationFilePath, out Uri uri); if (isValidUri) { @@ -92,7 +90,7 @@ namespace SafeExamBrowser.Runtime.Operations return OperationResult.Failed; } - public OperationResult Revert() + public override OperationResult Revert() { return OperationResult.Success; } @@ -101,11 +99,12 @@ namespace SafeExamBrowser.Runtime.Operations { var adminPassword = default(string); var settingsPassword = default(string); + var settings = default(Settings); var status = default(LoadStatus); for (int adminAttempts = 0, settingsAttempts = 0; adminAttempts < 5 && settingsAttempts < 5;) { - status = configuration.LoadSettings(uri, adminPassword, settingsPassword); + status = configuration.TryLoadSettings(uri, out settings, adminPassword, settingsPassword); if (status == LoadStatus.AdminPasswordNeeded || status == LoadStatus.SettingsPasswordNeeded) { @@ -132,7 +131,15 @@ namespace SafeExamBrowser.Runtime.Operations HandleInvalidData(ref status, uri); } - return status == LoadStatus.Success ? OperationResult.Success : OperationResult.Failed; + if (status == LoadStatus.Success) + { + Context.Next.Settings = settings; + + return OperationResult.Success; + } + + + return OperationResult.Failed; } private PasswordRequiredEventArgs TryGetPassword(LoadStatus status) @@ -150,7 +157,7 @@ namespace SafeExamBrowser.Runtime.Operations if (resourceLoader.IsHtmlResource(uri)) { configuration.LoadDefaultSettings(); - configuration.CurrentSettings.Browser.StartUrl = uri.AbsoluteUri; + Context.Next.Settings.Browser.StartUrl = uri.AbsoluteUri; logger.Info($"The specified URI '{uri.AbsoluteUri}' appears to point to a HTML resource, setting it as startup URL."); status = LoadStatus.Success; @@ -165,8 +172,8 @@ namespace SafeExamBrowser.Runtime.Operations { var path = string.Empty; var isValidUri = false; - var programDataSettings = Path.Combine(appConfig.ProgramDataFolder, appConfig.DefaultSettingsFileName); - var appDataSettings = Path.Combine(appConfig.AppDataFolder, appConfig.DefaultSettingsFileName); + var programDataSettings = Path.Combine(Context.Next.AppConfig.ProgramDataFolder, Context.Next.AppConfig.DefaultSettingsFileName); + var appDataSettings = Path.Combine(Context.Next.AppConfig.AppDataFolder, Context.Next.AppConfig.DefaultSettingsFileName); uri = null; @@ -206,7 +213,7 @@ namespace SafeExamBrowser.Runtime.Operations private void HandleClientConfiguration(ref OperationResult result) { - if (result == OperationResult.Success && configuration.CurrentSettings.ConfigurationMode == ConfigurationMode.ConfigureClient) + if (result == OperationResult.Success && Context.Next.Settings.ConfigurationMode == ConfigurationMode.ConfigureClient) { var args = new ConfigurationCompletedEventArgs(); diff --git a/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs b/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs index 0f6a3361..024c017d 100644 --- a/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs @@ -6,7 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel.Events; @@ -16,52 +15,40 @@ using SafeExamBrowser.Contracts.WindowsApi; namespace SafeExamBrowser.Runtime.Operations { - internal class KioskModeOperation : IRepeatableOperation + internal class KioskModeOperation : SessionOperation { - private IConfigurationRepository configuration; - private IDesktopFactory desktopFactory; - private IExplorerShell explorerShell; - private KioskMode kioskMode; - private ILogger logger; - private IProcessFactory processFactory; + protected IDesktopFactory desktopFactory; + protected IExplorerShell explorerShell; + protected ILogger logger; + protected IProcessFactory processFactory; - public event ActionRequiredEventHandler ActionRequired { add { } remove { } } - public event StatusChangedEventHandler StatusChanged; + private static IDesktop newDesktop; + private static IDesktop originalDesktop; - private IDesktop NewDesktop - { - get { return configuration.CurrentSession.NewDesktop; } - set { configuration.CurrentSession.NewDesktop = value; } - } + protected static KioskMode? ActiveMode { get; private set; } - private IDesktop OriginalDesktop - { - get { return configuration.CurrentSession.OriginalDesktop; } - set { configuration.CurrentSession.OriginalDesktop = value; } - } + public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public override event StatusChangedEventHandler StatusChanged; public KioskModeOperation( - IConfigurationRepository configuration, IDesktopFactory desktopFactory, IExplorerShell explorerShell, ILogger logger, - IProcessFactory processFactory) + IProcessFactory processFactory, + SessionContext sessionContext) : base(sessionContext) { - this.configuration = configuration; this.desktopFactory = desktopFactory; this.explorerShell = explorerShell; this.logger = logger; this.processFactory = processFactory; } - public virtual OperationResult Perform() + public override OperationResult Perform() { - kioskMode = configuration.CurrentSettings.KioskMode; - - logger.Info($"Initializing kiosk mode '{kioskMode}'..."); + logger.Info($"Initializing kiosk mode '{Context.Next.Settings.KioskMode}'..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeKioskMode); - switch (kioskMode) + switch (Context.Next.Settings.KioskMode) { case KioskMode.CreateNewDesktop: CreateNewDesktop(); @@ -71,17 +58,18 @@ namespace SafeExamBrowser.Runtime.Operations break; } + ActiveMode = Context.Next.Settings.KioskMode; + return OperationResult.Success; } - public virtual OperationResult Repeat() + public override OperationResult Repeat() { - var oldMode = kioskMode; - var newMode = configuration.CurrentSettings.KioskMode; + var newMode = Context.Next.Settings.KioskMode; - if (newMode == oldMode) + if (ActiveMode == newMode) { - logger.Info($"New kiosk mode '{newMode}' is equal to the currently active '{oldMode}', skipping re-initialization..."); + logger.Info($"New kiosk mode '{newMode}' is already active, skipping initialization..."); return OperationResult.Success; } @@ -89,12 +77,12 @@ namespace SafeExamBrowser.Runtime.Operations return Perform(); } - public virtual OperationResult Revert() + public override OperationResult Revert() { - logger.Info($"Reverting kiosk mode '{kioskMode}'..."); + logger.Info($"Reverting kiosk mode '{ActiveMode}'..."); StatusChanged?.Invoke(TextKey.OperationStatus_RevertKioskMode); - switch (kioskMode) + switch (ActiveMode) { case KioskMode.CreateNewDesktop: CloseNewDesktop(); @@ -109,14 +97,14 @@ namespace SafeExamBrowser.Runtime.Operations private void CreateNewDesktop() { - OriginalDesktop = desktopFactory.GetCurrent(); - logger.Info($"Current desktop is {OriginalDesktop}."); + originalDesktop = desktopFactory.GetCurrent(); + logger.Info($"Current desktop is {originalDesktop}."); - NewDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser)); - logger.Info($"Created new desktop {NewDesktop}."); + newDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser)); + logger.Info($"Created new desktop {newDesktop}."); - NewDesktop.Activate(); - processFactory.StartupDesktop = NewDesktop; + newDesktop.Activate(); + processFactory.StartupDesktop = newDesktop; logger.Info("Successfully activated new desktop."); explorerShell.Suspend(); @@ -124,21 +112,21 @@ namespace SafeExamBrowser.Runtime.Operations private void CloseNewDesktop() { - if (OriginalDesktop != null) + if (originalDesktop != null) { - OriginalDesktop.Activate(); - processFactory.StartupDesktop = OriginalDesktop; - logger.Info($"Switched back to original desktop {OriginalDesktop}."); + originalDesktop.Activate(); + processFactory.StartupDesktop = originalDesktop; + logger.Info($"Switched back to original desktop {originalDesktop}."); } else { logger.Warn($"No original desktop found when attempting to close new desktop!"); } - if (NewDesktop != null) + if (newDesktop != null) { - NewDesktop.Close(); - logger.Info($"Closed new desktop {NewDesktop}."); + newDesktop.Close(); + logger.Info($"Closed new desktop {newDesktop}."); } else { @@ -151,6 +139,9 @@ namespace SafeExamBrowser.Runtime.Operations private void TerminateExplorerShell() { StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerTermination); + + // TODO: Hiding all windows must be done here, as the explorer shell is needed to do so! + explorerShell.Terminate(); } @@ -158,6 +149,8 @@ namespace SafeExamBrowser.Runtime.Operations { StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerStartup); explorerShell.Start(); + + // TODO: Restore all hidden windows! } } } diff --git a/SafeExamBrowser.Runtime/Operations/KioskModeTerminationOperation.cs b/SafeExamBrowser.Runtime/Operations/KioskModeTerminationOperation.cs index 09f26bae..04555167 100644 --- a/SafeExamBrowser.Runtime/Operations/KioskModeTerminationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/KioskModeTerminationOperation.cs @@ -6,8 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.Contracts.Configuration; -using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.WindowsApi; @@ -16,36 +14,27 @@ namespace SafeExamBrowser.Runtime.Operations { internal class KioskModeTerminationOperation : KioskModeOperation, IRepeatableOperation { - private IConfigurationRepository configuration; - private KioskMode kioskMode; - private ILogger logger; - public KioskModeTerminationOperation( - IConfigurationRepository configuration, IDesktopFactory desktopFactory, IExplorerShell explorerShell, ILogger logger, - IProcessFactory processFactory) : base(configuration, desktopFactory, explorerShell, logger, processFactory) + IProcessFactory processFactory, + SessionContext sessionContext) : base(desktopFactory, explorerShell, logger, processFactory, sessionContext) { - this.configuration = configuration; - this.logger = logger; } public override OperationResult Perform() { - kioskMode = configuration.CurrentSettings.KioskMode; - return OperationResult.Success; } public override OperationResult Repeat() { - var oldMode = kioskMode; - var newMode = configuration.CurrentSettings.KioskMode; + var newMode = Context.Next.Settings.KioskMode; - if (newMode == oldMode) + if (ActiveMode == newMode) { - logger.Info($"New kiosk mode '{newMode}' is equal to the currently active '{oldMode}', skipping termination..."); + logger.Info($"New kiosk mode '{newMode}' is the same as the currently active, skipping termination..."); return OperationResult.Success; } diff --git a/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs b/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs index 92aac37a..d5a62503 100644 --- a/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ServiceOperation.cs @@ -7,7 +7,6 @@ */ using SafeExamBrowser.Contracts.Communication.Proxies; -using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel.Events; @@ -16,29 +15,27 @@ using SafeExamBrowser.Contracts.Logging; namespace SafeExamBrowser.Runtime.Operations { - internal class ServiceOperation : IRepeatableOperation + internal class ServiceOperation : SessionOperation { private bool connected, mandatory; - private IConfigurationRepository configuration; private ILogger logger; private IServiceProxy service; - public event ActionRequiredEventHandler ActionRequired { add { } remove { } } - public event StatusChangedEventHandler StatusChanged; + public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public override event StatusChangedEventHandler StatusChanged; - public ServiceOperation(IConfigurationRepository configuration, ILogger logger, IServiceProxy service) + public ServiceOperation(ILogger logger, IServiceProxy service, SessionContext sessionContext) : base(sessionContext) { - this.configuration = configuration; - this.service = service; this.logger = logger; + this.service = service; } - public OperationResult Perform() + public override OperationResult Perform() { logger.Info($"Initializing service session..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); - mandatory = configuration.CurrentSettings.ServicePolicy == ServicePolicy.Mandatory; + mandatory = Context.Next.Settings.ServicePolicy == ServicePolicy.Mandatory; connected = service.Connect(); if (mandatory && !connected) @@ -59,7 +56,7 @@ namespace SafeExamBrowser.Runtime.Operations return OperationResult.Success; } - public OperationResult Repeat() + public override OperationResult Repeat() { var result = Revert(); @@ -71,7 +68,7 @@ namespace SafeExamBrowser.Runtime.Operations return Perform(); } - public OperationResult Revert() + public override OperationResult Revert() { logger.Info("Finalizing service session..."); StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession); @@ -97,12 +94,12 @@ namespace SafeExamBrowser.Runtime.Operations private void StartServiceSession() { - service.StartSession(configuration.CurrentSession.Id, configuration.CurrentSettings); + service.StartSession(Context.Next.Id, Context.Next.Settings); } private void StopServiceSession() { - service.StopSession(configuration.CurrentSession.Id); + service.StopSession(Context.Current.Id); } } } diff --git a/SafeExamBrowser.Runtime/Operations/SessionActivationOperation.cs b/SafeExamBrowser.Runtime/Operations/SessionActivationOperation.cs new file mode 100644 index 00000000..9d68912c --- /dev/null +++ b/SafeExamBrowser.Runtime/Operations/SessionActivationOperation.cs @@ -0,0 +1,63 @@ +/* + * 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.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; +using SafeExamBrowser.Contracts.Logging; + +namespace SafeExamBrowser.Runtime.Operations +{ + internal class SessionActivationOperation : SessionOperation + { + private ILogger logger; + + public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public override event StatusChangedEventHandler StatusChanged { add { } remove { } } + + public SessionActivationOperation(ILogger logger, SessionContext sessionContext) : base(sessionContext) + { + this.logger = logger; + } + + public override OperationResult Perform() + { + ActivateNewSession(); + + return OperationResult.Success; + } + + public override OperationResult Repeat() + { + ActivateNewSession(); + + return OperationResult.Success; + } + + public override OperationResult Revert() + { + return OperationResult.Success; + } + + private void ActivateNewSession() + { + var isFirstSession = Context.Current == null; + + if (isFirstSession) + { + logger.Info($"Successfully activated first session '{Context.Next.Id}'."); + } + else + { + logger.Info($"Successfully terminated old session '{Context.Current.Id}' and activated new session '{Context.Next.Id}'."); + } + + Context.Current = Context.Next; + Context.Next = null; + } + } +} diff --git a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs index 4c7a0a48..a60ed095 100644 --- a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs @@ -15,35 +15,41 @@ using SafeExamBrowser.Contracts.Logging; namespace SafeExamBrowser.Runtime.Operations { - internal class SessionInitializationOperation : IRepeatableOperation + internal class SessionInitializationOperation : SessionOperation { private IConfigurationRepository configuration; private ILogger logger; private IRuntimeHost runtimeHost; - public event ActionRequiredEventHandler ActionRequired { add { } remove { } } - public event StatusChangedEventHandler StatusChanged; + public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } + public override event StatusChangedEventHandler StatusChanged; - public SessionInitializationOperation(IConfigurationRepository configuration, ILogger logger, IRuntimeHost runtimeHost) + public SessionInitializationOperation( + IConfigurationRepository configuration, + ILogger logger, + IRuntimeHost runtimeHost, + SessionContext sessionContext) : base(sessionContext) { this.configuration = configuration; this.logger = logger; this.runtimeHost = runtimeHost; } - public OperationResult Perform() + public override OperationResult Perform() { InitializeSessionConfiguration(); return OperationResult.Success; } - public OperationResult Repeat() + public override OperationResult Repeat() { - return Perform(); + InitializeSessionConfiguration(); + + return OperationResult.Success; } - public OperationResult Revert() + public override OperationResult Revert() { return OperationResult.Success; } @@ -53,12 +59,12 @@ namespace SafeExamBrowser.Runtime.Operations logger.Info("Initializing new session configuration..."); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSession); - configuration.InitializeSessionConfiguration(); - runtimeHost.StartupToken = configuration.CurrentSession.StartupToken; + Context.Next = configuration.InitializeSessionConfiguration(); + runtimeHost.StartupToken = Context.Next.StartupToken; - logger.Info($" -> Client-ID: {configuration.AppConfig.ClientId}"); - logger.Info($" -> Runtime-ID: {configuration.AppConfig.RuntimeId}"); - logger.Info($" -> Session-ID: {configuration.CurrentSession.Id}"); + logger.Info($" -> Client-ID: {Context.Next.AppConfig.ClientId}"); + logger.Info($" -> Runtime-ID: {Context.Next.AppConfig.RuntimeId}"); + logger.Info($" -> Session-ID: {Context.Next.Id}"); } } } diff --git a/SafeExamBrowser.Runtime/Operations/SessionOperation.cs b/SafeExamBrowser.Runtime/Operations/SessionOperation.cs new file mode 100644 index 00000000..3bdba83c --- /dev/null +++ b/SafeExamBrowser.Runtime/Operations/SessionOperation.cs @@ -0,0 +1,25 @@ +using SafeExamBrowser.Contracts.Core.OperationModel; +using SafeExamBrowser.Contracts.Core.OperationModel.Events; + +namespace SafeExamBrowser.Runtime.Operations +{ + /// + /// The base implementation to be used for all operations in the session operation sequence. + /// + internal abstract class SessionOperation : IRepeatableOperation + { + protected SessionContext Context { get; private set; } + + public abstract event ActionRequiredEventHandler ActionRequired; + public abstract event StatusChangedEventHandler StatusChanged; + + public SessionOperation(SessionContext sessionContext) + { + Context = sessionContext; + } + + public abstract OperationResult Perform(); + public abstract OperationResult Repeat(); + public abstract OperationResult Revert(); + } +} diff --git a/SafeExamBrowser.Runtime/RuntimeController.cs b/SafeExamBrowser.Runtime/RuntimeController.cs index 0c3178b1..0a441c72 100644 --- a/SafeExamBrowser.Runtime/RuntimeController.cs +++ b/SafeExamBrowser.Runtime/RuntimeController.cs @@ -28,10 +28,7 @@ namespace SafeExamBrowser.Runtime { internal class RuntimeController : IRuntimeController { - private bool sessionRunning; - private AppConfig appConfig; - private IConfigurationRepository configuration; private ILogger logger; private IMessageBox messageBox; private IOperationSequence bootstrapSequence; @@ -39,32 +36,43 @@ namespace SafeExamBrowser.Runtime private IRuntimeHost runtimeHost; private IRuntimeWindow runtimeWindow; private IServiceProxy service; + private SessionContext sessionContext; private ISplashScreen splashScreen; private Action shutdown; private IText text; private IUserInterfaceFactory uiFactory; + private ISessionConfiguration Session + { + get { return sessionContext.Current; } + } + + private bool SessionIsRunning + { + get { return Session != null; } + } + public RuntimeController( AppConfig appConfig, - IConfigurationRepository configuration, ILogger logger, IMessageBox messageBox, IOperationSequence bootstrapSequence, IRepeatableOperationSequence sessionSequence, IRuntimeHost runtimeHost, IServiceProxy service, + SessionContext sessionContext, Action shutdown, IText text, IUserInterfaceFactory uiFactory) { this.appConfig = appConfig; - this.configuration = configuration; this.bootstrapSequence = bootstrapSequence; this.logger = logger; this.messageBox = messageBox; this.runtimeHost = runtimeHost; this.sessionSequence = sessionSequence; this.service = service; + this.sessionContext = sessionContext; this.shutdown = shutdown; this.text = text; this.uiFactory = uiFactory; @@ -96,7 +104,7 @@ namespace SafeExamBrowser.Runtime logger.Subscribe(runtimeWindow); splashScreen.Close(); - StartSession(true); + StartSession(); } else { @@ -106,14 +114,14 @@ namespace SafeExamBrowser.Runtime messageBox.Show(TextKey.MessageBox_StartupError, TextKey.MessageBox_StartupErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen); } - return initialized && sessionRunning; + return initialized && SessionIsRunning; } public void Terminate() { DeregisterEvents(); - if (sessionRunning) + if (SessionIsRunning) { StopSession(); } @@ -145,51 +153,79 @@ namespace SafeExamBrowser.Runtime splashScreen.Close(); } - private void StartSession(bool initial = false) + private void StartSession() { runtimeWindow.Show(); runtimeWindow.BringToForeground(); runtimeWindow.ShowProgressBar(); logger.Info("### --- Session Start Procedure --- ###"); - if (sessionRunning) + if (SessionIsRunning) { DeregisterSessionEvents(); } - var result = initial ? sessionSequence.TryPerform() : sessionSequence.TryRepeat(); + var result = SessionIsRunning ? sessionSequence.TryRepeat() : sessionSequence.TryPerform(); if (result == OperationResult.Success) { - RegisterSessionEvents(); - logger.Info("### --- Session Running --- ###"); + + HandleSessionStartSuccess(); + } + else if (result == OperationResult.Failed) + { + logger.Info("### --- Session Start Failed --- ###"); + + HandleSessionStartFailure(); + } + else if (result == OperationResult.Aborted) + { + logger.Info("### --- Session Start Aborted --- ###"); + + HandleSessionStartAbortion(); + } + } + + private void HandleSessionStartSuccess() + { + RegisterSessionEvents(); + + runtimeWindow.HideProgressBar(); + runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning); + runtimeWindow.TopMost = Session.Settings.KioskMode != KioskMode.None; + + if (Session.Settings.KioskMode == KioskMode.DisableExplorerShell) + { + runtimeWindow.Hide(); + } + } + + private void HandleSessionStartFailure() + { + if (SessionIsRunning) + { + StopSession(); + + messageBox.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow); + logger.Info("Terminating application..."); + + shutdown.Invoke(); + } + } + + private void HandleSessionStartAbortion() + { + if (SessionIsRunning) + { runtimeWindow.HideProgressBar(); runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning); - runtimeWindow.TopMost = configuration.CurrentSettings.KioskMode != KioskMode.None; + runtimeWindow.TopMost = Session.Settings.KioskMode != KioskMode.None; - if (configuration.CurrentSettings.KioskMode == KioskMode.DisableExplorerShell) + if (Session.Settings.KioskMode == KioskMode.DisableExplorerShell) { runtimeWindow.Hide(); } - - sessionRunning = true; - } - else - { - logger.Info($"### --- Session Start {(result == OperationResult.Aborted ? "Aborted" : "Failed")} --- ###"); - - if (result == OperationResult.Failed) - { - // TODO: Find solution for this; maybe manually switch back to original desktop? - // messageBox.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow); - - if (!initial) - { - logger.Info("Terminating application..."); - shutdown.Invoke(); - } - } } } @@ -207,7 +243,6 @@ namespace SafeExamBrowser.Runtime if (success) { logger.Info("### --- Session Terminated --- ###"); - sessionRunning = false; } else { @@ -218,32 +253,34 @@ namespace SafeExamBrowser.Runtime private void RegisterEvents() { + runtimeHost.ClientConfigurationNeeded += RuntimeHost_ClientConfigurationNeeded; runtimeHost.ReconfigurationRequested += RuntimeHost_ReconfigurationRequested; runtimeHost.ShutdownRequested += RuntimeHost_ShutdownRequested; } private void DeregisterEvents() { + runtimeHost.ClientConfigurationNeeded -= RuntimeHost_ClientConfigurationNeeded; runtimeHost.ReconfigurationRequested -= RuntimeHost_ReconfigurationRequested; runtimeHost.ShutdownRequested -= RuntimeHost_ShutdownRequested; } private void RegisterSessionEvents() { - configuration.CurrentSession.ClientProcess.Terminated += ClientProcess_Terminated; - configuration.CurrentSession.ClientProxy.ConnectionLost += Client_ConnectionLost; + sessionContext.ClientProcess.Terminated += ClientProcess_Terminated; + sessionContext.ClientProxy.ConnectionLost += Client_ConnectionLost; } private void DeregisterSessionEvents() { - if (configuration.CurrentSession.ClientProcess != null) + if (sessionContext.ClientProcess != null) { - configuration.CurrentSession.ClientProcess.Terminated -= ClientProcess_Terminated; + sessionContext.ClientProcess.Terminated -= ClientProcess_Terminated; } - if (configuration.CurrentSession.ClientProxy != null) + if (sessionContext.ClientProxy != null) { - configuration.CurrentSession.ClientProxy.ConnectionLost -= Client_ConnectionLost; + sessionContext.ClientProxy.ConnectionLost -= Client_ConnectionLost; } } @@ -261,7 +298,7 @@ namespace SafeExamBrowser.Runtime { logger.Error($"Client application has unexpectedly terminated with exit code {exitCode}!"); - if (sessionRunning) + if (SessionIsRunning) { StopSession(); } @@ -275,7 +312,7 @@ namespace SafeExamBrowser.Runtime { logger.Error("Lost connection to the client application!"); - if (sessionRunning) + if (SessionIsRunning) { StopSession(); } @@ -285,21 +322,31 @@ namespace SafeExamBrowser.Runtime shutdown.Invoke(); } + private void RuntimeHost_ClientConfigurationNeeded(ClientConfigurationEventArgs args) + { + args.ClientConfiguration = new ClientConfiguration + { + AppConfig = sessionContext.Next.AppConfig, + SessionId = sessionContext.Next.Id, + Settings = sessionContext.Next.Settings + }; + } + private void RuntimeHost_ReconfigurationRequested(ReconfigurationEventArgs args) { - var mode = configuration.CurrentSettings.ConfigurationMode; + var mode = Session.Settings.ConfigurationMode; if (mode == ConfigurationMode.ConfigureClient) { logger.Info($"Accepted request for reconfiguration with '{args.ConfigurationPath}'."); - configuration.ReconfigurationFilePath = args.ConfigurationPath; + sessionContext.ReconfigurationFilePath = args.ConfigurationPath; StartSession(); } else { logger.Info($"Denied request for reconfiguration with '{args.ConfigurationPath}' due to '{mode}' mode!"); - configuration.CurrentSession.ClientProxy.InformReconfigurationDenied(args.ConfigurationPath); + sessionContext.ClientProxy.InformReconfigurationDenied(args.ConfigurationPath); } } @@ -333,8 +380,8 @@ namespace SafeExamBrowser.Runtime private void AskForPassword(PasswordRequiredEventArgs args) { - var isStartup = configuration.CurrentSession == null; - var isRunningOnDefaultDesktop = configuration.CurrentSettings?.KioskMode == KioskMode.DisableExplorerShell; + var isStartup = !SessionIsRunning; + var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.KioskMode == KioskMode.DisableExplorerShell; if (isStartup || isRunningOnDefaultDesktop) { @@ -374,7 +421,7 @@ namespace SafeExamBrowser.Runtime runtimeHost.PasswordReceived += responseEventHandler; - var communication = configuration.CurrentSession.ClientProxy.RequestPassword(args.Purpose, requestId); + var communication = sessionContext.ClientProxy.RequestPassword(args.Purpose, requestId); if (communication.Success) { @@ -401,6 +448,9 @@ namespace SafeExamBrowser.Runtime runtimeWindow?.UpdateStatus(status, true); } + /// + /// TODO: Move to utility in core library and use in client controller! + /// private void MapProgress(IProgressIndicator progressIndicator, ProgressChangedEventArgs args) { if (args.CurrentValue.HasValue) diff --git a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj index 71bf3f6c..baeaea2b 100644 --- a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj +++ b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj @@ -94,6 +94,8 @@ + + @@ -112,6 +114,7 @@ True + ResXFileCodeGenerator Resources.Designer.cs diff --git a/SafeExamBrowser.Runtime/SessionContext.cs b/SafeExamBrowser.Runtime/SessionContext.cs new file mode 100644 index 00000000..ac66560e --- /dev/null +++ b/SafeExamBrowser.Runtime/SessionContext.cs @@ -0,0 +1,45 @@ +/* + * 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.Proxies; +using SafeExamBrowser.Contracts.Configuration; +using SafeExamBrowser.Contracts.WindowsApi; + +namespace SafeExamBrowser.Runtime +{ + /// + /// Holds all configuration and runtime data required for the session handling. + /// + internal class SessionContext + { + /// + /// The currently running client process. + /// + public IProcess ClientProcess { get; set; } + + /// + /// The communication proxy for the currently running client process. + /// + public IClientProxy ClientProxy { get; set; } + + /// + /// The configuration of the currently active session. + /// + public ISessionConfiguration Current { get; set; } + + /// + /// The configuration of the next session to be activated. + /// + public ISessionConfiguration Next { get; set; } + + /// + /// The path of the configuration file to be used for reconfiguration. + /// + public string ReconfigurationFilePath { get; set; } + } +}