SEBWIN-221: Fixed implementation of session sequence to correctly handle initialization, transition and termination of sessions by introducing the SessionContext.

This commit is contained in:
dbuechel 2018-10-12 11:16:59 +02:00
parent f4631a1a3d
commit 95ad176047
25 changed files with 551 additions and 375 deletions

View file

@ -74,7 +74,7 @@ namespace SafeExamBrowser.Communication.Proxies
{ {
if (Ignore) 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; return Ignore;

View file

@ -25,86 +25,15 @@ namespace SafeExamBrowser.Configuration
private AppConfig appConfig; 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) public ConfigurationRepository(string executablePath, string programCopyright, string programTitle, string programVersion)
{ {
this.executablePath = executablePath ?? throw new ArgumentNullException(nameof(executablePath)); this.executablePath = executablePath ?? string.Empty;
this.programCopyright = programCopyright ?? throw new ArgumentNullException(nameof(programCopyright)); this.programCopyright = programCopyright ?? string.Empty;
this.programTitle = programTitle ?? throw new ArgumentNullException(nameof(programTitle)); this.programTitle = programTitle ?? string.Empty;
this.programVersion = programVersion ?? throw new ArgumentNullException(nameof(programVersion)); this.programVersion = programVersion ?? string.Empty;
} }
public ClientConfiguration BuildClientConfiguration() public AppConfig InitializeAppConfig()
{
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()
{ {
var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser)); var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser));
var startTime = DateTime.Now; var startTime = DateTime.Now;
@ -134,12 +63,89 @@ namespace SafeExamBrowser.Configuration
appConfig.SebUriScheme = "seb"; appConfig.SebUriScheme = "seb";
appConfig.SebUriSchemeSecure = "sebs"; appConfig.SebUriSchemeSecure = "sebs";
appConfig.ServiceAddress = $"{BASE_ADDRESS}/service"; 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() private void UpdateAppConfig()
{ {
AppConfig.ClientId = Guid.NewGuid(); appConfig.ClientId = Guid.NewGuid();
AppConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}"; appConfig.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}";
} }
} }
} }

View file

@ -15,7 +15,7 @@ namespace SafeExamBrowser.Configuration
{ {
public bool IsHtmlResource(Uri resource) public bool IsHtmlResource(Uri resource)
{ {
// TODO // TODO: Implement resource loader
return false; return false;
} }
} }

View file

@ -53,9 +53,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ResourceLoader.cs" /> <Compile Include="ResourceLoader.cs" />
<Compile Include="SessionData.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ConfigurationRepository.cs" /> <Compile Include="ConfigurationRepository.cs" />
<Compile Include="SessionConfiguration.cs" />
<Compile Include="SystemInfo.cs" /> <Compile Include="SystemInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -7,19 +7,16 @@
*/ */
using System; using System;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.Contracts.Configuration.Settings;
namespace SafeExamBrowser.Configuration namespace SafeExamBrowser.Configuration
{ {
public class SessionData : ISessionData internal class SessionConfiguration : ISessionConfiguration
{ {
public IClientProxy ClientProxy { get; set; } public AppConfig AppConfig { get; set; }
public IProcess ClientProcess { get; set; }
public Guid Id { get; set; } public Guid Id { get; set; }
public IDesktop NewDesktop { get; set; } public Settings Settings { get; set; }
public IDesktop OriginalDesktop { get; set; }
public Guid StartupToken { get; set; } public Guid StartupToken { get; set; }
} }
} }

View file

@ -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
{
/// <summary>
/// The event arguments used for the client configuration event fired by the <see cref="Hosts.IRuntimeHost"/>.
/// </summary>
public class ClientConfigurationEventArgs : CommunicationEventArgs
{
/// <summary>
/// The configuration to be sent to the client.
/// </summary>
public ClientConfiguration ClientConfiguration { get; set; }
}
}

View file

@ -31,6 +31,11 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// </summary> /// </summary>
event CommunicationEventHandler ClientReady; event CommunicationEventHandler ClientReady;
/// <summary>
/// Event fired when the client requested its configuration data.
/// </summary>
event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded;
/// <summary> /// <summary>
/// Event fired when the client submitted a password entered by the user. /// Event fired when the client submitted a password entered by the user.
/// </summary> /// </summary>

View file

@ -16,46 +16,25 @@ namespace SafeExamBrowser.Contracts.Configuration
public interface IConfigurationRepository public interface IConfigurationRepository
{ {
/// <summary> /// <summary>
/// The global configuration information for the currently running application instance. /// Initializes the global configuration information for the currently running application instance.
/// </summary> /// </summary>
AppConfig AppConfig { get; } AppConfig InitializeAppConfig();
/// <summary> /// <summary>
/// Retrieves the current session data, i.e. the last one which was initialized. If no session has been initialized yet, this /// Initializes all relevant configuration data for a new session.
/// property will be <c>null</c>!
/// </summary> /// </summary>
ISessionData CurrentSession { get; } ISessionConfiguration InitializeSessionConfiguration();
/// <summary>
/// Retrieves the current settings, i.e. the last ones which were loaded. If no settings have been loaded yet, this property will
/// be <c>null</c>!
/// </summary>
Settings.Settings CurrentSettings { get; }
/// <summary>
/// The path of the settings file to be used when reconfiguring the application.
/// </summary>
string ReconfigurationFilePath { get; set; }
/// <summary>
/// Builds a configuration for the client component, given the currently loaded settings, session and runtime information.
/// </summary>
ClientConfiguration BuildClientConfiguration();
/// <summary>
/// Initializes all relevant data for a new session.
/// </summary>
void InitializeSessionConfiguration();
/// <summary> /// <summary>
/// Attempts to load settings from the specified resource, using the optional passwords. Returns a <see cref="LoadStatus"/> /// Attempts to load settings from the specified resource, using the optional passwords. Returns a <see cref="LoadStatus"/>
/// indicating the result of the operation. /// indicating the result of the operation. As long as the result is not <see cref="LoadStatus.Success"/>, the declared
/// <paramref name="settings"/> will be <c>null</c>!
/// </summary> /// </summary>
LoadStatus LoadSettings(Uri resource, string adminPassword = null, string settingsPassword = null); LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, string adminPassword = null, string settingsPassword = null);
/// <summary> /// <summary>
/// Loads the default settings. /// Loads the default settings.
/// </summary> /// </summary>
void LoadDefaultSettings(); Settings.Settings LoadDefaultSettings();
} }
} }

