SEBWIN-220: Scaffolding for new (re-)configuration procedure.

This commit is contained in:
dbuechel 2018-06-21 07:56:25 +02:00
parent 45dd741e4c
commit 50dcb7502a
54 changed files with 710 additions and 256 deletions

14
.editorconfig Normal file
View file

@ -0,0 +1,14 @@
# Defines coding style deviations from the Microsoft Minimum Recommended Rules ruleset, which is active per default in Visual Studio 2017
# For more info, see https://editorconfig.org/ and https://docs.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options
root = true
[*]
end_of_line = crlf
[*.cs]
dotnet_style_object_initializer = false:none
indent_style = tab
[*.xml]
indent_style = space

View file

@ -8,12 +8,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using CefSharp;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
@ -24,30 +22,30 @@ using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.Browser
namespace SafeExamBrowser.Browser
{
public class BrowserApplicationController : IApplicationController
public class BrowserApplicationController : IBrowserApplicationController
{
private IApplicationButton button;
private IList<IApplicationInstance> instances = new List<IApplicationInstance>();
private BrowserSettings settings;
private IList<IApplicationInstance> instances;
private ILogger logger;
private IMessageBox messageBox;
private IRuntimeProxy runtime;
private RuntimeInfo runtimeInfo;
private IUserInterfaceFactory uiFactory;
private BrowserSettings settings;
private IText text;
private IUserInterfaceFactory uiFactory;
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public BrowserApplicationController(
BrowserSettings settings,
RuntimeInfo runtimeInfo,
ILogger logger,
IMessageBox messageBox,
IRuntimeProxy runtime,
IText text,
IUserInterfaceFactory uiFactory)
{
this.instances = new List<IApplicationInstance>();
this.logger = logger;
this.messageBox = messageBox;
this.runtime = runtime;
this.runtimeInfo = runtimeInfo;
this.settings = settings;
this.text = text;
@ -84,15 +82,14 @@ namespace SafeExamBrowser.Browser
private void CreateNewInstance()
{
var instance = new BrowserApplicationInstance(settings, text, uiFactory, instances.Count == 0);
var instance = new BrowserApplicationInstance(settings, runtimeInfo, text, uiFactory, instances.Count == 0);
instance.Initialize();
instance.ConfigurationDetected += Instance_ConfigurationDetected;
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.Terminated += Instance_Terminated;
button.RegisterInstance(instance);
instances.Add(instance);
instance.Window.Show();
}
@ -124,36 +121,6 @@ namespace SafeExamBrowser.Browser
}
}
private void Instance_ConfigurationDetected(string url, CancelEventArgs args)
{
var result = messageBox.Show(TextKey.MessageBox_ReconfigurationQuestion, TextKey.MessageBox_ReconfigurationQuestionTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question);
var reconfigure = result == MessageBoxResult.Yes;
var allowed = false;
logger.Info($"Detected configuration request for '{url}'. The user chose to {(reconfigure ? "start" : "abort")} the reconfiguration.");
if (reconfigure)
{
try
{
allowed = runtime.RequestReconfiguration(url);
logger.Info($"The runtime {(allowed ? "accepted" : "denied")} the reconfiguration request.");
if (!allowed)
{
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle);
}
}
catch (Exception e)
{
logger.Error("Failed to communicate the reconfiguration request to the runtime!", e);
messageBox.Show(TextKey.MessageBox_ReconfigurationError, TextKey.MessageBox_ReconfigurationErrorTitle, icon: MessageBoxIcon.Error);
}
}
args.Cancel = !allowed;
}
private void Instance_Terminated(Guid id)
{
instances.Remove(instances.FirstOrDefault(i => i.Id == id));

View file

@ -8,6 +8,7 @@
using System;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n;
@ -22,6 +23,7 @@ namespace SafeExamBrowser.Browser
private IBrowserControl control;
private IBrowserWindow window;
private bool isMainInstance;
private RuntimeInfo runtimeInfo;
private BrowserSettings settings;
private IText text;
private IUserInterfaceFactory uiFactory;
@ -30,13 +32,19 @@ namespace SafeExamBrowser.Browser
public string Name { get; private set; }
public IWindow Window { get { return window; } }
internal event ConfigurationDetectedEventHandler ConfigurationDetected;
public event TerminatedEventHandler Terminated;
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event NameChangedEventHandler NameChanged;
public event TerminatedEventHandler Terminated;
public BrowserApplicationInstance(BrowserSettings settings, IText text, IUserInterfaceFactory uiFactory, bool isMainInstance)
public BrowserApplicationInstance(
BrowserSettings settings,
RuntimeInfo runtimeInfo,
IText text,
IUserInterfaceFactory uiFactory,
bool isMainInstance)
{
this.isMainInstance = isMainInstance;
this.runtimeInfo = runtimeInfo;
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
@ -44,13 +52,17 @@ namespace SafeExamBrowser.Browser
internal void Initialize()
{
var downloadHandler = new DownloadHandler(settings, runtimeInfo);
Id = Guid.NewGuid();
downloadHandler.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
control = new BrowserControl(settings, text);
control.AddressChanged += Control_AddressChanged;
(control as BrowserControl).ConfigurationDetected += (url, args) => ConfigurationDetected?.Invoke(url, args);
control.LoadingStateChanged += Control_LoadingStateChanged;
control.TitleChanged += Control_TitleChanged;
(control as BrowserControl).DownloadHandler = downloadHandler;
(control as BrowserControl).Initialize();
window = uiFactory.CreateBrowserWindow(control, settings);
window.IsMainWindow = isMainInstance;

View file

@ -24,8 +24,6 @@ namespace SafeExamBrowser.Browser
private LoadingStateChangedEventHandler loadingStateChanged;
private TitleChangedEventHandler titleChanged;
internal event ConfigurationDetectedEventHandler ConfigurationDetected;
event AddressChangedEventHandler IBrowserControl.AddressChanged
{
add { addressChanged += value; }
@ -48,8 +46,17 @@ namespace SafeExamBrowser.Browser
{
this.settings = settings;
this.text = text;
}
Initialize();
public void Initialize()
{
AddressChanged += (o, args) => addressChanged?.Invoke(args.Address);
LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading);
TitleChanged += (o, args) => titleChanged?.Invoke(args.Title);
KeyboardHandler = new KeyboardHandler(settings);
MenuHandler = new ContextMenuHandler(settings, text);
RequestHandler = new RequestHandler();
}
public void NavigateBackwards()
@ -74,19 +81,5 @@ namespace SafeExamBrowser.Browser
{
GetBrowser().Reload();
}
private void Initialize()
{
var requestHandler = new RequestHandler();
AddressChanged += (o, args) => addressChanged?.Invoke(args.Address);
LoadingStateChanged += (o, args) => loadingStateChanged?.Invoke(args.IsLoading);
TitleChanged += (o, args) => titleChanged?.Invoke(args.Title);
requestHandler.ConfigurationDetected += (url, args) => ConfigurationDetected?.Invoke(url, args);
KeyboardHandler = new KeyboardHandler(settings);
MenuHandler = new ContextMenuHandler(settings, text);
RequestHandler = requestHandler;
}
}
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
using CefSharp;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Configuration;
using BrowserSettings = SafeExamBrowser.Contracts.Configuration.Settings.BrowserSettings;
namespace SafeExamBrowser.Browser.Handlers
{
/// <remarks>
/// See https://cefsharp.github.io/api/63.0.0/html/T_CefSharp_IDownloadHandler.htm.
/// </remarks>
internal class DownloadHandler : IDownloadHandler
{
private BrowserSettings settings;
private RuntimeInfo runtimeInfo;
private ConcurrentDictionary<int, DownloadFinishedCallback> callbacks;
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public DownloadHandler(BrowserSettings settings, RuntimeInfo runtimeInfo)
{
this.callbacks = new ConcurrentDictionary<int, DownloadFinishedCallback>();
this.settings = settings;
this.runtimeInfo = runtimeInfo;
}
public void OnBeforeDownload(IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
{
var uri = new Uri(downloadItem.Url);
var extension = Path.GetExtension(uri.AbsolutePath);
var isConfigFile = String.Equals(extension, runtimeInfo.ConfigurationFileExtension, StringComparison.InvariantCultureIgnoreCase);
if (isConfigFile)
{
Task.Run(() => RequestConfigurationFileDownload(downloadItem, callback));
}
else if (!isConfigFile && settings.AllowDownloads)
{
using (callback)
{
callback.Continue(null, true);
}
}
}
public void OnDownloadUpdated(IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
{
if (downloadItem.IsComplete || downloadItem.IsCancelled)
{
if (callbacks.TryRemove(downloadItem.Id, out DownloadFinishedCallback finished) && finished != null)
{
Task.Run(() => finished.Invoke(downloadItem.IsComplete, downloadItem.FullPath));
}
}
}
private void RequestConfigurationFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
{
var args = new DownloadEventArgs();
ConfigurationDownloadRequested?.Invoke(downloadItem.SuggestedFileName, args);
if (args.AllowDownload)
{
if (args.Callback != null)
{
callbacks[downloadItem.Id] = args.Callback;
}
using (callback)
{
callback.Continue(args.DownloadPath, false);
}
}
}
}
}

View file

@ -7,46 +7,31 @@
*/
using System;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.Handler;
namespace SafeExamBrowser.Browser.Handlers
{
internal delegate void ConfigurationDetectedEventHandler(string url, CancelEventArgs args);
/// <remarks>
/// See https://cefsharp.github.io/api/63.0.0/html/T_CefSharp_Handler_DefaultRequestHandler.htm.
/// </remarks>
internal class RequestHandler : DefaultRequestHandler
{
internal event ConfigurationDetectedEventHandler ConfigurationDetected;
public override CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
{
Task.Run(() =>
var uri = new Uri(request.Url);
// TODO: Move to globals -> SafeExamBrowserUriScheme, SafeExamBrowserSecureUriScheme
if (uri.Scheme == "seb")
{
var allow = true;
var uri = new Uri(request.Url);
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.ToString();
}
else if (uri.Scheme == "sebs")
{
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.ToString();
}
if (uri.Scheme == "seb" || uri.Scheme == "sebs" || Path.HasExtension("seb"))
{
var args = new CancelEventArgs();
ConfigurationDetected?.Invoke(request.Url, args);
allow = !args.Cancel;
}
using (callback)
{
callback.Continue(allow);
}
});
return CefReturnValue.ContinueAsync;
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
}
}
}

View file

@ -51,7 +51,10 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@ -67,6 +70,7 @@
<SubType>Component</SubType>
</Compile>
<Compile Include="BrowserIconResource.cs" />
<Compile Include="Handlers\DownloadHandler.cs" />
<Compile Include="Handlers\KeyboardHandler.cs" />
<Compile Include="Handlers\RequestHandler.cs" />
<Compile Include="Handlers\SchemeHandlerFactory.cs" />

View file

@ -53,7 +53,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">

View file

@ -7,8 +7,10 @@
*/
using System;
using System.IO;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
@ -38,6 +40,7 @@ namespace SafeExamBrowser.Client.Behaviour
private IWindowMonitor windowMonitor;
private RuntimeInfo runtimeInfo;
public IBrowserApplicationController Browser { private get; set; }
public IClientHost ClientHost { private get; set; }
public Guid SessionId { private get; set; }
public Settings Settings { private get; set; }
@ -145,6 +148,7 @@ namespace SafeExamBrowser.Client.Behaviour
private void RegisterEvents()
{
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
ClientHost.Shutdown += ClientHost_Shutdown;
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted;
@ -155,6 +159,7 @@ namespace SafeExamBrowser.Client.Behaviour
private void DeregisterEvents()
{
Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested;
ClientHost.Shutdown -= ClientHost_Shutdown;
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted;
@ -183,6 +188,53 @@ namespace SafeExamBrowser.Client.Behaviour
logger.Info("Desktop successfully restored.");
}
private void Browser_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
{
if (Settings.ConfigurationMode == ConfigurationMode.ConfigureClient)
{
logger.Info($"Detected download request for configuration file '{fileName}'.");
var result = messageBox.Show(TextKey.MessageBox_ReconfigurationQuestion, TextKey.MessageBox_ReconfigurationQuestionTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question);
var reconfigure = result == MessageBoxResult.Yes;
logger.Info($"The user chose to {(reconfigure ? "start" : "abort")} the reconfiguration.");
if (reconfigure)
{
args.AllowDownload = true;
args.Callback = Browser_ConfigurationDownloadFinished;
args.DownloadPath = Path.Combine(runtimeInfo.DownloadDirectory, fileName);
}
}
else
{
logger.Info($"Denied download request for configuration file '{fileName}' due to '{Settings.ConfigurationMode}' mode.");
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle);
}
}
private void Browser_ConfigurationDownloadFinished(bool success, string filePath = null)
{
if (success)
{
try
{
runtime.RequestReconfiguration(filePath);
logger.Info($"Sent reconfiguration request for '{filePath}' to the runtime.");
}
catch (Exception e)
{
logger.Error($"Failed to communicate reconfiguration request for '{filePath}'!", e);
messageBox.Show(TextKey.MessageBox_ReconfigurationError, TextKey.MessageBox_ReconfigurationErrorTitle, icon: MessageBoxIcon.Error);
}
}
else
{
logger.Error($"Failed to download configuration file '{filePath}'!");
messageBox.Show(TextKey.MessageBox_ConfigurationDownloadError, TextKey.MessageBox_ConfigurationDownloadErrorTitle, icon: MessageBoxIcon.Error);
}
}
private void ClientHost_Shutdown()
{
taskbar.Close();

View file

@ -7,8 +7,8 @@
*/
using System;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Hosts;

View file

@ -17,6 +17,7 @@ using SafeExamBrowser.Client.Notifications;
using SafeExamBrowser.Configuration;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
@ -47,6 +48,7 @@ namespace SafeExamBrowser.Client
private string runtimeHostUri;
private Guid startupToken;
private IBrowserApplicationController browserController;
private ClientConfiguration configuration;
private IClientHost clientHost;
private ILogger logger;
@ -88,7 +90,6 @@ namespace SafeExamBrowser.Client
operations.Enqueue(new RuntimeConnectionOperation(logger, runtimeProxy, startupToken));
operations.Enqueue(new ConfigurationOperation(configuration, logger, runtimeProxy));
operations.Enqueue(new DelayedInitializationOperation(BuildCommunicationHostOperation));
operations.Enqueue(new DelegateOperation(UpdateClientControllerDependencies));
// TODO
//operations.Enqueue(new DelayedInitializationOperation(BuildKeyboardInterceptorOperation));
//operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
@ -98,6 +99,7 @@ namespace SafeExamBrowser.Client
operations.Enqueue(new DelayedInitializationOperation(BuildBrowserOperation));
operations.Enqueue(new ClipboardOperation(logger, nativeMethods));
//operations.Enqueue(new DelayedInitializationOperation(BuildMouseInterceptorOperation));
operations.Enqueue(new DelegateOperation(UpdateClientControllerDependencies));
var sequence = new OperationSequence(logger, operations);
@ -150,10 +152,12 @@ namespace SafeExamBrowser.Client
private IOperation BuildBrowserOperation()
{
var moduleLogger = new ModuleLogger(logger, typeof(BrowserApplicationController));
var browserController = new BrowserApplicationController(configuration.Settings.Browser, configuration.RuntimeInfo, moduleLogger, messageBox, runtimeProxy, text, uiFactory);
var browserController = new BrowserApplicationController(configuration.Settings.Browser, configuration.RuntimeInfo, moduleLogger, messageBox, text, uiFactory);
var browserInfo = new BrowserApplicationInfo();
var operation = new BrowserOperation(browserController, browserInfo, logger, Taskbar, uiFactory);
this.browserController = browserController;
return operation;
}
@ -200,6 +204,7 @@ namespace SafeExamBrowser.Client
private void UpdateClientControllerDependencies()
{
ClientController.Browser = browserController;
ClientController.ClientHost = clientHost;
ClientController.RuntimeInfo = configuration.RuntimeInfo;
ClientController.SessionId = configuration.SessionId;

View file

@ -44,8 +44,8 @@
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
@ -54,7 +54,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>

View file

@ -23,7 +23,7 @@ namespace SafeExamBrowser.Configuration
public ISessionData CurrentSession { get; private set; }
public Settings CurrentSettings { get; private set; }
public string ReconfigurationUrl { get; set; }
public string ReconfigurationFilePath { get; set; }
public RuntimeInfo RuntimeInfo
{
@ -66,35 +66,34 @@ namespace SafeExamBrowser.Configuration
}
}
public Settings LoadSettings(Uri path)
public LoadStatus LoadSettings(Uri resource, string settingsPassword = null, string adminPassword = null)
{
// TODO: Implement loading mechanism
return LoadDefaultSettings();
LoadDefaultSettings();
return LoadStatus.Success;
}
public Settings LoadDefaultSettings()
public void LoadDefaultSettings()
{
var settings = new Settings()
{
// TODO: Implement default settings
ServicePolicy = ServicePolicy.Optional
};
// TODO: Implement default settings
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;
CurrentSettings = new Settings();
settings.Taskbar.AllowApplicationLog = true;
settings.Taskbar.AllowKeyboardLayout = true;
settings.Taskbar.AllowWirelessNetwork = true;
CurrentSettings.ServicePolicy = ServicePolicy.Optional;
CurrentSettings = settings;
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;
return settings;
CurrentSettings.Taskbar.AllowApplicationLog = true;
CurrentSettings.Taskbar.AllowKeyboardLayout = true;
CurrentSettings.Taskbar.AllowWirelessNetwork = true;
}
private void InitializeRuntimeInfo()
@ -105,26 +104,26 @@ namespace SafeExamBrowser.Configuration
var logFolder = Path.Combine(appDataFolder, "Logs");
var logFilePrefix = startTime.ToString("yyyy-MM-dd\\_HH\\hmm\\mss\\s");
runtimeInfo = new RuntimeInfo
{
ApplicationStartTime = startTime,
AppDataFolder = appDataFolder,
BrowserCachePath = Path.Combine(appDataFolder, "Cache"),
BrowserLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Browser.txt"),
ClientId = Guid.NewGuid(),
ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}",
ClientExecutablePath = Path.Combine(Path.GetDirectoryName(executable.Location), $"{nameof(SafeExamBrowser)}.Client.exe"),
ClientLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Client.txt"),
DefaultSettingsFileName = "SebClientSettings.seb",
ProgramCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright,
ProgramDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), nameof(SafeExamBrowser)),
ProgramTitle = executable.GetCustomAttribute<AssemblyTitleAttribute>().Title,
ProgramVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion,
RuntimeId = Guid.NewGuid(),
RuntimeAddress = $"{BASE_ADDRESS}/runtime/{Guid.NewGuid()}",
RuntimeLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.txt"),
ServiceAddress = $"{BASE_ADDRESS}/service"
};
runtimeInfo = new RuntimeInfo();
runtimeInfo.ApplicationStartTime = startTime;
runtimeInfo.AppDataFolder = appDataFolder;
runtimeInfo.BrowserCachePath = Path.Combine(appDataFolder, "Cache");
runtimeInfo.BrowserLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Browser.txt");
runtimeInfo.ClientId = Guid.NewGuid();
runtimeInfo.ClientAddress = $"{BASE_ADDRESS}/client/{Guid.NewGuid()}";
runtimeInfo.ClientExecutablePath = Path.Combine(Path.GetDirectoryName(executable.Location), $"{nameof(SafeExamBrowser)}.Client.exe");
runtimeInfo.ClientLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Client.txt");
runtimeInfo.ConfigurationFileExtension = ".seb";
runtimeInfo.DefaultSettingsFileName = "SebClientSettings.seb";
runtimeInfo.DownloadDirectory = Path.Combine(appDataFolder, "Downloads");
runtimeInfo.ProgramCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright;
runtimeInfo.ProgramDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), nameof(SafeExamBrowser));
runtimeInfo.ProgramTitle = executable.GetCustomAttribute<AssemblyTitleAttribute>().Title;
runtimeInfo.ProgramVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
runtimeInfo.RuntimeId = Guid.NewGuid();
runtimeInfo.RuntimeAddress = $"{BASE_ADDRESS}/runtime/{Guid.NewGuid()}";
runtimeInfo.RuntimeLogFile = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.txt");
runtimeInfo.ServiceAddress = $"{BASE_ADDRESS}/service";
}
private void UpdateRuntimeInfo()

