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)
{
Logger.Debug($"Skipping {operationName} because the ignore flag is set.");
Logger.Debug($"Skipping '{operationName}' operation because the ignore flag is set.");
}
return Ignore;

View file

@ -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()}";
}
}
}

View file

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

View file

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

View file

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

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>
event CommunicationEventHandler ClientReady;
/// <summary>
/// Event fired when the client requested its configuration data.
/// </summary>
event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded;
/// <summary>
/// Event fired when the client submitted a password entered by the user.
/// </summary>

View file

@ -16,46 +16,25 @@ namespace SafeExamBrowser.Contracts.Configuration
public interface IConfigurationRepository
{
/// <summary>
/// The global configuration information for the currently running application instance.
/// Initializes the global configuration information for the currently running application instance.
/// </summary>
AppConfig AppConfig { get; }
AppConfig InitializeAppConfig();
/// <summary>
/// Retrieves the current session data, i.e. the last one which was initialized. If no session has been initialized yet, this
/// property will be <c>null</c>!
/// Initializes all relevant configuration data for a new session.
/// </summary>
ISessionData CurrentSession { get; }
/// <summary>
/// Retrieves the current settings, i.e. the last ones which were loaded. If no settings have been loaded yet, this property will
/// 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();
ISessionConfiguration InitializeSessionConfiguration();
/// <summary>
/// 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>
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>
/// Loads the default settings.
/// </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" />
</ItemGroup>
<ItemGroup>
<Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" />
<Compile Include="Core\Events\InstanceTerminatedEventHandler.cs" />
<Compile Include="Core\Events\NameChangedEventHandler.cs" />
<Compile Include="Core\IApplicationController.cs" />
@ -109,7 +110,7 @@
<Compile Include="Configuration\IResourceLoader.cs" />
<Compile Include="Configuration\LoadStatus.cs" />
<Compile Include="Configuration\AppConfig.cs" />
<Compile Include="Configuration\ISessionData.cs" />
<Compile Include="Configuration\ISessionConfiguration.cs" />
<Compile Include="Configuration\Settings\ConfigurationMode.cs" />
<Compile Include="Core\INotificationController.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.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<ClientConfigurationEventArgs> ClientConfigurationNeeded;
public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived;
public event CommunicationEventHandler<ReconfigurationEventArgs> 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 };
}
}
}

View file

@ -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<IOperation>();
@ -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<AssemblyCopyrightAttribute>().Copyright;

View file

@ -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!");

View file

@ -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)
{
}

View file

@ -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();

View file

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

View file

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

View file

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

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
{
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}");
}
}
}

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
{
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);
}
/// <summary>
/// TODO: Move to utility in core library and use in client controller!
/// </summary>
private void MapProgress(IProgressIndicator progressIndicator, ProgressChangedEventArgs args)
{
if (args.CurrentValue.HasValue)

View file

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