View file

@ -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
{
/// <summary>
/// Holds all session-related configuration data.
/// </summary>
public interface ISessionConfiguration
{
/// <summary>
/// The active application configuration for this session.
/// </summary>
AppConfig AppConfig { get; }
/// <summary>
/// The unique session identifier.
/// </summary>
Guid Id { get; }
/// <summary>
/// The settings used for this session.
/// </summary>
Settings.Settings Settings { get; set; }
/// <summary>
/// The startup token used by the client and runtime components for initial authentication.
/// </summary>
Guid StartupToken { get; }
}
}

View file

@ -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
{
/// <summary>
/// Holds all session-related configuration and runtime data.
/// </summary>
public interface ISessionData
{
/// <summary>
/// The communication proxy for the client instance associated to this session.
/// </summary>
IClientProxy ClientProxy { get; set; }
/// <summary>
/// The process information of the client instance associated to this session.
/// </summary>
IProcess ClientProcess { get; set; }
/// <summary>
/// The unique session identifier.
/// </summary>
Guid Id { get; }
/// <summary>
/// The new desktop, if <see cref="Settings.KioskMode.CreateNewDesktop"/> is active for this session.
/// </summary>
IDesktop NewDesktop { get; set; }
/// <summary>
/// The original desktop, if <see cref="Settings.KioskMode.CreateNewDesktop"/> is active for this session.
/// </summary>
IDesktop OriginalDesktop { get; set; }
/// <summary>
/// The startup token used by the client and runtime components for initial authentication.
/// </summary>
Guid StartupToken { get; }
}
}

View file

@ -52,6 +52,7 @@
<Reference Include="System.ServiceModel" /> <Reference Include="System.ServiceModel" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" />
<Compile Include="Core\Events\InstanceTerminatedEventHandler.cs" /> <Compile Include="Core\Events\InstanceTerminatedEventHandler.cs" />
<Compile Include="Core\Events\NameChangedEventHandler.cs" /> <Compile Include="Core\Events\NameChangedEventHandler.cs" />
<Compile Include="Core\IApplicationController.cs" /> <Compile Include="Core\IApplicationController.cs" />
@ -109,7 +110,7 @@
<Compile Include="Configuration\IResourceLoader.cs" /> <Compile Include="Configuration\IResourceLoader.cs" />
<Compile Include="Configuration\LoadStatus.cs" /> <Compile Include="Configuration\LoadStatus.cs" />
<Compile Include="Configuration\AppConfig.cs" /> <Compile Include="Configuration\AppConfig.cs" />
<Compile Include="Configuration\ISessionData.cs" /> <Compile Include="Configuration\ISessionConfiguration.cs" />
<Compile Include="Configuration\Settings\ConfigurationMode.cs" /> <Compile Include="Configuration\Settings\ConfigurationMode.cs" />
<Compile Include="Core\INotificationController.cs" /> <Compile Include="Core\INotificationController.cs" />
<Compile Include="Core\OperationModel\IOperation.cs" /> <Compile Include="Core\OperationModel\IOperation.cs" />

View file

@ -11,7 +11,6 @@ using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Runtime.Communication namespace SafeExamBrowser.Runtime.Communication
@ -19,24 +18,18 @@ namespace SafeExamBrowser.Runtime.Communication
internal class RuntimeHost : BaseHost, IRuntimeHost internal class RuntimeHost : BaseHost, IRuntimeHost
{ {
private bool allowConnection = true; private bool allowConnection = true;
private IConfigurationRepository configuration;
public Guid StartupToken { private get; set; } public Guid StartupToken { private get; set; }
public event CommunicationEventHandler ClientDisconnected; public event CommunicationEventHandler ClientDisconnected;
public event CommunicationEventHandler ClientReady; public event CommunicationEventHandler ClientReady;
public event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded;
public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived; public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested; public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
public event CommunicationEventHandler ShutdownRequested; public event CommunicationEventHandler ShutdownRequested;
public RuntimeHost( public RuntimeHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms)
string address,
IConfigurationRepository configuration,
IHostObjectFactory factory,
ILogger logger,
int timeout_ms) : base(address, factory, logger, timeout_ms)
{ {
this.configuration = configuration;
} }
protected override bool OnConnect(Guid? token = null) protected override bool OnConnect(Guid? token = null)
@ -87,8 +80,7 @@ namespace SafeExamBrowser.Runtime.Communication
ClientReady?.Invoke(); ClientReady?.Invoke();
return new SimpleResponse(SimpleResponsePurport.Acknowledged); return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case SimpleMessagePurport.ConfigurationNeeded: case SimpleMessagePurport.ConfigurationNeeded:
// TODO: Not the job of the host, fire event or alike! return HandleConfigurationRequest();
return new ConfigurationResponse { Configuration = configuration.BuildClientConfiguration() };
case SimpleMessagePurport.RequestShutdown: case SimpleMessagePurport.RequestShutdown:
ShutdownRequested?.Invoke(); ShutdownRequested?.Invoke();
return new SimpleResponse(SimpleResponsePurport.Acknowledged); return new SimpleResponse(SimpleResponsePurport.Acknowledged);
@ -96,5 +88,14 @@ namespace SafeExamBrowser.Runtime.Communication
return new SimpleResponse(SimpleResponsePurport.UnknownMessage); return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }
private Response HandleConfigurationRequest()
{
var args = new ClientConfigurationEventArgs();
ClientConfigurationNeeded?.Invoke(args);
return new ConfigurationResponse { Configuration = args.ClientConfiguration };
}
} }
} }

View file

@ -49,7 +49,7 @@ namespace SafeExamBrowser.Runtime
var nativeMethods = new NativeMethods(); var nativeMethods = new NativeMethods();
logger = new Logger(); logger = new Logger();
appConfig = configuration.AppConfig; appConfig = configuration.InitializeAppConfig();
systemInfo = new SystemInfo(); systemInfo = new SystemInfo();
InitializeLogging(); InitializeLogging();
@ -61,8 +61,9 @@ namespace SafeExamBrowser.Runtime
var processFactory = new ProcessFactory(new ModuleLogger(logger, nameof(ProcessFactory))); var processFactory = new ProcessFactory(new ModuleLogger(logger, nameof(ProcessFactory)));
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger); var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger);
var resourceLoader = new ResourceLoader(); 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 serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(ServiceProxy)));
var sessionContext = new SessionContext();
var uiFactory = new UserInterfaceFactory(text); var uiFactory = new UserInterfaceFactory(text);
var bootstrapOperations = new Queue<IOperation>(); var bootstrapOperations = new Queue<IOperation>();
@ -71,18 +72,19 @@ namespace SafeExamBrowser.Runtime
bootstrapOperations.Enqueue(new I18nOperation(logger, text, textResource)); bootstrapOperations.Enqueue(new I18nOperation(logger, text, textResource));
bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger)); bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger));
sessionOperations.Enqueue(new ConfigurationOperation(appConfig, configuration, logger, resourceLoader, args)); sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost, sessionContext));
sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, FIFTEEN_SECONDS)); sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, logger, resourceLoader, sessionContext));
sessionOperations.Enqueue(new KioskModeTerminationOperation(configuration, desktopFactory, explorerShell, logger, processFactory)); sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, FIFTEEN_SECONDS));
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost)); sessionOperations.Enqueue(new KioskModeTerminationOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy)); sessionOperations.Enqueue(new ServiceOperation(logger, serviceProxy, sessionContext));
sessionOperations.Enqueue(new KioskModeOperation(configuration, desktopFactory, explorerShell, logger, processFactory)); sessionOperations.Enqueue(new KioskModeOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new ClientOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, FIFTEEN_SECONDS)); sessionOperations.Enqueue(new ClientOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, FIFTEEN_SECONDS));
sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext));
var bootstrapSequence = new OperationSequence(logger, bootstrapOperations); var bootstrapSequence = new OperationSequence(logger, bootstrapOperations);
var sessionSequence = new RepeatableOperationSequence(logger, sessionOperations); 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() 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")}"); 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 executable = Assembly.GetExecutingAssembly();
var programCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright; var programCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright;

View file