View file

@ -45,7 +45,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -7,6 +7,7 @@
*/
using System;
using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
@ -18,6 +19,11 @@ namespace SafeExamBrowser.Contracts.Behaviour
/// </summary>
public interface IClientController
{
/// <summary>
/// The controller for the browser application.
/// </summary>
IBrowserApplicationController Browser { set; }
/// <summary>
/// The client host used for communication handling.
/// </summary>

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Browser
{
/// <summary>
/// TODO
/// </summary>
public class DownloadEventArgs
{
/// <summary>
/// Determines whether the specified download is allowed.
/// </summary>
public bool AllowDownload { get; set; }
/// <summary>
/// Callback executed once a download has been finished.
/// </summary>
public DownloadFinishedCallback Callback { get; set; }
/// <summary>
/// The full path under which the specified file should be saved.
/// </summary>
public string DownloadPath { get; set; }
}
}

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Browser
{
public delegate void DownloadFinishedCallback(bool success, string filePath = null);
}

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Browser
{
public delegate void DownloadRequestedEventHandler(string fileName, DownloadEventArgs args);
}

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.Behaviour;
namespace SafeExamBrowser.Contracts.Browser
{
/// <summary>
/// Controls the lifetime and functionality of the browser application.
/// </summary>
public interface IBrowserApplicationController : IApplicationController
{
/// <summary>
/// Event fired when the browser application detects a download request for an application configuration file.
/// </summary>
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
}
}

View file

@ -17,13 +17,13 @@ namespace SafeExamBrowser.Contracts.Communication.Data
public class ReconfigurationMessage : Message
{
/// <summary>
/// The locator of the new configuration to be used.
/// The full path of the configuration file to be used.
/// </summary>
public string ConfigurationUrl { get; private set; }
public string ConfigurationPath { get; private set; }
public ReconfigurationMessage(string url)
public ReconfigurationMessage(string path)
{
ConfigurationUrl = url;
ConfigurationPath = path;
}
}
}

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Communication.Events
{
/// <summary>
/// Base class which must be used for all event parameters <c>T</c> of <see cref="CommunicationEventHandler{T}"/>.
/// </summary>
public abstract class CommunicationEventArgs
{
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.Threading.Tasks;
namespace SafeExamBrowser.Contracts.Communication.Events
{
/// <summary>
/// The default handler for communication events of an interlocutor.
/// </summary>
public delegate void CommunicationEventHandler();
/// <summary>
/// The handler with parameter for communication events of an interlocutor.
/// </summary>
public delegate void CommunicationEventHandler<T>(T args) where T : CommunicationEventArgs;
public static class CommunicationEventHandlerExtensions
{
/// <summary>
/// Executes the event handler asynchronously, i.e. on a separate thread.
/// </summary>
public static async Task InvokeAsync(this CommunicationEventHandler handler)
{
await Task.Run(() => handler?.Invoke());
}
/// <summary>
/// Executes the event handler asynchronously, i.e. on a separate thread.
/// </summary>
public static async Task InvokeAsync<T>(this CommunicationEventHandler<T> handler, T args) where T : CommunicationEventArgs
{
await Task.Run(() => handler?.Invoke(args));
}
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Communication.Events
{
/// <summary>
/// The event arguments used for the reconfiguration event fired by the <see cref="Hosts.IRuntimeHost"/>.
/// </summary>
public class ReconfigurationEventArgs : CommunicationEventArgs
{
/// <summary>
/// The full path to the configuration file to be used for reconfiguration.
/// </summary>
public string ConfigurationPath { get; set; }
}
}

View file

@ -7,6 +7,7 @@
*/
using System;
using SafeExamBrowser.Contracts.Communication.Events;
namespace SafeExamBrowser.Contracts.Communication.Hosts
{

View file

@ -7,6 +7,7 @@
*/
using System;
using SafeExamBrowser.Contracts.Communication.Events;
namespace SafeExamBrowser.Contracts.Communication.Hosts
{
@ -31,9 +32,9 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
event CommunicationEventHandler ClientReady;
/// <summary>
/// Event fired when the client detected a reconfiguration request.
/// Event fired when the client requested a reconfiguration of the application.
/// </summary>
event CommunicationEventHandler ReconfigurationRequested;
event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
/// <summary>
/// Event fired when the client requests to shut down the application.

View file

@ -8,8 +8,6 @@
namespace SafeExamBrowser.Contracts.Communication
{
public delegate void CommunicationEventHandler();
/// <summary>
/// Defines the common functionality for all communication hosts. A communication host can be hosted by an application component to
/// allow for inter-process communication with other components (e.g. runtime -> client communication).

View file

@ -7,6 +7,7 @@
*/
using System;
using SafeExamBrowser.Contracts.Communication.Events;
namespace SafeExamBrowser.Contracts.Communication
{

View file

@ -34,10 +34,9 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
void RequestShutdown();
/// <summary>
/// Requests the runtime to reconfigure the application with the configuration from the given location. Returns <c>true</c> if
/// the runtime accepted the request, otherwise <c>false</c>.
/// Requests the runtime to reconfigure the application with the specified configuration.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
bool RequestReconfiguration(string url);
void RequestReconfiguration(string filePath);
}
}

View file

@ -28,9 +28,9 @@ namespace SafeExamBrowser.Contracts.Configuration
Settings.Settings CurrentSettings { get; }
/// <summary>
/// The locator of the configuration to be used when reconfiguring the application.
/// The path of the settings file to be used when reconfiguring the application.
/// </summary>
string ReconfigurationUrl { get; set; }
string ReconfigurationFilePath { get; set; }
/// <summary>
/// The runtime information for the currently running application instance.
@ -48,14 +48,14 @@ namespace SafeExamBrowser.Contracts.Configuration
void InitializeSessionConfiguration();
/// <summary>
/// Attempts to load settings from the specified path.
/// Attempts to load settings from the specified resource, using the optional passwords. Returns a <see cref="LoadStatus"/>
/// indicating the result of the operation.
/// </summary>
/// <exception cref="ArgumentException">Thrown if the given path cannot be resolved to a settings file.</exception>
Settings.Settings LoadSettings(Uri path);
LoadStatus LoadSettings(Uri resource, string settingsPassword = null, string adminPassword = null);
/// <summary>
/// Loads the default settings.
/// </summary>
Settings.Settings LoadDefaultSettings();
void LoadDefaultSettings();
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Configuration
{
/// <summary>
/// Defines all possible results of <see cref="IConfigurationRepository.LoadSettings(System.Uri)"/>.
/// </summary>
public enum LoadStatus
{
/// <summary>
/// Indicates that an admin password is needed in order to load the settings.
/// </summary>
AdminPasswordNeeded = 1,
/// <summary>
/// Indicates that a resource does not comply with the required data format.
/// </summary>
InvalidData,
/// <summary>
/// Indicates that a settings password is needed in order to load the settings.
/// </summary>
SettingsPasswordNeeded,
/// <summary>
/// The <see cref="Settings.Settings"/> were loaded successfully.
/// </summary>
Success
}
}

View file

@ -12,6 +12,7 @@ namespace SafeExamBrowser.Contracts.Configuration
{
/// <summary>
/// Defines the fundamental, global configuration information for all application components.
/// TODO: Rename to Globals or GlobalConfiguration or alike!
/// </summary>
[Serializable]
public class RuntimeInfo
@ -56,11 +57,21 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary>
public string ClientLogFile { get; set; }
/// <summary>
/// The file extension of configuration files for the application (including the period).
/// </summary>
public string ConfigurationFileExtension { get; set; }
/// <summary>
/// The default file name for application settings.
/// </summary>
public string DefaultSettingsFileName { get; set; }
/// <summary>
/// The default directory for file downloads.
/// </summary>
public string DownloadDirectory { get; set; }
/// <summary>
/// The copyright information for the application (i.e. the executing assembly).
/// </summary>

View file

@ -31,6 +31,11 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
/// </summary>
public bool AllowDeveloperConsole { get; set; }
/// <summary>
/// Determines whether the user should be allowed to download files.
/// </summary>
public bool AllowDownloads { get; set; }
/// <summary>
/// Determines whether the user should be allowed to navigate forwards in a browser window.
/// </summary>

View file

@ -20,6 +20,8 @@ namespace SafeExamBrowser.Contracts.I18n
MessageBox_ApplicationErrorTitle,
MessageBox_ClientConfigurationQuestion,
MessageBox_ClientConfigurationQuestionTitle,
MessageBox_ConfigurationDownloadError,
MessageBox_ConfigurationDownloadErrorTitle,
MessageBox_Quit,
MessageBox_QuitTitle,
MessageBox_QuitError,

View file

@ -45,7 +45,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
@ -57,6 +56,13 @@
<Compile Include="Behaviour\IRuntimeController.cs" />
<Compile Include="Behaviour\OperationModel\IOperationSequence.cs" />
<Compile Include="Behaviour\OperationModel\OperationResult.cs" />
<Compile Include="Browser\DownloadEventArgs.cs" />
<Compile Include="Browser\DownloadFinishedCallback.cs" />
<Compile Include="Browser\DownloadRequestedEventHandler.cs" />
<Compile Include="Browser\IBrowserApplicationController.cs" />
<Compile Include="Communication\Events\CommunicationEventArgs.cs" />
<Compile Include="Communication\Events\CommunicationEventHandler.cs" />
<Compile Include="Communication\Events\ReconfigurationEventArgs.cs" />
<Compile Include="Communication\Hosts\IClientHost.cs" />
<Compile Include="Communication\Hosts\IHostObject.cs" />
<Compile Include="Communication\Hosts\IHostObjectFactory.cs" />
@ -84,6 +90,7 @@
<Compile Include="Communication\Data\SimpleResponsePurport.cs" />
<Compile Include="Communication\Data\SimpleResponse.cs" />
<Compile Include="Configuration\ClientConfiguration.cs" />
<Compile Include="Configuration\LoadStatus.cs" />
<Compile Include="Configuration\RuntimeInfo.cs" />
<Compile Include="Configuration\ISessionData.cs" />
<Compile Include="Configuration\Settings\ConfigurationMode.cs" />

View file

@ -96,47 +96,56 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[TestMethod]
public void MustCorrectlyRequestReconfiguration()
{
var url = "sebs://some/url.seb";
var response = new ReconfigurationResponse
{
Accepted = true
};
//var url = "sebs://some/url.seb";
//var response = new ReconfigurationResponse
//{
// Accepted = true
//};
proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url))).Returns(response);
//proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url))).Returns(response);
var accepted = sut.RequestReconfiguration(url);
//var accepted = sut.RequestReconfiguration(url);
proxy.Verify(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url)), Times.Once);
//proxy.Verify(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url)), Times.Once);
Assert.IsTrue(accepted);
//Assert.IsTrue(accepted);
// TODO
Assert.Fail();
}
[TestMethod]
public void MustCorrectlyHandleDeniedReconfigurationRequest()
{
var url = "sebs://some/url.seb";
var response = new ReconfigurationResponse
{
Accepted = false
};
//var url = "sebs://some/url.seb";
//var response = new ReconfigurationResponse
//{
// Accepted = false
//};
proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url))).Returns(response);
//proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url))).Returns(response);
var accepted = sut.RequestReconfiguration(url);
//var accepted = sut.RequestReconfiguration(url);
Assert.IsFalse(accepted);
//Assert.IsFalse(accepted);
// TODO
Assert.Fail();
}
[TestMethod]
public void MustNotFailIfIncorrectResponseToReconfigurationRequest()
{
var url = "sebs://some/url.seb";
//var url = "sebs://some/url.seb";
proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url))).Returns<Response>(null);
//proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url))).Returns<Response>(null);
var accepted = sut.RequestReconfiguration(url);
//var accepted = sut.RequestReconfiguration(url);
Assert.IsFalse(accepted);
//Assert.IsFalse(accepted);
// TODO
Assert.Fail();
}
[TestMethod]

