SEBWIN-221: Implemented scaffolding for loading and parsing of configuration resources.
This commit is contained in:
parent
b4f468a2b4
commit
902b0c2b3b
33 changed files with 710 additions and 174 deletions
|
@ -103,9 +103,17 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
private void DownloadHandler_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
|
||||
{
|
||||
args.BrowserWindow = window;
|
||||
if (settings.AllowConfigurationDownloads)
|
||||
{
|
||||
args.BrowserWindow = window;
|
||||
logger.Debug($"Forwarding download request for configuration file '{fileName}'.");
|
||||
|
||||
ConfigurationDownloadRequested?.Invoke(fileName, args);
|
||||
ConfigurationDownloadRequested?.Invoke(fileName, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Discarded download request for configuration file '{fileName}'.");
|
||||
}
|
||||
}
|
||||
|
||||
private void Window_AddressChanged(string address)
|
||||
|
|
|
@ -31,11 +31,11 @@ namespace SafeExamBrowser.Browser.Handlers
|
|||
|
||||
if (uri.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.ToString();
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttp }.Uri.AbsoluteUri;
|
||||
}
|
||||
else if (uri.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.ToString();
|
||||
request.Url = new UriBuilder(uri) { Scheme = Uri.UriSchemeHttps }.Uri.AbsoluteUri;
|
||||
}
|
||||
|
||||
return base.OnBeforeResourceLoad(browserControl, browser, frame, request, callback);
|
||||
|
|
|
@ -206,7 +206,7 @@ namespace SafeExamBrowser.Client
|
|||
{
|
||||
if (Settings.ConfigurationMode == ConfigurationMode.ConfigureClient)
|
||||
{
|
||||
logger.Debug($"Received download request for configuration file '{fileName}'. Asking user to confirm the reconfiguration...");
|
||||
logger.Info($"Received download request for configuration file '{fileName}'. Asking user to confirm the reconfiguration...");
|
||||
|
||||
var message = TextKey.MessageBox_ReconfigurationQuestion;
|
||||
var title = TextKey.MessageBox_ReconfigurationQuestionTitle;
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.UnitTests
|
||||
{
|
||||
|
@ -22,8 +24,9 @@ namespace SafeExamBrowser.Configuration.UnitTests
|
|||
public void Initialize()
|
||||
{
|
||||
var executablePath = Assembly.GetExecutingAssembly().Location;
|
||||
var logger = new Mock<ILogger>();
|
||||
|
||||
sut = new ConfigurationRepository(executablePath, string.Empty, string.Empty, string.Empty);
|
||||
sut = new ConfigurationRepository(logger.Object, executablePath, string.Empty, string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using SafeExamBrowser.Configuration.DataFormats;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
@ -24,9 +28,16 @@ namespace SafeExamBrowser.Configuration
|
|||
private readonly string programVersion;
|
||||
|
||||
private AppConfig appConfig;
|
||||
private IList<IDataFormat> dataFormats;
|
||||
private ILogger logger;
|
||||
private IList<IResourceLoader> resourceLoaders;
|
||||
|
||||
public ConfigurationRepository(string executablePath, string programCopyright, string programTitle, string programVersion)
|
||||
public ConfigurationRepository(ILogger logger, string executablePath, string programCopyright, string programTitle, string programVersion)
|
||||
{
|
||||
dataFormats = new List<IDataFormat>();
|
||||
resourceLoaders = new List<IResourceLoader>();
|
||||
|
||||
this.logger = logger;
|
||||
this.executablePath = executablePath ?? string.Empty;
|
||||
this.programCopyright = programCopyright ?? string.Empty;
|
||||
this.programTitle = programTitle ?? string.Empty;
|
||||
|
@ -73,38 +84,28 @@ namespace SafeExamBrowser.Configuration
|
|||
|
||||
UpdateAppConfig();
|
||||
|
||||
configuration.AppConfig = CloneAppConfig();
|
||||
configuration.AppConfig = appConfig.Clone();
|
||||
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.KioskMode = KioskMode.None;
|
||||
settings.ServicePolicy = ServicePolicy.Optional;
|
||||
|
||||
settings.Browser.StartUrl = "https://www.safeexambrowser.org/testing";
|
||||
settings.Browser.AllowAddressBar = true;
|
||||
settings.Browser.AllowBackwardNavigation = true;
|
||||
settings.Browser.AllowConfigurationDownloads = true;
|
||||
settings.Browser.AllowDeveloperConsole = true;
|
||||
settings.Browser.AllowDownloads = true;
|
||||
settings.Browser.AllowForwardNavigation = true;
|
||||
settings.Browser.AllowReloading = true;
|
||||
settings.Browser.AllowDownloads = true;
|
||||
|
||||
settings.Taskbar.AllowApplicationLog = true;
|
||||
settings.Taskbar.AllowKeyboardLayout = true;
|
||||
|
@ -113,33 +114,90 @@ namespace SafeExamBrowser.Configuration
|
|||
return settings;
|
||||
}
|
||||
|
||||
private AppConfig CloneAppConfig()
|
||||
public void Register(IDataFormat dataFormat)
|
||||
{
|
||||
return new AppConfig
|
||||
dataFormats.Add(dataFormat);
|
||||
}
|
||||
|
||||
public void Register(IResourceLoader resourceLoader)
|
||||
{
|
||||
resourceLoaders.Add(resourceLoader);
|
||||
}
|
||||
|
||||
public LoadStatus TryLoadSettings(Uri resource, out Settings settings, string adminPassword = null, string settingsPassword = null)
|
||||
{
|
||||
settings = default(Settings);
|
||||
|
||||
logger.Info($"Attempting to load '{resource}'...");
|
||||
|
||||
try
|
||||
{
|
||||
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
|
||||
};
|
||||
var resourceLoader = resourceLoaders.FirstOrDefault(l => l.CanLoad(resource));
|
||||
|
||||
if (resourceLoader != null)
|
||||
{
|
||||
var data = resourceLoader.Load(resource);
|
||||
var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data));
|
||||
|
||||
logger.Info($"Successfully loaded {data.Length / 1000.0} KB data from '{resource}' using {resourceLoader.GetType().Name}.");
|
||||
|
||||
if (dataFormat is HtmlFormat)
|
||||
{
|
||||
return HandleHtml(resource, out settings);
|
||||
}
|
||||
|
||||
if (dataFormat != null)
|
||||
{
|
||||
return dataFormat.TryParse(data, out settings, adminPassword, settingsPassword);
|
||||
}
|
||||
}
|
||||
|
||||
logger.Warn($"No {(resourceLoader == null ? "resource loader" : "data format")} found for '{resource}'!");
|
||||
|
||||
return LoadStatus.NotSupported;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Unexpected error while trying to load '{resource}'!", e);
|
||||
|
||||
return LoadStatus.UnexpectedError;
|
||||
}
|
||||
}
|
||||
|
||||
private LoadStatus HandleHtml(Uri resource, out Settings settings)
|
||||
{
|
||||
logger.Info($"Loaded data appears to be HTML, loading default settings and using '{resource}' as startup URL.");
|
||||
|
||||
settings = LoadDefaultSettings();
|
||||
settings.Browser.StartUrl = resource.AbsoluteUri;
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
private byte[] Decompress(byte[] bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
var buffer = new byte[4096];
|
||||
|
||||
using (var stream = new GZipStream(new MemoryStream(bytes), CompressionMode.Decompress))
|
||||
using (var decompressed = new MemoryStream())
|
||||
{
|
||||
var bytesRead = 0;
|
||||
|
||||
do
|
||||
{
|
||||
bytesRead = stream.Read(buffer, 0, buffer.Length);
|
||||
decompressed.Write(buffer, 0, bytesRead);
|
||||
} while (bytesRead > 0);
|
||||
|
||||
return decompressed.ToArray();
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAppConfig()
|
||||
|
|
37
SafeExamBrowser.Configuration/DataFormats/DefaultFormat.cs
Normal file
37
SafeExamBrowser.Configuration/DataFormats/DefaultFormat.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public class DefaultFormat : IDataFormat
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public DefaultFormat(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanParse(byte[] data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public LoadStatus TryParse(byte[] data, out Settings settings, string adminPassword = null, string settingsPassword = null)
|
||||
{
|
||||
settings = new Settings();
|
||||
settings.ServicePolicy = ServicePolicy.Optional;
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
34
SafeExamBrowser.Configuration/DataFormats/HtmlFormat.cs
Normal file
34
SafeExamBrowser.Configuration/DataFormats/HtmlFormat.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public class HtmlFormat : IDataFormat
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public HtmlFormat(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanParse(byte[] data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public LoadStatus TryParse(byte[] data, out Settings settings, string adminPassword = null, string settingsPassword = null)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
34
SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs
Normal file
34
SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public class XmlFormat : IDataFormat
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public XmlFormat(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanParse(byte[] data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public LoadStatus TryParse(byte[] data, out Settings settings, string adminPassword = null, string settingsPassword = null)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +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.Configuration;
|
||||
|
||||
namespace SafeExamBrowser.Configuration
|
||||
{
|
||||
public class ResourceLoader : IResourceLoader
|
||||
{
|
||||
public bool IsHtmlResource(Uri resource)
|
||||
{
|
||||
// TODO: Implement resource loader
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.IO;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ResourceLoaders
|
||||
{
|
||||
public class FileResourceLoader : IResourceLoader
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public FileResourceLoader(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanLoad(Uri resource)
|
||||
{
|
||||
if (resource.IsFile && File.Exists(resource.AbsolutePath))
|
||||
{
|
||||
logger.Debug($"Can load '{resource}' as it references an existing file.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.Debug($"Can't load '{resource}' since it isn't a file URI or no file exists at the specified path.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] Load(Uri resource)
|
||||
{
|
||||
logger.Debug($"Loading data from '{resource}'...");
|
||||
|
||||
return File.ReadAllBytes(resource.AbsolutePath);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ResourceLoaders
|
||||
{
|
||||
public class NetworkResourceLoader : IResourceLoader
|
||||
{
|
||||
private AppConfig appConfig;
|
||||
private ILogger logger;
|
||||
|
||||
private string[] SupportedSchemes => new[]
|
||||
{
|
||||
appConfig.SebUriScheme,
|
||||
appConfig.SebUriSchemeSecure,
|
||||
Uri.UriSchemeHttp,
|
||||
Uri.UriSchemeHttps
|
||||
};
|
||||
|
||||
public NetworkResourceLoader(AppConfig appConfig, ILogger logger)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanLoad(Uri resource)
|
||||
{
|
||||
if (SupportedSchemes.Contains(resource.Scheme) && IsAvailable(resource))
|
||||
{
|
||||
logger.Debug($"Can load '{resource}' as it references an existing network resource.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.Debug($"Can't load '{resource}' since its URI scheme is not supported or the resource is unavailable.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] Load(Uri resource)
|
||||
{
|
||||
var uri = BuildUriFor(resource);
|
||||
|
||||
logger.Debug($"Downloading data from '{uri}'...");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
var response = Execute(request);
|
||||
|
||||
logger.Debug($"Sent GET request for '{uri}', received response '{(int) response.StatusCode} - {response.ReasonPhrase}'.");
|
||||
|
||||
var data = Extract(response.Content);
|
||||
|
||||
logger.Debug($"Extracted {data.Length / 1000.0} KB data from response.");
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private bool IsAvailable(Uri resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = BuildUriFor(resource);
|
||||
var request = new HttpRequestMessage(HttpMethod.Head, uri);
|
||||
var response = Execute(request);
|
||||
|
||||
logger.Debug($"Sent HEAD request for '{uri}', received response '{(int) response.StatusCode} - {response.ReasonPhrase}'.");
|
||||
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to check availability of '{resource}'!", e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Uri BuildUriFor(Uri resource)
|
||||
{
|
||||
var scheme = GetSchemeFor(resource);
|
||||
var builder = new UriBuilder(resource) { Scheme = scheme };
|
||||
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
private string GetSchemeFor(Uri resource)
|
||||
{
|
||||
if (resource.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
return Uri.UriSchemeHttp;
|
||||
}
|
||||
|
||||
if (resource.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
return Uri.UriSchemeHttps;
|
||||
}
|
||||
|
||||
return resource.Scheme;
|
||||
}
|
||||
|
||||
private HttpResponseMessage Execute(HttpRequestMessage request)
|
||||
{
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
return await client.SendAsync(request);
|
||||
}
|
||||
});
|
||||
|
||||
return task.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private byte[] Extract(HttpContent content)
|
||||
{
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
return await content.ReadAsByteArrayAsync();
|
||||
});
|
||||
|
||||
return task.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,13 +48,18 @@
|
|||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ResourceLoader.cs" />
|
||||
<Compile Include="DataFormats\DefaultFormat.cs" />
|
||||
<Compile Include="DataFormats\HtmlFormat.cs" />
|
||||
<Compile Include="DataFormats\XmlFormat.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ConfigurationRepository.cs" />
|
||||
<Compile Include="ResourceLoaders\FileResourceLoader.cs" />
|
||||
<Compile Include="ResourceLoaders\NetworkResourceLoader.cs" />
|
||||
<Compile Include="SessionConfiguration.cs" />
|
||||
<Compile Include="SystemInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -126,5 +126,13 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// The communication address of the service component.
|
||||
/// </summary>
|
||||
public string ServiceAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a shallow clone.
|
||||
/// </summary>
|
||||
public AppConfig Clone()
|
||||
{
|
||||
return MemberwiseClone() as AppConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,16 +25,25 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// </summary>
|
||||
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. As long as the result is not <see cref="LoadStatus.Success"/>, the declared
|
||||
/// <paramref name="settings"/> will be <c>null</c>!
|
||||
/// </summary>
|
||||
LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, string adminPassword = null, string settingsPassword = null);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the default settings.
|
||||
/// </summary>
|
||||
Settings.Settings LoadDefaultSettings();
|
||||
|
||||
/// <summary>
|
||||
/// Registers the specified <see cref="IDataFormat"/> as option to parse configuration data.
|
||||
/// </summary>
|
||||
void Register(IDataFormat dataFormat);
|
||||
|
||||
/// <summary>
|
||||
/// Registers the specified <see cref="IResourceLoader"/> as option to load configuration data.
|
||||
/// </summary>
|
||||
void Register(IResourceLoader resourceLoader);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load settings from the specified resource, using the optional passwords. As long as the result is not
|
||||
/// <see cref="LoadStatus.Success"/>, the referenced settings may be <c>null</c> or in an undefinable state!
|
||||
/// </summary>
|
||||
LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, string adminPassword = null, string settingsPassword = null);
|
||||
}
|
||||
}
|
||||
|
|
26
SafeExamBrowser.Contracts/Configuration/IDataFormat.cs
Normal file
26
SafeExamBrowser.Contracts/Configuration/IDataFormat.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the data format for a configuration file.
|
||||
/// </summary>
|
||||
public interface IDataFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the given data complies with the required format.
|
||||
/// </summary>
|
||||
bool CanParse(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parse the given binary data.
|
||||
/// </summary>
|
||||
LoadStatus TryParse(byte[] data, out Settings.Settings settings, string adminPassword = null, string settingsPassword = null);
|
||||
}
|
||||
}
|
|
@ -11,13 +11,18 @@ using System;
|
|||
namespace SafeExamBrowser.Contracts.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads configuration data from various sources (e.g. the internet) and provides related resource handling functionality.
|
||||
/// Loads binary data from a particular resource.
|
||||
/// </summary>
|
||||
public interface IResourceLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the given <see cref="Uri"/> identifies a HTML resource.
|
||||
/// Indicates whether the resource loader is able to load data from the specified resource.
|
||||
/// </summary>
|
||||
bool IsHtmlResource(Uri resource);
|
||||
bool CanLoad(Uri resource);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the binary data from the specified resource.
|
||||
/// </summary>
|
||||
byte[] Load(Uri resource);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
namespace SafeExamBrowser.Contracts.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines all possible results of <see cref="IConfigurationRepository.LoadSettings(System.Uri)"/>.
|
||||
/// Defines all possible results of an attempt to load a configuration file.
|
||||
/// </summary>
|
||||
public enum LoadStatus
|
||||
{
|
||||
|
@ -23,14 +23,24 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// </summary>
|
||||
InvalidData,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a resource is not supported.
|
||||
/// </summary>
|
||||
NotSupported,
|
||||
|
||||
/// <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.
|
||||
/// The settings were loaded successfully.
|
||||
/// </summary>
|
||||
Success
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// An unexpected error occurred while trying to load the settings.
|
||||
/// </summary>
|
||||
UnexpectedError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,13 +26,18 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings
|
|||
/// </summary>
|
||||
public bool AllowBackwardNavigation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user should be allowed to download configuration files.
|
||||
/// </summary>
|
||||
public bool AllowConfigurationDownloads { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user should be allowed to open the developer console of a browser window.
|
||||
/// </summary>
|
||||
public bool AllowDeveloperConsole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user should be allowed to download files.
|
||||
/// Determines whether the user should be allowed to download files (excluding configuration files).
|
||||
/// </summary>
|
||||
public bool AllowDownloads { get; set; }
|
||||
|
||||
|
|
|
@ -22,6 +22,10 @@ namespace SafeExamBrowser.Contracts.I18n
|
|||
MessageBox_ClientConfigurationQuestionTitle,
|
||||
MessageBox_ConfigurationDownloadError,
|
||||
MessageBox_ConfigurationDownloadErrorTitle,
|
||||
MessageBox_InvalidConfigurationData,
|
||||
MessageBox_InvalidConfigurationDataTitle,
|
||||
MessageBox_NotSupportedConfigurationResource,
|
||||
MessageBox_NotSupportedConfigurationResourceTitle,
|
||||
MessageBox_Quit,
|
||||
MessageBox_QuitTitle,
|
||||
MessageBox_QuitError,
|
||||
|
@ -42,6 +46,8 @@ namespace SafeExamBrowser.Contracts.I18n
|
|||
MessageBox_SingleInstanceTitle,
|
||||
MessageBox_StartupError,
|
||||
MessageBox_StartupErrorTitle,
|
||||
MessageBox_UnexpectedConfigurationError,
|
||||
MessageBox_UnexpectedConfigurationErrorTitle,
|
||||
Notification_AboutTooltip,
|
||||
Notification_LogTooltip,
|
||||
OperationStatus_CloseRuntimeConnection,
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" />
|
||||
<Compile Include="Configuration\IDataFormat.cs" />
|
||||
<Compile Include="Core\Events\InstanceTerminatedEventHandler.cs" />
|
||||
<Compile Include="Core\Events\NameChangedEventHandler.cs" />
|
||||
<Compile Include="Core\IApplicationController.cs" />
|
||||
|
|
|
@ -24,6 +24,18 @@
|
|||
<Entry key="MessageBox_ConfigurationDownloadErrorTitle">
|
||||
Download Error
|
||||
</Entry>
|
||||
<Entry key="MessageBox_InvalidConfigurationData">
|
||||
The configuration resource '%%URI%%' contains invalid data!
|
||||
</Entry>
|
||||
<Entry key="MessageBox_InvalidConfigurationDataTitle">
|
||||
Configuration Error
|
||||
</Entry>
|
||||
<Entry key="MessageBox_NotSupportedConfigurationResource">
|
||||
The configuration resource '%%URI%%' is not supported!
|
||||
</Entry>
|
||||
<Entry key="MessageBox_NotSupportedConfigurationResourceTitle">
|
||||
Configuration Error
|
||||
</Entry>
|
||||
<Entry key="MessageBox_Quit">
|
||||
Would you really like to quit the application?
|
||||
</Entry>
|
||||
|
@ -78,6 +90,12 @@
|
|||
<Entry key="MessageBox_StartupErrorTitle">
|
||||
Startup Error
|
||||
</Entry>
|
||||
<Entry key="MessageBox_UnexpectedConfigurationError">
|
||||
An unexpected error occurred while trying to load configuration resource '%%URI%%'! Please consult the application log for more information...
|
||||
</Entry>
|
||||
<Entry key="MessageBox_UnexpectedConfigurationErrorTitle">
|
||||
Configuration Error
|
||||
</Entry>
|
||||
<Entry key="Notification_AboutTooltip">
|
||||
About Safe Exam Browser
|
||||
</Entry>
|
||||
|
|
|
@ -26,7 +26,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
private AppConfig appConfig;
|
||||
private Mock<ILogger> logger;
|
||||
private Mock<IConfigurationRepository> repository;
|
||||
private Mock<IResourceLoader> resourceLoader;
|
||||
private Mock<ISessionConfiguration> session;
|
||||
private SessionContext sessionContext;
|
||||
private Settings settings;
|
||||
|
@ -39,7 +38,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
appConfig = new AppConfig();
|
||||
logger = new Mock<ILogger>();
|
||||
repository = new Mock<IConfigurationRepository>();
|
||||
resourceLoader = new Mock<IResourceLoader>();
|
||||
session = new Mock<ISessionConfiguration>();
|
||||
sessionContext = new SessionContext();
|
||||
settings = new Settings();
|
||||
|
@ -64,7 +62,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.Perform();
|
||||
|
||||
var resource = new Uri(url);
|
||||
|
@ -82,7 +80,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.Perform();
|
||||
|
||||
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt"));
|
||||
|
@ -99,7 +97,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.Perform();
|
||||
|
||||
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt"));
|
||||
|
@ -110,10 +108,19 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
[TestMethod]
|
||||
public void MustFallbackToDefaultsAsLastPrio()
|
||||
{
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
var actualSettings = default(Settings);
|
||||
var defaultSettings = new Settings();
|
||||
|
||||
repository.Setup(r => r.LoadDefaultSettings()).Returns(defaultSettings);
|
||||
session.SetupSet<Settings>(s => s.Settings = It.IsAny<Settings>()).Callback(s => actualSettings = s);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.Perform();
|
||||
|
||||
repository.Verify(r => r.LoadDefaultSettings(), Times.Once);
|
||||
session.VerifySet(s => s.Settings = defaultSettings);
|
||||
|
||||
Assert.AreSame(defaultSettings, actualSettings);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -122,7 +129,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
appConfig.ProgramDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is ConfigurationCompletedEventArgs c)
|
||||
|
@ -141,7 +148,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
{
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is ConfigurationCompletedEventArgs c)
|
||||
|
@ -161,7 +168,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
settings.ConfigurationMode = ConfigurationMode.Exam;
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is ConfigurationCompletedEventArgs c)
|
||||
|
@ -176,15 +183,25 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
[TestMethod]
|
||||
public void MustNotFailWithoutCommandLineArgs()
|
||||
{
|
||||
repository.Setup(r => r.LoadDefaultSettings());
|
||||
var actualSettings = default(Settings);
|
||||
var defaultSettings = new Settings();
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
repository.Setup(r => r.LoadDefaultSettings()).Returns(defaultSettings);
|
||||
session.SetupSet<Settings>(s => s.Settings = It.IsAny<Settings>()).Callback(s => actualSettings = s);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.Perform();
|
||||
|
||||
sut = new ConfigurationOperation(new string[] { }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
repository.Verify(r => r.LoadDefaultSettings(), Times.Once);
|
||||
|
||||
Assert.AreSame(defaultSettings, actualSettings);
|
||||
|
||||
sut = new ConfigurationOperation(new string[] { }, repository.Object, logger.Object, sessionContext);
|
||||
sut.Perform();
|
||||
|
||||
repository.Verify(r => r.LoadDefaultSettings(), Times.Exactly(2));
|
||||
|
||||
Assert.AreSame(defaultSettings, actualSettings);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -192,7 +209,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
{
|
||||
var uri = @"an/invalid\uri.'*%yolo/()你好";
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", uri }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", uri }, repository.Object, logger.Object, sessionContext);
|
||||
sut.Perform();
|
||||
}
|
||||
|
||||
|
@ -203,7 +220,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.AdminPasswordNeeded);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is PasswordRequiredEventArgs p)
|
||||
|
@ -224,7 +241,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is PasswordRequiredEventArgs p)
|
||||
|
@ -247,7 +264,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.AdminPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, password, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is PasswordRequiredEventArgs p)
|
||||
|
@ -272,7 +289,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, password)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is PasswordRequiredEventArgs p)
|
||||
|
@ -295,7 +312,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.AdminPasswordNeeded);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is PasswordRequiredEventArgs p)
|
||||
|
@ -316,7 +333,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is PasswordRequiredEventArgs p)
|
||||
|
@ -341,7 +358,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, settingsPassword)).Returns(LoadStatus.AdminPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, adminPassword, settingsPassword)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
{
|
||||
if (args is PasswordRequiredEventArgs p)
|
||||
|
@ -358,37 +375,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, adminPassword, settingsPassword), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustHandleInvalidData()
|
||||
{
|
||||
var url = @"http://www.safeexambrowser.org/whatever.seb";
|
||||
|
||||
resourceLoader.Setup(r => r.IsHtmlResource(It.IsAny<Uri>())).Returns(false);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.InvalidData);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
|
||||
var result = sut.Perform();
|
||||
|
||||
Assert.AreEqual(OperationResult.Failed, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustHandleHtmlAsInvalidData()
|
||||
{
|
||||
var url = "http://www.blubb.org/some/resource.html";
|
||||
|
||||
resourceLoader.Setup(r => r.IsHtmlResource(It.IsAny<Uri>())).Returns(true);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.InvalidData);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
|
||||
var result = sut.Perform();
|
||||
|
||||
Assert.AreEqual(OperationResult.Success, result);
|
||||
Assert.AreEqual(url, settings.Browser.StartUrl);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MustReconfigureSuccessfullyWithCorrectUri()
|
||||
{
|
||||
|
@ -398,7 +384,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
sessionContext.ReconfigurationFilePath = resource.AbsolutePath;
|
||||
repository.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
|
||||
var result = sut.Repeat();
|
||||
|
||||
|
@ -415,7 +401,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
sessionContext.ReconfigurationFilePath = null;
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, resourceLoader.Object, sessionContext);
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
|
||||
var result = sut.Repeat();
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
|
@ -13,6 +13,8 @@ using System.Reflection;
|
|||
using SafeExamBrowser.Communication.Hosts;
|
||||
using SafeExamBrowser.Communication.Proxies;
|
||||
using SafeExamBrowser.Configuration;
|
||||
using SafeExamBrowser.Configuration.DataFormats;
|
||||
using SafeExamBrowser.Configuration.ResourceLoaders;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Core;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
|
@ -32,6 +34,7 @@ namespace SafeExamBrowser.Runtime
|
|||
internal class CompositionRoot
|
||||
{
|
||||
private AppConfig appConfig;
|
||||
private IConfigurationRepository configuration;
|
||||
private ILogger logger;
|
||||
private ISystemInfo systemInfo;
|
||||
private IText text;
|
||||
|
@ -45,13 +48,12 @@ namespace SafeExamBrowser.Runtime
|
|||
const int FIFTEEN_SECONDS = 15000;
|
||||
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
var configuration = BuildConfigurationRepository();
|
||||
var nativeMethods = new NativeMethods();
|
||||
|
||||
logger = new Logger();
|
||||
appConfig = configuration.InitializeAppConfig();
|
||||
systemInfo = new SystemInfo();
|
||||
|
||||
InitializeConfiguration();
|
||||
InitializeLogging();
|
||||
InitializeText();
|
||||
|
||||
|
@ -60,7 +62,6 @@ namespace SafeExamBrowser.Runtime
|
|||
var explorerShell = new ExplorerShell(new ModuleLogger(logger, nameof(ExplorerShell)), nativeMethods);
|
||||
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, 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();
|
||||
|
@ -73,7 +74,7 @@ namespace SafeExamBrowser.Runtime
|
|||
bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger));
|
||||
|
||||
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost, sessionContext));
|
||||
sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, logger, resourceLoader, sessionContext));
|
||||
sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, logger, 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));
|
||||
|
@ -105,15 +106,22 @@ namespace SafeExamBrowser.Runtime
|
|||
logger?.Log($"# Application terminated at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
|
||||
}
|
||||
|
||||
private IConfigurationRepository BuildConfigurationRepository()
|
||||
private void InitializeConfiguration()
|
||||
{
|
||||
var executable = Assembly.GetExecutingAssembly();
|
||||
var programCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright;
|
||||
var programTitle = executable.GetCustomAttribute<AssemblyTitleAttribute>().Title;
|
||||
var programVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
var repository = new ConfigurationRepository(executable.Location, programCopyright, programTitle, programVersion);
|
||||
var moduleLogger = new ModuleLogger(logger, nameof(ConfigurationRepository));
|
||||
|
||||
return repository;
|
||||
configuration = new ConfigurationRepository(moduleLogger, executable.Location, programCopyright, programTitle, programVersion);
|
||||
appConfig = configuration.InitializeAppConfig();
|
||||
|
||||
configuration.Register(new DefaultFormat(new ModuleLogger(logger, nameof(DefaultFormat))));
|
||||
configuration.Register(new HtmlFormat(new ModuleLogger(logger, nameof(HtmlFormat))));
|
||||
configuration.Register(new XmlFormat(new ModuleLogger(logger, nameof(XmlFormat))));
|
||||
configuration.Register(new FileResourceLoader(new ModuleLogger(logger, nameof(FileResourceLoader))));
|
||||
configuration.Register(new NetworkResourceLoader(appConfig, new ModuleLogger(logger, nameof(NetworkResourceLoader))));
|
||||
}
|
||||
|
||||
private void InitializeLogging()
|
||||
|
|
|
@ -24,7 +24,6 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
private string[] commandLineArgs;
|
||||
private IConfigurationRepository configuration;
|
||||
private ILogger logger;
|
||||
private IResourceLoader resourceLoader;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired;
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
@ -33,13 +32,11 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
string[] commandLineArgs,
|
||||
IConfigurationRepository configuration,
|
||||
ILogger logger,
|
||||
IResourceLoader resourceLoader,
|
||||
SessionContext sessionContext) : base(sessionContext)
|
||||
{
|
||||
this.commandLineArgs = commandLineArgs;
|
||||
this.logger = logger;
|
||||
this.configuration = configuration;
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
|
@ -51,7 +48,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
if (isValidUri)
|
||||
{
|
||||
logger.Info($"Attempting to load settings from '{uri.AbsolutePath}'...");
|
||||
logger.Info($"Attempting to load settings from '{uri}'...");
|
||||
|
||||
var result = LoadSettings(uri);
|
||||
|
||||
|
@ -62,7 +59,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
}
|
||||
|
||||
logger.Info("No valid settings resource specified nor found in PROGRAMDATA or APPDATA - loading default settings...");
|
||||
configuration.LoadDefaultSettings();
|
||||
Context.Next.Settings = configuration.LoadDefaultSettings();
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
@ -76,7 +73,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
if (isValidUri)
|
||||
{
|
||||
logger.Info($"Attempting to load settings from '{uri.AbsolutePath}'...");
|
||||
logger.Info($"Attempting to load settings from '{uri}'...");
|
||||
|
||||
var result = LoadSettings(uri);
|
||||
|
||||
|
@ -126,11 +123,6 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
}
|
||||
}
|
||||
|
||||
if (status == LoadStatus.InvalidData)
|
||||
{
|
||||
HandleInvalidData(ref status, uri);
|
||||
}
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
{
|
||||
Context.Next.Settings = settings;
|
||||
|
@ -138,6 +130,18 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case LoadStatus.InvalidData:
|
||||
ActionRequired?.Invoke(new InvalidDataMessageArgs(uri.ToString()));
|
||||
break;
|
||||
case LoadStatus.NotSupported:
|
||||
ActionRequired?.Invoke(new NotSupportedMessageArgs(uri.ToString()));
|
||||
break;
|
||||
case LoadStatus.UnexpectedError:
|
||||
ActionRequired?.Invoke(new UnexpectedErrorMessageArgs(uri.ToString()));
|
||||
break;
|
||||
}
|
||||
|
||||
return OperationResult.Failed;
|
||||
}
|
||||
|
@ -152,22 +156,6 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
return args;
|
||||
}
|
||||
|
||||
private void HandleInvalidData(ref LoadStatus status, Uri uri)
|
||||
{
|
||||
if (resourceLoader.IsHtmlResource(uri))
|
||||
{
|
||||
configuration.LoadDefaultSettings();
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"The specified settings resource '{uri.AbsoluteUri}' is invalid!");
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryInitializeSettingsUri(out Uri uri)
|
||||
{
|
||||
var path = string.Empty;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.I18n;
|
||||
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class InvalidDataMessageArgs : MessageEventArgs
|
||||
{
|
||||
internal InvalidDataMessageArgs(string uri)
|
||||
{
|
||||
Icon = MessageBoxIcon.Error;
|
||||
Message = TextKey.MessageBox_InvalidConfigurationData;
|
||||
MessagePlaceholders["%%URI%%"] = uri;
|
||||
Title = TextKey.MessageBox_InvalidConfigurationDataTitle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.Collections.Generic;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
|
||||
using SafeExamBrowser.Contracts.I18n;
|
||||
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class MessageEventArgs : ActionRequiredEventArgs
|
||||
{
|
||||
internal MessageBoxIcon Icon { get; set; }
|
||||
internal TextKey Message { get; set; }
|
||||
internal TextKey Title { get; set; }
|
||||
internal Dictionary<string, string> MessagePlaceholders { get; private set; }
|
||||
internal Dictionary<string, string> TitlePlaceholders { get; private set; }
|
||||
|
||||
public MessageEventArgs()
|
||||
{
|
||||
MessagePlaceholders = new Dictionary<string, string>();
|
||||
TitlePlaceholders = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.I18n;
|
||||
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class NotSupportedMessageArgs : MessageEventArgs
|
||||
{
|
||||
internal NotSupportedMessageArgs(string uri)
|
||||
{
|
||||
Icon = MessageBoxIcon.Error;
|
||||
Message = TextKey.MessageBox_NotSupportedConfigurationResource;
|
||||
MessagePlaceholders["%%URI%%"] = uri;
|
||||
Title = TextKey.MessageBox_NotSupportedConfigurationResourceTitle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.I18n;
|
||||
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class UnexpectedErrorMessageArgs : MessageEventArgs
|
||||
{
|
||||
internal UnexpectedErrorMessageArgs(string uri)
|
||||
{
|
||||
Icon = MessageBoxIcon.Error;
|
||||
Message = TextKey.MessageBox_UnexpectedConfigurationError;
|
||||
MessagePlaceholders["%%URI%%"] = uri;
|
||||
Title = TextKey.MessageBox_UnexpectedConfigurationErrorTitle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,10 +35,6 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
set { Context.OriginalDesktop = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TODO: This mechanism exposes the internal state of the operation! Find better solution which will keep the
|
||||
/// state internal but still allow unit testing of both kiosk mode operations independently!
|
||||
/// </summary>
|
||||
protected KioskMode? ActiveMode
|
||||
{
|
||||
get { return Context.ActiveMode; }
|
||||
|
|
|
@ -363,6 +363,9 @@ namespace SafeExamBrowser.Runtime
|
|||
case ConfigurationCompletedEventArgs a:
|
||||
AskIfConfigurationSufficient(a);
|
||||
break;
|
||||
case MessageEventArgs m:
|
||||
ShowMessageBox(m);
|
||||
break;
|
||||
case PasswordRequiredEventArgs p:
|
||||
AskForPassword(p);
|
||||
break;
|
||||
|
@ -393,6 +396,24 @@ namespace SafeExamBrowser.Runtime
|
|||
}
|
||||
}
|
||||
|
||||
private void ShowMessageBox(MessageEventArgs args)
|
||||
{
|
||||
var message = text.Get(args.Message);
|
||||
var title = text.Get(args.Title);
|
||||
|
||||
foreach (var placeholder in args.MessagePlaceholders)
|
||||
{
|
||||
message = message.Replace(placeholder.Key, placeholder.Value);
|
||||
}
|
||||
|
||||
foreach (var placeholder in args.TitlePlaceholders)
|
||||
{
|
||||
title = title.Replace(placeholder.Key, placeholder.Value);
|
||||
}
|
||||
|
||||
messageBox.Show(message, title, MessageBoxAction.Confirm, args.Icon, runtimeWindow);
|
||||
}
|
||||
|
||||
private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args)
|
||||
{
|
||||
var isAdmin = args.Purpose == PasswordRequestPurpose.Administrator;
|
||||
|
|
|
@ -90,7 +90,11 @@
|
|||
<Compile Include="Operations\ClientTerminationOperation.cs" />
|
||||
<Compile Include="Operations\ConfigurationOperation.cs" />
|
||||
<Compile Include="Operations\Events\ConfigurationCompletedEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\InvalidDataMessageArgs.cs" />
|
||||
<Compile Include="Operations\Events\MessageEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\NotSupportedMessageArgs.cs" />
|
||||
<Compile Include="Operations\Events\PasswordRequiredEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\UnexpectedErrorMessageArgs.cs" />
|
||||
<Compile Include="Operations\KioskModeOperation.cs" />
|
||||
<Compile Include="Operations\KioskModeTerminationOperation.cs" />
|
||||
<Compile Include="Operations\ServiceOperation.cs" />
|
||||
|
|
|
@ -64,6 +64,7 @@ namespace SafeExamBrowser.WindowsApi
|
|||
logger.Info($"Restored window '{window.Title}' with handle = {window.Handle}.");
|
||||
}
|
||||
|
||||
minimizedWindows.Clear();
|
||||
logger.Info("Minimized windows successfully restored.");
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue