SEBWIN-221: Implemented scaffolding for loading and parsing of configuration resources.

This commit is contained in:
dbuechel 2018-11-08 09:39:52 +01:00
parent b4f468a2b4
commit 902b0c2b3b
33 changed files with 710 additions and 174 deletions

View file

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

View file

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

View file

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

View file

@ -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]

View file

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

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

@ -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,

View file

@ -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" />

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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" />

View file

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