View file

@ -53,7 +53,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">

View file

@ -11,6 +11,7 @@ using System.ServiceModel;
using System.Timers;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging;

View file

@ -45,16 +45,14 @@ namespace SafeExamBrowser.Core.Communication.Proxies
}
}
public bool RequestReconfiguration(string url)
public void RequestReconfiguration(string filePath)
{
var response = Send(new ReconfigurationMessage(url));
var response = Send(new ReconfigurationMessage(filePath));
if (response is ReconfigurationResponse reconfiguration)
if (!IsAcknowledged(response))
{
return reconfiguration.Accepted;
throw new CommunicationException($"Runtime did not acknowledge reconfiguration request! Response: {ToString(response)}.");
}
return false;
}
public void RequestShutdown()

View file

@ -18,6 +18,12 @@
<Entry key="MessageBox_ClientConfigurationQuestionTitle">
Configuration Successful
</Entry>
<Entry key="MessageBox_ConfigurationDownloadError">
Failed to download the new application configuration. Please try again or contact technical support.
</Entry>
<Entry key="MessageBox_ConfigurationDownloadErrorTitle">
Download Error
</Entry>
<Entry key="MessageBox_Quit">
Would you really like to quit the application?
</Entry>

View file

@ -45,7 +45,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -45,7 +45,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -45,8 +45,9 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
info.AppDataFolder = @"C:\Not\Really\AppData";
info.DefaultSettingsFileName = "SettingsDummy.txt";
info.ProgramDataFolder = @"C:\Not\Really\ProgramData";
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>())).Returns(settings);
repository.Setup(r => r.LoadDefaultSettings()).Returns(settings);
// TODO
//repository.Setup(r => r.LoadSettings(It.IsAny<Uri>())).Returns(settings);
//repository.Setup(r => r.LoadDefaultSettings()).Returns(settings);
}
[TestMethod]
@ -88,7 +89,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
sut.Perform();
repository.Verify(r => r.LoadSettings(It.Is<Uri>(u => u.Equals(new Uri(path)))), Times.Once);
Assert.Fail();
//repository.Verify(r => r.LoadSettings(It.Is<Uri>(u => u.Equals(new Uri(path)))), Times.Once);
}
[TestMethod]
@ -103,7 +105,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
sut.Perform();
repository.Verify(r => r.LoadSettings(It.Is<Uri>(u => u.Equals(new Uri(Path.Combine(location, "SettingsDummy.txt"))))), Times.Once);
Assert.Fail();
//repository.Verify(r => r.LoadSettings(It.Is<Uri>(u => u.Equals(new Uri(Path.Combine(location, "SettingsDummy.txt"))))), Times.Once);
}
[TestMethod]
@ -117,7 +120,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
sut.Perform();
repository.Verify(r => r.LoadSettings(It.Is<Uri>(u => u.Equals(new Uri(Path.Combine(location, "SettingsDummy.txt"))))), Times.Once);
Assert.Fail();
//repository.Verify(r => r.LoadSettings(It.Is<Uri>(u => u.Equals(new Uri(Path.Combine(location, "SettingsDummy.txt"))))), Times.Once);
}
[TestMethod]