@ -10,7 +10,6 @@ using System.Threading;
using SafeExamBrowser.Contracts.Communication.Events; using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
@ -20,40 +19,38 @@ using SafeExamBrowser.Contracts.WindowsApi.Events;
namespace SafeExamBrowser.Runtime.Operations namespace SafeExamBrowser.Runtime.Operations
{ {
internal class ClientOperation : IRepeatableOperation internal class ClientOperation : SessionOperation
{ {
private readonly int timeout_ms; private readonly int timeout_ms;
private IConfigurationRepository configuration;
private ILogger logger; private ILogger logger;
private IProcessFactory processFactory; private IProcessFactory processFactory;
private IProxyFactory proxyFactory; private IProxyFactory proxyFactory;
private IRuntimeHost runtimeHost; private IRuntimeHost runtimeHost;
public event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public event StatusChangedEventHandler StatusChanged;
private IProcess ClientProcess private IProcess ClientProcess
{ {
get { return configuration.CurrentSession.ClientProcess; } get { return Context.ClientProcess; }
set { configuration.CurrentSession.ClientProcess = value; } set { Context.ClientProcess = value; }
} }
private IClientProxy ClientProxy private IClientProxy ClientProxy
{ {
get { return configuration.CurrentSession.ClientProxy; } get { return Context.ClientProxy; }
set { configuration.CurrentSession.ClientProxy = value; } set { Context.ClientProxy = value; }
} }
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ClientOperation( public ClientOperation(
IConfigurationRepository configuration,
ILogger logger, ILogger logger,
IProcessFactory processFactory, IProcessFactory processFactory,
IProxyFactory proxyFactory, IProxyFactory proxyFactory,
IRuntimeHost runtimeHost, IRuntimeHost runtimeHost,
int timeout_ms) SessionContext sessionContext,
int timeout_ms) : base(sessionContext)
{ {
this.configuration = configuration;
this.logger = logger; this.logger = logger;
this.processFactory = processFactory; this.processFactory = processFactory;
this.proxyFactory = proxyFactory; this.proxyFactory = proxyFactory;
@ -61,7 +58,7 @@ namespace SafeExamBrowser.Runtime.Operations
this.timeout_ms = timeout_ms; this.timeout_ms = timeout_ms;
} }
public virtual OperationResult Perform() public override OperationResult Perform()
{ {
StatusChanged?.Invoke(TextKey.OperationStatus_StartClient); StatusChanged?.Invoke(TextKey.OperationStatus_StartClient);
@ -79,12 +76,12 @@ namespace SafeExamBrowser.Runtime.Operations
return success ? OperationResult.Success : OperationResult.Failed; return success ? OperationResult.Success : OperationResult.Failed;
} }
public virtual OperationResult Repeat() public override OperationResult Repeat()
{ {
return Perform(); return Perform();
} }
public virtual OperationResult Revert() public override OperationResult Revert()
{ {
var success = true; var success = true;
@ -103,10 +100,10 @@ namespace SafeExamBrowser.Runtime.Operations
var clientReadyEvent = new AutoResetEvent(false); var clientReadyEvent = new AutoResetEvent(false);
var clientReadyEventHandler = new CommunicationEventHandler(() => clientReadyEvent.Set()); var clientReadyEventHandler = new CommunicationEventHandler(() => clientReadyEvent.Set());
var clientExecutable = configuration.AppConfig.ClientExecutablePath; var clientExecutable = Context.Next.AppConfig.ClientExecutablePath;
var clientLogFile = $"{'"' + configuration.AppConfig.ClientLogFile + '"'}"; var clientLogFile = $"{'"' + Context.Next.AppConfig.ClientLogFile + '"'}";
var hostUri = configuration.AppConfig.RuntimeAddress; var hostUri = Context.Next.AppConfig.RuntimeAddress;
var token = configuration.CurrentSession.StartupToken.ToString("D"); var token = Context.Next.StartupToken.ToString("D");
logger.Info("Starting new client process..."); logger.Info("Starting new client process...");
runtimeHost.ClientReady += clientReadyEventHandler; 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..."); 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!"); logger.Error("Failed to connect to client!");

View file

@ -8,22 +8,21 @@
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Operations namespace SafeExamBrowser.Runtime.Operations
{ {
internal class ClientTerminationOperation : ClientOperation, IRepeatableOperation internal class ClientTerminationOperation : ClientOperation
{ {
public ClientTerminationOperation( public ClientTerminationOperation(
IConfigurationRepository configuration,
ILogger logger, ILogger logger,
IProcessFactory processFactory, IProcessFactory processFactory,
IProxyFactory proxyFactory, IProxyFactory proxyFactory,
IRuntimeHost runtimeHost, 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)
{ {
} }

View file

@ -19,32 +19,30 @@ using SafeExamBrowser.Runtime.Operations.Events;
namespace SafeExamBrowser.Runtime.Operations namespace SafeExamBrowser.Runtime.Operations
{ {
internal class ConfigurationOperation : IRepeatableOperation internal class ConfigurationOperation : SessionOperation
{ {
private string[] commandLineArgs;
private IConfigurationRepository configuration; private IConfigurationRepository configuration;
private ILogger logger; private ILogger logger;
private IResourceLoader resourceLoader; private IResourceLoader resourceLoader;
private AppConfig appConfig;
private string[] commandLineArgs;
public event ActionRequiredEventHandler ActionRequired; public override event ActionRequiredEventHandler ActionRequired;
public event StatusChangedEventHandler StatusChanged; public override event StatusChangedEventHandler StatusChanged;
public ConfigurationOperation( public ConfigurationOperation(
AppConfig appConfig, string[] commandLineArgs,
IConfigurationRepository configuration, IConfigurationRepository configuration,
ILogger logger, ILogger logger,
IResourceLoader resourceLoader, IResourceLoader resourceLoader,
string[] commandLineArgs) SessionContext sessionContext) : base(sessionContext)
{ {
this.appConfig = appConfig; this.commandLineArgs = commandLineArgs;
this.logger = logger; this.logger = logger;
this.configuration = configuration; this.configuration = configuration;
this.resourceLoader = resourceLoader; this.resourceLoader = resourceLoader;
this.commandLineArgs = commandLineArgs;
} }
public OperationResult Perform() public override OperationResult Perform()
{ {
logger.Info("Initializing application configuration..."); logger.Info("Initializing application configuration...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration);
@ -69,12 +67,12 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success; return OperationResult.Success;
} }
public OperationResult Repeat() public override OperationResult Repeat()
{ {
logger.Info("Initializing new application configuration..."); logger.Info("Initializing new application configuration...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration);
var isValidUri = TryValidateSettingsUri(configuration.ReconfigurationFilePath, out Uri uri); var isValidUri = TryValidateSettingsUri(Context.ReconfigurationFilePath, out Uri uri);
if (isValidUri) if (isValidUri)
{ {
@ -92,7 +90,7 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Failed; return OperationResult.Failed;
} }
public OperationResult Revert() public override OperationResult Revert()
{ {
return OperationResult.Success; return OperationResult.Success;
} }
@ -101,11 +99,12 @@ namespace SafeExamBrowser.Runtime.Operations
{ {
var adminPassword = default(string); var adminPassword = default(string);
var settingsPassword = default(string); var settingsPassword = default(string);
var settings = default(Settings);
var status = default(LoadStatus); var status = default(LoadStatus);
for (int adminAttempts = 0, settingsAttempts = 0; adminAttempts < 5 && settingsAttempts < 5;) 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) if (status == LoadStatus.AdminPasswordNeeded || status == LoadStatus.SettingsPasswordNeeded)
{ {
@ -132,7 +131,15 @@ namespace SafeExamBrowser.Runtime.Operations
HandleInvalidData(ref status, uri); 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) private PasswordRequiredEventArgs TryGetPassword(LoadStatus status)
@ -150,7 +157,7 @@ namespace SafeExamBrowser.Runtime.Operations
if (resourceLoader.IsHtmlResource(uri)) if (resourceLoader.IsHtmlResource(uri))
{ {
configuration.LoadDefaultSettings(); 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."); logger.Info($"The specified URI '{uri.AbsoluteUri}' appears to point to a HTML resource, setting it as startup URL.");
status = LoadStatus.Success; status = LoadStatus.Success;
@ -165,8 +172,8 @@ namespace SafeExamBrowser.Runtime.Operations
{ {
var path = string.Empty; var path = string.Empty;
var isValidUri = false; var isValidUri = false;
var programDataSettings = Path.Combine(appConfig.ProgramDataFolder, appConfig.DefaultSettingsFileName); var programDataSettings = Path.Combine(Context.Next.AppConfig.ProgramDataFolder, Context.Next.AppConfig.DefaultSettingsFileName);
var appDataSettings = Path.Combine(appConfig.AppDataFolder, appConfig.DefaultSettingsFileName); var appDataSettings = Path.Combine(Context.Next.AppConfig.AppDataFolder, Context.Next.AppConfig.DefaultSettingsFileName);
uri = null; uri = null;
@ -206,7 +213,7 @@ namespace SafeExamBrowser.Runtime.Operations
private void HandleClientConfiguration(ref OperationResult result) 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(); var args = new ConfigurationCompletedEventArgs();

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.Core.OperationModel.Events;
@ -16,52 +15,40 @@ using SafeExamBrowser.Contracts.WindowsApi;
namespace SafeExamBrowser.Runtime.Operations namespace SafeExamBrowser.Runtime.Operations
{ {
internal class KioskModeOperation : IRepeatableOperation internal class KioskModeOperation : SessionOperation
{ {
private IConfigurationRepository configuration; protected IDesktopFactory desktopFactory;
private IDesktopFactory desktopFactory; protected IExplorerShell explorerShell;
private IExplorerShell explorerShell; protected ILogger logger;
private KioskMode kioskMode; protected IProcessFactory processFactory;
private ILogger logger;
private IProcessFactory processFactory;
public event ActionRequiredEventHandler ActionRequired { add { } remove { } } private static IDesktop newDesktop;
public event StatusChangedEventHandler StatusChanged; private static IDesktop originalDesktop;
private IDesktop NewDesktop protected static KioskMode? ActiveMode { get; private set; }
{
get { return configuration.CurrentSession.NewDesktop; }
set { configuration.CurrentSession.NewDesktop = value; }
}
private IDesktop OriginalDesktop public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
{ public override event StatusChangedEventHandler StatusChanged;
get { return configuration.CurrentSession.OriginalDesktop; }
set { configuration.CurrentSession.OriginalDesktop = value; }
}
public KioskModeOperation( public KioskModeOperation(
IConfigurationRepository configuration,
IDesktopFactory desktopFactory, IDesktopFactory desktopFactory,
IExplorerShell explorerShell, IExplorerShell explorerShell,
ILogger logger, ILogger logger,
IProcessFactory processFactory) IProcessFactory processFactory,
SessionContext sessionContext) : base(sessionContext)
{ {
this.configuration = configuration;
this.desktopFactory = desktopFactory; this.desktopFactory = desktopFactory;
this.explorerShell = explorerShell; this.explorerShell = explorerShell;
this.logger = logger; this.logger = logger;
this.processFactory = processFactory; this.processFactory = processFactory;
} }
public virtual OperationResult Perform() public override OperationResult Perform()
{ {
kioskMode = configuration.CurrentSettings.KioskMode; logger.Info($"Initializing kiosk mode '{Context.Next.Settings.KioskMode}'...");
logger.Info($"Initializing kiosk mode '{kioskMode}'...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeKioskMode); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeKioskMode);
switch (kioskMode) switch (Context.Next.Settings.KioskMode)
{ {
case KioskMode.CreateNewDesktop: case KioskMode.CreateNewDesktop:
CreateNewDesktop(); CreateNewDesktop();
@ -71,17 +58,18 @@ namespace SafeExamBrowser.Runtime.Operations
break; break;
} }
ActiveMode = Context.Next.Settings.KioskMode;
return OperationResult.Success; return OperationResult.Success;
} }
public virtual OperationResult Repeat() public override OperationResult Repeat()
{ {
var oldMode = kioskMode; var newMode = Context.Next.Settings.KioskMode;
var newMode = configuration.CurrentSettings.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; return OperationResult.Success;
} }
@ -89,12 +77,12 @@ namespace SafeExamBrowser.Runtime.Operations
return Perform(); 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); StatusChanged?.Invoke(TextKey.OperationStatus_RevertKioskMode);
switch (kioskMode) switch (ActiveMode)
{ {
case KioskMode.CreateNewDesktop: case KioskMode.CreateNewDesktop:
CloseNewDesktop(); CloseNewDesktop();
@ -109,14 +97,14 @@ namespace SafeExamBrowser.Runtime.Operations
private void CreateNewDesktop() private void CreateNewDesktop()
{ {
OriginalDesktop = desktopFactory.GetCurrent(); originalDesktop = desktopFactory.GetCurrent();
logger.Info($"Current desktop is {OriginalDesktop}."); logger.Info($"Current desktop is {originalDesktop}.");
NewDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser)); newDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser));
logger.Info($"Created new desktop {NewDesktop}."); logger.Info($"Created new desktop {newDesktop}.");
NewDesktop.Activate(); newDesktop.Activate();
processFactory.StartupDesktop = NewDesktop; processFactory.StartupDesktop = newDesktop;
logger.Info("Successfully activated new desktop."); logger.Info("Successfully activated new desktop.");
explorerShell.Suspend(); explorerShell.Suspend();
@ -124,21 +112,21 @@ namespace SafeExamBrowser.Runtime.Operations
private void CloseNewDesktop() private void CloseNewDesktop()
{ {
if (OriginalDesktop != null) if (originalDesktop != null)
{ {
OriginalDesktop.Activate(); originalDesktop.Activate();
processFactory.StartupDesktop = OriginalDesktop; processFactory.StartupDesktop = originalDesktop;
logger.Info($"Switched back to original desktop {OriginalDesktop}."); logger.Info($"Switched back to original desktop {originalDesktop}.");
} }
else else
{ {
logger.Warn($"No original desktop found when attempting to close new desktop!"); logger.Warn($"No original desktop found when attempting to close new desktop!");
} }
if (NewDesktop != null) if (newDesktop != null)
{ {
NewDesktop.Close(); newDesktop.Close();
logger.Info($"Closed new desktop {NewDesktop}."); logger.Info($"Closed new desktop {newDesktop}.");
} }
else else
{ {
@ -151,6 +139,9 @@ namespace SafeExamBrowser.Runtime.Operations
private void TerminateExplorerShell() private void TerminateExplorerShell()
{ {
StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerTermination); StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerTermination);
// TODO: Hiding all windows must be done here, as the explorer shell is needed to do so!
explorerShell.Terminate(); explorerShell.Terminate();
} }
@ -158,6 +149,8 @@ namespace SafeExamBrowser.Runtime.Operations
{ {
StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerStartup); StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerStartup);
explorerShell.Start(); explorerShell.Start();
// TODO: Restore all hidden windows!
} }
} }
} }

View file

@ -6,8 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.Contracts.WindowsApi;
@ -16,36 +14,27 @@ namespace SafeExamBrowser.Runtime.Operations
{ {
internal class KioskModeTerminationOperation : KioskModeOperation, IRepeatableOperation internal class KioskModeTerminationOperation : KioskModeOperation, IRepeatableOperation
{ {
private IConfigurationRepository configuration;
private KioskMode kioskMode;
private ILogger logger;
public KioskModeTerminationOperation( public KioskModeTerminationOperation(
IConfigurationRepository configuration,
IDesktopFactory desktopFactory, IDesktopFactory desktopFactory,
IExplorerShell explorerShell, IExplorerShell explorerShell,
ILogger logger, 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() public override OperationResult Perform()
{ {
kioskMode = configuration.CurrentSettings.KioskMode;
return OperationResult.Success; return OperationResult.Success;
} }
public override OperationResult Repeat() public override OperationResult Repeat()
{ {
var oldMode = kioskMode; var newMode = Context.Next.Settings.KioskMode;
var newMode = configuration.CurrentSettings.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; return OperationResult.Success;
} }

View file

@ -7,7 +7,6 @@
*/ */
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.Core.OperationModel.Events;
@ -16,29 +15,27 @@ using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Runtime.Operations namespace SafeExamBrowser.Runtime.Operations
{ {
internal class ServiceOperation : IRepeatableOperation internal class ServiceOperation : SessionOperation
{ {
private bool connected, mandatory; private bool connected, mandatory;
private IConfigurationRepository configuration;
private ILogger logger; private ILogger logger;
private IServiceProxy service; private IServiceProxy service;
public event ActionRequiredEventHandler ActionRequired { add { } remove { } } public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public event StatusChangedEventHandler StatusChanged; 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.logger = logger;
this.service = service;
} }
public OperationResult Perform() public override OperationResult Perform()
{ {
logger.Info($"Initializing service session..."); logger.Info($"Initializing service session...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
mandatory = configuration.CurrentSettings.ServicePolicy == ServicePolicy.Mandatory; mandatory = Context.Next.Settings.ServicePolicy == ServicePolicy.Mandatory;
connected = service.Connect(); connected = service.Connect();
if (mandatory && !connected) if (mandatory && !connected)
@ -59,7 +56,7 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success; return OperationResult.Success;
} }
public OperationResult Repeat() public override OperationResult Repeat()
{ {
var result = Revert(); var result = Revert();
@ -71,7 +68,7 @@ namespace SafeExamBrowser.Runtime.Operations
return Perform(); return Perform();
} }
public OperationResult Revert() public override OperationResult Revert()
{ {
logger.Info("Finalizing service session..."); logger.Info("Finalizing service session...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession); StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession);
@ -97,12 +94,12 @@ namespace SafeExamBrowser.Runtime.Operations
private void StartServiceSession() private void StartServiceSession()
{ {
service.StartSession(configuration.CurrentSession.Id, configuration.CurrentSettings); service.StartSession(Context.Next.Id, Context.Next.Settings);
} }
private void StopServiceSession() private void StopServiceSession()
{ {
service.StopSession(configuration.CurrentSession.Id); service.StopSession(Context.Current.Id);
} }
} }
} }

View file

@ -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;
}
}
}

View file

@ -15,35 +15,41 @@ using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Runtime.Operations namespace SafeExamBrowser.Runtime.Operations
{ {
internal class SessionInitializationOperation : IRepeatableOperation internal class SessionInitializationOperation : SessionOperation
{ {
private IConfigurationRepository configuration; private IConfigurationRepository configuration;
private ILogger logger; private ILogger logger;
private IRuntimeHost runtimeHost; private IRuntimeHost runtimeHost;
public event ActionRequiredEventHandler ActionRequired { add { } remove { } } public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public event StatusChangedEventHandler StatusChanged; 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.configuration = configuration;
this.logger = logger; this.logger = logger;
this.runtimeHost = runtimeHost; this.runtimeHost = runtimeHost;
} }
public OperationResult Perform() public override OperationResult Perform()
{ {
InitializeSessionConfiguration(); InitializeSessionConfiguration();
return OperationResult.Success; 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; return OperationResult.Success;
} }
@ -53,12 +59,12 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Info("Initializing new session configuration..."); logger.Info("Initializing new session configuration...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSession); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSession);
configuration.InitializeSessionConfiguration(); Context.Next = configuration.InitializeSessionConfiguration();
runtimeHost.StartupToken = configuration.CurrentSession.StartupToken; runtimeHost.StartupToken = Context.Next.StartupToken;
logger.Info($" -> Client-ID: {configuration.AppConfig.ClientId}"); logger.Info($" -> Client-ID: {Context.Next.AppConfig.ClientId}");
logger.Info($" -> Runtime-ID: {configuration.AppConfig.RuntimeId}"); logger.Info($" -> Runtime-ID: {Context.Next.AppConfig.RuntimeId}");
logger.Info($" -> Session-ID: {configuration.CurrentSession.Id}"); logger.Info($" -> Session-ID: {Context.Next.Id}");
} }
} }
} }

View file

@ -0,0 +1,25 @@
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
namespace SafeExamBrowser.Runtime.Operations
{
/// <summary>
/// The base implementation to be used for all operations in the session operation sequence.
/// </summary>
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();
}
}

View file

@ -28,10 +28,7 @@ namespace SafeExamBrowser.Runtime
{ {
internal class RuntimeController : IRuntimeController internal class RuntimeController : IRuntimeController
{ {
private bool sessionRunning;
private AppConfig appConfig; private AppConfig appConfig;
private IConfigurationRepository configuration;
private ILogger logger; private ILogger logger;
private IMessageBox messageBox; private IMessageBox messageBox;
private IOperationSequence bootstrapSequence; private IOperationSequence bootstrapSequence;
@ -39,32 +36,43 @@ namespace SafeExamBrowser.Runtime
private IRuntimeHost runtimeHost; private IRuntimeHost runtimeHost;
private IRuntimeWindow runtimeWindow; private IRuntimeWindow runtimeWindow;
private IServiceProxy service; private IServiceProxy service;
private SessionContext sessionContext;
private ISplashScreen splashScreen; private ISplashScreen splashScreen;
private Action shutdown; private Action shutdown;
private IText text; private IText text;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
private ISessionConfiguration Session
{
get { return sessionContext.Current; }
}
private bool SessionIsRunning
{
get { return Session != null; }
}
public RuntimeController( public RuntimeController(
AppConfig appConfig, AppConfig appConfig,
IConfigurationRepository configuration,
ILogger logger, ILogger logger,
IMessageBox messageBox, IMessageBox messageBox,
IOperationSequence bootstrapSequence, IOperationSequence bootstrapSequence,
IRepeatableOperationSequence sessionSequence, IRepeatableOperationSequence sessionSequence,
IRuntimeHost runtimeHost, IRuntimeHost runtimeHost,
IServiceProxy service, IServiceProxy service,
SessionContext sessionContext,
Action shutdown, Action shutdown,
IText text, IText text,
IUserInterfaceFactory uiFactory) IUserInterfaceFactory uiFactory)
{ {
this.appConfig = appConfig; this.appConfig = appConfig;
this.configuration = configuration;
this.bootstrapSequence = bootstrapSequence; this.bootstrapSequence = bootstrapSequence;
this.logger = logger; this.logger = logger;
this.messageBox = messageBox; this.messageBox = messageBox;
this.runtimeHost = runtimeHost; this.runtimeHost = runtimeHost;
this.sessionSequence = sessionSequence; this.sessionSequence = sessionSequence;
this.service = service; this.service = service;
this.sessionContext = sessionContext;
this.shutdown = shutdown; this.shutdown = shutdown;
this.text = text; this.text = text;
this.uiFactory = uiFactory; this.uiFactory = uiFactory;
@ -96,7 +104,7 @@ namespace SafeExamBrowser.Runtime
logger.Subscribe(runtimeWindow); logger.Subscribe(runtimeWindow);
splashScreen.Close(); splashScreen.Close();
StartSession(true); StartSession();
} }
else else
{ {
@ -106,14 +114,14 @@ namespace SafeExamBrowser.Runtime
messageBox.Show(TextKey.MessageBox_StartupError, TextKey.MessageBox_StartupErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen); messageBox.Show(TextKey.MessageBox_StartupError, TextKey.MessageBox_StartupErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen);
} }
return initialized && sessionRunning; return initialized && SessionIsRunning;
} }
public void Terminate() public void Terminate()
{ {
DeregisterEvents(); DeregisterEvents();
if (sessionRunning) if (SessionIsRunning)
{ {
StopSession(); StopSession();
} }
@ -145,51 +153,79 @@ namespace SafeExamBrowser.Runtime
splashScreen.Close(); splashScreen.Close();
} }
private void StartSession(bool initial = false) private void StartSession()
{ {
runtimeWindow.Show(); runtimeWindow.Show();
runtimeWindow.BringToForeground(); runtimeWindow.BringToForeground();
runtimeWindow.ShowProgressBar(); runtimeWindow.ShowProgressBar();
logger.Info("### --- Session Start Procedure --- ###"); logger.Info("### --- Session Start Procedure --- ###");
if (sessionRunning) if (SessionIsRunning)
{ {
DeregisterSessionEvents(); DeregisterSessionEvents();
} }
var result = initial ? sessionSequence.TryPerform() : sessionSequence.TryRepeat(); var result = SessionIsRunning ? sessionSequence.TryRepeat() : sessionSequence.TryPerform();
if (result == OperationResult.Success) if (result == OperationResult.Success)
{ {
RegisterSessionEvents();
logger.Info("### --- Session Running --- ###"); 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.HideProgressBar();
runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning); 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(); 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) if (success)
{ {
logger.Info("### --- Session Terminated --- ###"); logger.Info("### --- Session Terminated --- ###");
sessionRunning = false;
} }
else else
{ {
@ -218,32 +253,34 @@ namespace SafeExamBrowser.Runtime
private void RegisterEvents() private void RegisterEvents()
{ {
runtimeHost.ClientConfigurationNeeded += RuntimeHost_ClientConfigurationNeeded;
runtimeHost.ReconfigurationRequested += RuntimeHost_ReconfigurationRequested; runtimeHost.ReconfigurationRequested += RuntimeHost_ReconfigurationRequested;
runtimeHost.ShutdownRequested += RuntimeHost_ShutdownRequested; runtimeHost.ShutdownRequested += RuntimeHost_ShutdownRequested;
} }
private void DeregisterEvents() private void DeregisterEvents()
{ {
runtimeHost.ClientConfigurationNeeded -= RuntimeHost_ClientConfigurationNeeded;
runtimeHost.ReconfigurationRequested -= RuntimeHost_ReconfigurationRequested; runtimeHost.ReconfigurationRequested -= RuntimeHost_ReconfigurationRequested;
runtimeHost.ShutdownRequested -= RuntimeHost_ShutdownRequested; runtimeHost.ShutdownRequested -= RuntimeHost_ShutdownRequested;
} }
private void RegisterSessionEvents() private void RegisterSessionEvents()
{ {
configuration.CurrentSession.ClientProcess.Terminated += ClientProcess_Terminated; sessionContext.ClientProcess.Terminated += ClientProcess_Terminated;
configuration.CurrentSession.ClientProxy.ConnectionLost += Client_ConnectionLost; sessionContext.ClientProxy.ConnectionLost += Client_ConnectionLost;
} }
private void DeregisterSessionEvents() 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}!"); logger.Error($"Client application has unexpectedly terminated with exit code {exitCode}!");
if (sessionRunning) if (SessionIsRunning)
{ {
StopSession(); StopSession();
} }
@ -275,7 +312,7 @@ namespace SafeExamBrowser.Runtime
{ {
logger.Error("Lost connection to the client application!"); logger.Error("Lost connection to the client application!");
if (sessionRunning) if (SessionIsRunning)
{ {
StopSession(); StopSession();
} }
@ -285,21 +322,31 @@ namespace SafeExamBrowser.Runtime
shutdown.Invoke(); 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) private void RuntimeHost_ReconfigurationRequested(ReconfigurationEventArgs args)
{ {
var mode = configuration.CurrentSettings.ConfigurationMode; var mode = Session.Settings.ConfigurationMode;
if (mode == ConfigurationMode.ConfigureClient) if (mode == ConfigurationMode.ConfigureClient)
{ {
logger.Info($"Accepted request for reconfiguration with '{args.ConfigurationPath}'."); logger.Info($"Accepted request for reconfiguration with '{args.ConfigurationPath}'.");
configuration.ReconfigurationFilePath = args.ConfigurationPath; sessionContext.ReconfigurationFilePath = args.ConfigurationPath;
StartSession(); StartSession();
} }
else else
{ {
logger.Info($"Denied request for reconfiguration with '{args.ConfigurationPath}' due to '{mode}' mode!"); 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) private void AskForPassword(PasswordRequiredEventArgs args)
{ {
var isStartup = configuration.CurrentSession == null; var isStartup = !SessionIsRunning;
var isRunningOnDefaultDesktop = configuration.CurrentSettings?.KioskMode == KioskMode.DisableExplorerShell; var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.KioskMode == KioskMode.DisableExplorerShell;
if (isStartup || isRunningOnDefaultDesktop) if (isStartup || isRunningOnDefaultDesktop)
{ {
@ -374,7 +421,7 @@ namespace SafeExamBrowser.Runtime
runtimeHost.PasswordReceived += responseEventHandler; runtimeHost.PasswordReceived += responseEventHandler;
var communication = configuration.CurrentSession.ClientProxy.RequestPassword(args.Purpose, requestId); var communication = sessionContext.ClientProxy.RequestPassword(args.Purpose, requestId);
if (communication.Success) if (communication.Success)
{ {
@ -401,6 +448,9 @@ namespace SafeExamBrowser.Runtime
runtimeWindow?.UpdateStatus(status, true); runtimeWindow?.UpdateStatus(status, true);
} }
/// <summary>
/// TODO: Move to utility in core library and use in client controller!
/// </summary>
private void MapProgress(IProgressIndicator progressIndicator, ProgressChangedEventArgs args) private void MapProgress(IProgressIndicator progressIndicator, ProgressChangedEventArgs args)
{ {
if (args.CurrentValue.HasValue) if (args.CurrentValue.HasValue)

View file

@ -94,6 +94,8 @@
<Compile Include="Operations\KioskModeOperation.cs" /> <Compile Include="Operations\KioskModeOperation.cs" />
<Compile Include="Operations\KioskModeTerminationOperation.cs" /> <Compile Include="Operations\KioskModeTerminationOperation.cs" />
<Compile Include="Operations\ServiceOperation.cs" /> <Compile Include="Operations\ServiceOperation.cs" />
<Compile Include="Operations\SessionActivationOperation.cs" />
<Compile Include="Operations\SessionOperation.cs" />
<Compile Include="Operations\SessionInitializationOperation.cs" /> <Compile Include="Operations\SessionInitializationOperation.cs" />
<Compile Include="Communication\ProxyFactory.cs" /> <Compile Include="Communication\ProxyFactory.cs" />
<Compile Include="Communication\RuntimeHost.cs" /> <Compile Include="Communication\RuntimeHost.cs" />
@ -112,6 +114,7 @@
<DesignTimeSharedInput>True</DesignTimeSharedInput> <DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile> </Compile>
<Compile Include="RuntimeController.cs" /> <Compile Include="RuntimeController.cs" />
<Compile Include="SessionContext.cs" />
<EmbeddedResource Include="Properties\Resources.resx"> <EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator> <Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput> <LastGenOutput>Resources.Designer.cs</LastGenOutput>

View file

@ -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
{
/// <summary>
/// Holds all configuration and runtime data required for the session handling.
/// </summary>
internal class SessionContext
{
/// <summary>
/// The currently running client process.
/// </summary>
public IProcess ClientProcess { get; set; }
/// <summary>
/// The communication proxy for the currently running client process.
/// </summary>
public IClientProxy ClientProxy { get; set; }
/// <summary>
/// The configuration of the currently active session.
/// </summary>
public ISessionConfiguration Current { get; set; }
/// <summary>
/// The configuration of the next session to be activated.
/// </summary>
public ISessionConfiguration Next { get; set; }
/// <summary>
/// The path of the configuration file to be used for reconfiguration.
/// </summary>
public string ReconfigurationFilePath { get; set; }
}
}