View file

@ -53,7 +53,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">

View file

@ -75,6 +75,9 @@ namespace SafeExamBrowser.Runtime
instances.RuntimeController.Terminate();
instances.LogShutdownInformation();
// TODO: Which UI operation is being cancelled without the timeout? Same problem with client? -> Debug!
Thread.Sleep(20);
base.Shutdown();
}

View file

@ -8,7 +8,7 @@
using System.Threading;
using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;

View file

@ -50,35 +50,58 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
logger.Info("Initializing application configuration...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeConfiguration);
var isValidUri = TryGetSettingsUri(out Uri uri);
var isValidUri = TryInitializeSettingsUri(out Uri uri);
if (isValidUri)
{
logger.Info($"Loading configuration from '{uri.AbsolutePath}'...");
logger.Info($"Loading settings from '{uri.AbsolutePath}'...");
var abort = LoadSettings(uri);
var result = LoadSettings(uri);
if (abort)
if (result == OperationResult.Success && repository.CurrentSettings.ConfigurationMode == ConfigurationMode.ConfigureClient)
{
return OperationResult.Aborted;
var abort = IsConfigurationSufficient();
logger.Info($"The user chose to {(abort ? "abort" : "continue")} after successful client configuration.");
if (abort)
{
return OperationResult.Aborted;
}
}
LogOperationResult(result);
return result;
}
else
{
logger.Info("No valid settings file specified nor found in PROGRAMDATA or APPDATA - loading default settings...");
repository.LoadDefaultSettings();
}
logger.Info("No valid settings resource specified nor found in PROGRAMDATA or APPDATA - loading default settings...");
repository.LoadDefaultSettings();
return OperationResult.Success;
}
public OperationResult Repeat()
{
// TODO: How will the new settings be retrieved? Uri passed to the repository? If yes, how does the Uri get here?!
// -> IDEA: Use configuration repository as container?
// -> IDEA: Introduce IRepeatParams or alike?
logger.Info("Initializing new application configuration...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeConfiguration);
return OperationResult.Success;
var isValidUri = TryValidateSettingsUri(repository.ReconfigurationFilePath, out Uri uri);
if (isValidUri)
{
logger.Info($"Loading settings from '{uri.AbsolutePath}'...");
var result = LoadSettings(uri);
LogOperationResult(result);
return result;
}
logger.Warn($"The resource specified for reconfiguration does not exist or is not a file!");
return OperationResult.Failed;
}
public void Revert()
@ -86,7 +109,79 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
// Nothing to do here...
}
private bool TryGetSettingsUri(out Uri uri)
private OperationResult LoadSettings(Uri uri)
{
var adminPassword = default(string);
var settingsPassword = default(string);
var status = default(LoadStatus);
for (int adminAttempts = 0, settingsAttempts = 0; adminAttempts < 5 && settingsAttempts < 5;)
{
status = repository.LoadSettings(uri, settingsPassword, adminPassword);
if (status == LoadStatus.InvalidData || status == LoadStatus.Success)
{
break;
}
else if (status == LoadStatus.AdminPasswordNeeded || status == LoadStatus.SettingsPasswordNeeded)
{
var isAdmin = status == LoadStatus.AdminPasswordNeeded;
var success = isAdmin ? TryGetAdminPassword(out adminPassword) : TryGetSettingsPassword(out settingsPassword);
if (success)
{
adminAttempts += isAdmin ? 1 : 0;
settingsAttempts += isAdmin ? 0 : 1;
}
else
{
return OperationResult.Aborted;
}
}
}
if (status == LoadStatus.InvalidData)
{
if (IsHtmlPage(uri))
{
repository.LoadDefaultSettings();
repository.CurrentSettings.Browser.StartUrl = uri.AbsoluteUri;
logger.Info($"The specified URI '{uri.AbsoluteUri}' appears to point to a HTML page, setting it as startup URL.");
return OperationResult.Success;
}
logger.Error($"The specified settings resource '{uri.AbsoluteUri}' is invalid!");
}
return status == LoadStatus.Success ? OperationResult.Success : OperationResult.Failed;
}
private bool IsHtmlPage(Uri uri)
{
// TODO
return false;
}
private bool TryGetAdminPassword(out string password)
{
password = default(string);
// TODO
return true;
}
private bool TryGetSettingsPassword(out string password)
{
password = default(string);
// TODO
return true;
}
private bool TryInitializeSettingsUri(out Uri uri)
{
var path = string.Empty;
var isValidUri = false;
@ -119,19 +214,14 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
return isValidUri;
}
private bool LoadSettings(Uri uri)
private bool TryValidateSettingsUri(string path, out Uri uri)
{
var abort = false;
var settings = repository.LoadSettings(uri);
var isValidUri = Uri.TryCreate(path, UriKind.Absolute, out uri);
if (settings.ConfigurationMode == ConfigurationMode.ConfigureClient)
{
abort = IsConfigurationSufficient();
isValidUri &= uri != null && uri.IsFile;
isValidUri &= File.Exists(path);
logger.Info($"The user chose to {(abort ? "abort" : "continue")} after successful client configuration.");
}
return abort;
return isValidUri;
}
private bool IsConfigurationSufficient()
@ -142,5 +232,21 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
return abort == MessageBoxResult.Yes;
}
private void LogOperationResult(OperationResult result)
{
switch (result)
{
case OperationResult.Aborted:
logger.Info("The configuration was aborted by the user.");
break;
case OperationResult.Failed:
logger.Warn("The configuration has failed!");
break;
case OperationResult.Success:
logger.Info("The configuration was successful.");
break;
}
}
}
}

View file

@ -9,6 +9,7 @@
using System;
using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration;
@ -169,13 +170,14 @@ namespace SafeExamBrowser.Runtime.Behaviour
if (result == OperationResult.Failed)
{
// TODO: Check if message box is rendered on new desktop as well! -> E.g. if settings for reconfiguration are invalid
messageBox.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error);
}
if (!initial)
{
logger.Info("Terminating application...");
shutdown.Invoke();
if (!initial)
{
logger.Info("Terminating application...");
shutdown.Invoke();
}
}
}
}
@ -245,10 +247,22 @@ namespace SafeExamBrowser.Runtime.Behaviour
shutdown.Invoke();
}
private void RuntimeHost_ReconfigurationRequested()
private void RuntimeHost_ReconfigurationRequested(ReconfigurationEventArgs args)
{
logger.Info($"Starting reconfiguration...");
StartSession();
var mode = configuration.CurrentSettings.ConfigurationMode;
if (mode == ConfigurationMode.ConfigureClient)
{
logger.Info($"Accepted request for reconfiguration with '{args.ConfigurationPath}'.");
configuration.ReconfigurationFilePath = args.ConfigurationPath;
StartSession();
}
else
{
logger.Info($"Denied request for reconfiguration with '{args.ConfigurationPath}' due to '{mode}' mode!");
// TODO: configuration.CurrentSession.ClientProxy.InformReconfigurationDenied();
}
}
private void RuntimeHost_ShutdownRequested()

View file

@ -7,12 +7,10 @@
*/
using System;
using System.Threading.Tasks;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Core.Communication.Hosts;
@ -27,7 +25,7 @@ namespace SafeExamBrowser.Runtime.Communication
public event CommunicationEventHandler ClientDisconnected;
public event CommunicationEventHandler ClientReady;
public event CommunicationEventHandler ReconfigurationRequested;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
public event CommunicationEventHandler ShutdownRequested;
public RuntimeHost(string address, IConfigurationRepository configuration, IHostObjectFactory factory, ILogger logger) : base(address, factory, logger)
@ -64,9 +62,9 @@ namespace SafeExamBrowser.Runtime.Communication
{
switch (message)
{
case ReconfigurationMessage reconfigurationMessage:
// TODO: Not the job of the host, fire event or alike!
return Handle(reconfigurationMessage);
case ReconfigurationMessage r:
ReconfigurationRequested?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = r.ConfigurationPath });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
@ -89,22 +87,5 @@ namespace SafeExamBrowser.Runtime.Communication
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
}
private Response Handle(ReconfigurationMessage message)
{
var isExam = configuration.CurrentSettings.ConfigurationMode == ConfigurationMode.Exam;
var isValidUri = Uri.TryCreate(message.ConfigurationUrl, UriKind.Absolute, out _);
var allowed = !isExam && isValidUri;
Logger.Info($"Received reconfiguration request for '{message.ConfigurationUrl}', {(allowed ? "accepted" : "denied")} it.");
if (allowed)
{
configuration.ReconfigurationUrl = message.ConfigurationUrl;
Task.Run(() => ReconfigurationRequested?.Invoke());
}
return new ReconfigurationResponse { Accepted = allowed };
}
}
}

View file

@ -61,8 +61,8 @@
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
@ -71,7 +71,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>

View file

@ -45,7 +45,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="SimpleWifi, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">

View file

@ -47,7 +47,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="FontAwesome.WPF, Version=4.7.0.37774, Culture=neutral, PublicKeyToken=0758b07a11a4f466, processorArchitecture=MSIL">

View file

@ -46,7 +46,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -45,7 +45,6 @@
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />

View file

@ -9,6 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SafeExamBrowser.Core", "Saf
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0A9E6674-2FB4-42EA-85DE-B2445B9AE2D9}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
LICENSE.txt = LICENSE.txt
EndProjectSection
EndProject