SEBWIN-221: Implemented changes in configuration algorithm to accomodate new functionality scope of 3.0 Alpha.
This commit is contained in:
parent
9b639b0c53
commit
f9f2bb3257
24 changed files with 330 additions and 124 deletions
|
@ -42,7 +42,7 @@ namespace SafeExamBrowser.Browser.Handlers
|
||||||
{
|
{
|
||||||
var uri = new Uri(downloadItem.Url);
|
var uri = new Uri(downloadItem.Url);
|
||||||
var extension = Path.GetExtension(uri.AbsolutePath);
|
var extension = Path.GetExtension(uri.AbsolutePath);
|
||||||
var isConfigFile = String.Equals(extension, appConfig.ConfigurationFileExtension, StringComparison.InvariantCultureIgnoreCase);
|
var isConfigFile = String.Equals(extension, appConfig.ConfigurationFileExtension, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
logger.Debug($"Handling download request for '{uri}'.");
|
logger.Debug($"Handling download request for '{uri}'.");
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,10 @@ namespace SafeExamBrowser.Configuration.UnitTests
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
var executablePath = Assembly.GetExecutingAssembly().Location;
|
var executablePath = Assembly.GetExecutingAssembly().Location;
|
||||||
|
var hashAlgorithm = new Mock<IHashAlgorithm>();
|
||||||
var logger = new Mock<ILogger>();
|
var logger = new Mock<ILogger>();
|
||||||
|
|
||||||
sut = new ConfigurationRepository(logger.Object, executablePath, string.Empty, string.Empty, string.Empty);
|
sut = new ConfigurationRepository(hashAlgorithm.Object, logger.Object, executablePath, string.Empty, string.Empty, string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
|
@ -10,6 +10,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using SafeExamBrowser.Configuration.DataFormats;
|
||||||
using SafeExamBrowser.Contracts.Configuration;
|
using SafeExamBrowser.Contracts.Configuration;
|
||||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||||
using SafeExamBrowser.Contracts.Logging;
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
|
@ -26,15 +27,23 @@ namespace SafeExamBrowser.Configuration
|
||||||
private readonly string programVersion;
|
private readonly string programVersion;
|
||||||
|
|
||||||
private AppConfig appConfig;
|
private AppConfig appConfig;
|
||||||
|
private IHashAlgorithm hashAlgorithm;
|
||||||
private IList<IDataFormat> dataFormats;
|
private IList<IDataFormat> dataFormats;
|
||||||
private ILogger logger;
|
|
||||||
private IList<IResourceLoader> resourceLoaders;
|
private IList<IResourceLoader> resourceLoaders;
|
||||||
|
private ILogger logger;
|
||||||
|
|
||||||
public ConfigurationRepository(ILogger logger, string executablePath, string programCopyright, string programTitle, string programVersion)
|
public ConfigurationRepository(
|
||||||
|
IHashAlgorithm hashAlgorithm,
|
||||||
|
ILogger logger,
|
||||||
|
string executablePath,
|
||||||
|
string programCopyright,
|
||||||
|
string programTitle,
|
||||||
|
string programVersion)
|
||||||
{
|
{
|
||||||
dataFormats = new List<IDataFormat>();
|
dataFormats = new List<IDataFormat>();
|
||||||
resourceLoaders = new List<IResourceLoader>();
|
resourceLoaders = new List<IResourceLoader>();
|
||||||
|
|
||||||
|
this.hashAlgorithm = hashAlgorithm;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.executablePath = executablePath ?? string.Empty;
|
this.executablePath = executablePath ?? string.Empty;
|
||||||
this.programCopyright = programCopyright ?? string.Empty;
|
this.programCopyright = programCopyright ?? string.Empty;
|
||||||
|
@ -124,7 +133,7 @@ namespace SafeExamBrowser.Configuration
|
||||||
resourceLoaders.Add(resourceLoader);
|
resourceLoaders.Add(resourceLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoadStatus TryLoadSettings(Uri resource, out Settings settings, string password = null, bool passwordIsHash = false)
|
public LoadStatus TryLoadSettings(Uri resource, PasswordInfo passwordInfo, out Settings settings)
|
||||||
{
|
{
|
||||||
logger.Info($"Attempting to load '{resource}'...");
|
logger.Info($"Attempting to load '{resource}'...");
|
||||||
|
|
||||||
|
@ -136,15 +145,17 @@ namespace SafeExamBrowser.Configuration
|
||||||
|
|
||||||
using (data)
|
using (data)
|
||||||
{
|
{
|
||||||
switch (status)
|
if (status == LoadStatus.LoadWithBrowser)
|
||||||
{
|
{
|
||||||
case LoadStatus.LoadWithBrowser:
|
return HandleBrowserResource(resource, settings);
|
||||||
return HandleBrowserResource(resource, settings);
|
|
||||||
case LoadStatus.Success:
|
|
||||||
return TryParseData(data, settings, password, passwordIsHash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
if (status != LoadStatus.Success)
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryParseData(data, passwordInfo, resource, settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -155,6 +166,35 @@ namespace SafeExamBrowser.Configuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ExtractAndImportCertificates(IDictionary<string, object> data)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoadStatus HandleBrowserResource(Uri resource, Settings settings)
|
||||||
|
{
|
||||||
|
settings.Browser.StartUrl = resource.AbsoluteUri;
|
||||||
|
logger.Info($"The resource needs authentication or is HTML data, loaded default settings with '{resource}' as startup URL.");
|
||||||
|
|
||||||
|
return LoadStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleParseSuccess(ParseResult result, Settings settings, PasswordInfo passwordInfo, Uri resource)
|
||||||
|
{
|
||||||
|
var appDataFile = new Uri(Path.Combine(appConfig.AppDataFolder, appConfig.DefaultSettingsFileName));
|
||||||
|
var programDataFile = new Uri(Path.Combine(appConfig.ProgramDataFolder, appConfig.DefaultSettingsFileName));
|
||||||
|
var isAppDataFile = resource.AbsolutePath.Equals(appDataFile.AbsolutePath, StringComparison.OrdinalIgnoreCase);
|
||||||
|
var isProgramDataFile = resource.AbsolutePath.Equals(programDataFile.AbsolutePath, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
logger.Info("Mapping settings data...");
|
||||||
|
result.RawData.MapTo(settings);
|
||||||
|
|
||||||
|
if (settings.ConfigurationMode == ConfigurationMode.ConfigureClient && !isAppDataFile && !isProgramDataFile)
|
||||||
|
{
|
||||||
|
result.Status = TryConfigureClient(result.RawData, settings, passwordInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private LoadStatus TryLoadData(Uri resource, out Stream data)
|
private LoadStatus TryLoadData(Uri resource, out Stream data)
|
||||||
{
|
{
|
||||||
var status = LoadStatus.NotSupported;
|
var status = LoadStatus.NotSupported;
|
||||||
|
@ -175,15 +215,23 @@ namespace SafeExamBrowser.Configuration
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadStatus TryParseData(Stream data, Settings settings, string password = null, bool passwordIsHash = false)
|
private LoadStatus TryParseData(Stream data, PasswordInfo passwordInfo, Uri resource, Settings settings)
|
||||||
{
|
{
|
||||||
var status = LoadStatus.NotSupported;
|
|
||||||
var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data));
|
var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data));
|
||||||
|
var status = LoadStatus.NotSupported;
|
||||||
|
|
||||||
if (dataFormat != null)
|
if (dataFormat != null)
|
||||||
{
|
{
|
||||||
status = dataFormat.TryParse(data, settings, password, passwordIsHash);
|
var result = dataFormat.TryParse(data, passwordInfo);
|
||||||
logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {status}.");
|
|
||||||
|
logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {result.Status}.");
|
||||||
|
|
||||||
|
if (result.Status == LoadStatus.Success || result.Status == LoadStatus.SuccessConfigureClient)
|
||||||
|
{
|
||||||
|
HandleParseSuccess(result, settings, passwordInfo, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = result.Status;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -193,12 +241,42 @@ namespace SafeExamBrowser.Configuration
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadStatus HandleBrowserResource(Uri resource, Settings settings)
|
private LoadStatus TryConfigureClient(IDictionary<string, object> data, Settings settings, PasswordInfo passwordInfo)
|
||||||
{
|
{
|
||||||
settings.Browser.StartUrl = resource.AbsoluteUri;
|
logger.Info("Attempting to configure local client settings...");
|
||||||
logger.Info($"The resource needs authentication or is HTML data, loaded default settings with '{resource}' as startup URL.");
|
|
||||||
|
|
||||||
return LoadStatus.Success;
|
if (passwordInfo.AdminPasswordHash != null)
|
||||||
|
{
|
||||||
|
var adminPasswordHash = passwordInfo.AdminPassword != null ? hashAlgorithm.GenerateHashFor(passwordInfo.AdminPassword) : null;
|
||||||
|
var settingsPasswordHash = passwordInfo.SettingsPassword != null ? hashAlgorithm.GenerateHashFor(passwordInfo.SettingsPassword) : null;
|
||||||
|
var enteredCorrectPassword = passwordInfo.AdminPasswordHash.Equals(adminPasswordHash, StringComparison.OrdinalIgnoreCase);
|
||||||
|
var sameAdminPassword = passwordInfo.AdminPasswordHash.Equals(settings.AdminPasswordHash, StringComparison.OrdinalIgnoreCase);
|
||||||
|
var knowsAdminPassword = passwordInfo.AdminPasswordHash.Equals(settingsPasswordHash, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (sameAdminPassword || knowsAdminPassword || enteredCorrectPassword)
|
||||||
|
{
|
||||||
|
logger.Info("Authentication was successful.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Info("Authentication has failed!");
|
||||||
|
|
||||||
|
return LoadStatus.AdminPasswordNeeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Info("Authentication is not required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -> Certificates need to be imported and REMOVED from the settings before the data is saved!
|
||||||
|
ExtractAndImportCertificates(data);
|
||||||
|
|
||||||
|
// Save configuration data as local client config under %APPDATA%!
|
||||||
|
// -> Default settings password for local client configuration appears to be string.Empty
|
||||||
|
// -> Local client configuration needs to again be encrypted in the same way as the original file was!!
|
||||||
|
|
||||||
|
return LoadStatus.SuccessConfigureClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateAppConfig()
|
private void UpdateAppConfig()
|
||||||
|
|
|
@ -11,7 +11,6 @@ using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using SafeExamBrowser.Configuration.DataFormats.Cryptography;
|
using SafeExamBrowser.Configuration.DataFormats.Cryptography;
|
||||||
using SafeExamBrowser.Contracts.Configuration;
|
using SafeExamBrowser.Contracts.Configuration;
|
||||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
|
||||||
using SafeExamBrowser.Contracts.Logging;
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Configuration.DataFormats
|
namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
|
@ -57,7 +56,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoadStatus TryParse(Stream data, Settings settings, string password = null, bool passwordIsHash = false)
|
public ParseResult TryParse(Stream data, PasswordInfo passwordInfo)
|
||||||
{
|
{
|
||||||
var prefix = ParsePrefix(data);
|
var prefix = ParsePrefix(data);
|
||||||
var success = TryDetermineFormat(prefix, out FormatType format);
|
var success = TryDetermineFormat(prefix, out FormatType format);
|
||||||
|
@ -76,22 +75,22 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
{
|
{
|
||||||
case FormatType.Password:
|
case FormatType.Password:
|
||||||
case FormatType.PasswordConfigureClient:
|
case FormatType.PasswordConfigureClient:
|
||||||
return ParsePasswordBlock(data, format, settings, password, passwordIsHash);
|
return ParsePasswordBlock(data, passwordInfo, format);
|
||||||
case FormatType.PlainData:
|
case FormatType.PlainData:
|
||||||
return ParsePlainDataBlock(data, settings);
|
return ParsePlainDataBlock(data, passwordInfo);
|
||||||
case FormatType.PublicKeyHash:
|
case FormatType.PublicKeyHash:
|
||||||
return ParsePublicKeyHashBlock(data, settings, password, passwordIsHash);
|
return ParsePublicKeyHashBlock(data, passwordInfo);
|
||||||
case FormatType.PublicKeyHashWithSymmetricKey:
|
case FormatType.PublicKeyHashWithSymmetricKey:
|
||||||
return ParsePublicKeyHashWithSymmetricKeyBlock(data, settings, password, passwordIsHash);
|
return ParsePublicKeyHashWithSymmetricKeyBlock(data, passwordInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error($"'{data}' starting with '{prefix}' does not match the binary format!");
|
logger.Error($"'{data}' starting with '{prefix}' does not match the binary format!");
|
||||||
|
|
||||||
return LoadStatus.InvalidData;
|
return new ParseResult { Status = LoadStatus.InvalidData };
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadStatus ParsePasswordBlock(Stream data, FormatType format, Settings settings, string password, bool passwordIsHash)
|
private ParseResult ParsePasswordBlock(Stream data, PasswordInfo passwordInfo, FormatType format)
|
||||||
{
|
{
|
||||||
var decrypted = default(Stream);
|
var decrypted = default(Stream);
|
||||||
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
|
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
|
||||||
|
@ -99,28 +98,32 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
|
|
||||||
if (format == FormatType.PasswordConfigureClient)
|
if (format == FormatType.PasswordConfigureClient)
|
||||||
{
|
{
|
||||||
password = password == null ? string.Empty : (passwordIsHash ? password : hashAlgorithm.GenerateHashFor(password));
|
status = encryption.Decrypt(data, string.Empty, out decrypted);
|
||||||
status = encryption.Decrypt(data, out decrypted, password);
|
|
||||||
|
|
||||||
if (status == LoadStatus.PasswordNeeded && passwordIsHash && password != string.Empty)
|
if (status == LoadStatus.SettingsPasswordNeeded && passwordInfo.AdminPasswordHash != null)
|
||||||
{
|
{
|
||||||
status = encryption.Decrypt(data, out decrypted, string.Empty);
|
status = encryption.Decrypt(data, passwordInfo.AdminPasswordHash, out decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == LoadStatus.SettingsPasswordNeeded && passwordInfo.SettingsPassword != null)
|
||||||
|
{
|
||||||
|
status = encryption.Decrypt(data, hashAlgorithm.GenerateHashFor(passwordInfo.SettingsPassword), out decrypted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
status = encryption.Decrypt(data, out decrypted, password);
|
status = encryption.Decrypt(data, passwordInfo.SettingsPassword, out decrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == LoadStatus.Success)
|
if (status == LoadStatus.Success)
|
||||||
{
|
{
|
||||||
return ParsePlainDataBlock(decrypted, settings);
|
return ParsePlainDataBlock(decrypted, passwordInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return new ParseResult { Status = status };
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadStatus ParsePlainDataBlock(Stream data, Settings settings)
|
private ParseResult ParsePlainDataBlock(Stream data, PasswordInfo passwordInfo)
|
||||||
{
|
{
|
||||||
var xmlFormat = new XmlFormat(logger.CloneFor(nameof(XmlFormat)));
|
var xmlFormat = new XmlFormat(logger.CloneFor(nameof(XmlFormat)));
|
||||||
|
|
||||||
|
@ -129,23 +132,23 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
data = compressor.Decompress(data);
|
data = compressor.Decompress(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return xmlFormat.TryParse(data, settings);
|
return xmlFormat.TryParse(data, passwordInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadStatus ParsePublicKeyHashBlock(Stream data, Settings settings, string password, bool passwordIsHash)
|
private ParseResult ParsePublicKeyHashBlock(Stream data, PasswordInfo passwordInfo)
|
||||||
{
|
{
|
||||||
var encryption = new PublicKeyHashEncryption(logger.CloneFor(nameof(PublicKeyHashEncryption)));
|
var encryption = new PublicKeyHashEncryption(logger.CloneFor(nameof(PublicKeyHashEncryption)));
|
||||||
var status = encryption.Decrypt(data, out Stream decrypted);
|
var status = encryption.Decrypt(data, out Stream decrypted);
|
||||||
|
|
||||||
if (status == LoadStatus.Success)
|
if (status == LoadStatus.Success)
|
||||||
{
|
{
|
||||||
return TryParse(decrypted, settings, password, passwordIsHash);
|
return TryParse(decrypted, passwordInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return new ParseResult { Status = status };
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadStatus ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, Settings settings, string password, bool passwordIsHash)
|
private ParseResult ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, PasswordInfo passwordInfo)
|
||||||
{
|
{
|
||||||
var logger = this.logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption));
|
var logger = this.logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption));
|
||||||
var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
|
var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
|
||||||
|
@ -154,10 +157,10 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
|
|
||||||
if (status == LoadStatus.Success)
|
if (status == LoadStatus.Success)
|
||||||
{
|
{
|
||||||
return TryParse(decrypted, settings, password, passwordIsHash);
|
return TryParse(decrypted, passwordInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return new ParseResult { Status = status };
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ParsePrefix(Stream data)
|
private string ParsePrefix(Stream data)
|
||||||
|
|
|
@ -31,13 +31,13 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal LoadStatus Decrypt(Stream data, out Stream decrypted, string password)
|
internal LoadStatus Decrypt(Stream data, string password, out Stream decrypted)
|
||||||
{
|
{
|
||||||
decrypted = default(Stream);
|
decrypted = default(Stream);
|
||||||
|
|
||||||
if (password == null)
|
if (password == null)
|
||||||
{
|
{
|
||||||
return LoadStatus.PasswordNeeded;
|
return LoadStatus.SettingsPasswordNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
var (version, options) = ParseHeader(data);
|
var (version, options) = ParseHeader(data);
|
||||||
|
@ -64,7 +64,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
|
|
||||||
if (version != VERSION || options != OPTIONS)
|
if (version != VERSION || options != OPTIONS)
|
||||||
{
|
{
|
||||||
logger.Warn($"Invalid encryption header! Expected: [{VERSION},{OPTIONS},...] - Actual: [{version},{options},...]");
|
logger.Debug($"Unknown encryption header! Expected: [{VERSION},{OPTIONS},...] - Actual: [{version},{options},...]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (version, options);
|
return (version, options);
|
||||||
|
@ -110,9 +110,9 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
|
|
||||||
private LoadStatus FailForInvalidHmac()
|
private LoadStatus FailForInvalidHmac()
|
||||||
{
|
{
|
||||||
logger.Warn($"The authentication failed due to an invalid password or corrupted data!");
|
logger.Debug($"The authentication failed due to an invalid password or corrupted data!");
|
||||||
|
|
||||||
return LoadStatus.PasswordNeeded;
|
return LoadStatus.SettingsPasswordNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength)
|
private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength)
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
|
|
||||||
var symmetricKey = ParseSymmetricKey(data, certificate);
|
var symmetricKey = ParseSymmetricKey(data, certificate);
|
||||||
var stream = new SubStream(data, data.Position, data.Length - data.Position);
|
var stream = new SubStream(data, data.Position, data.Length - data.Position);
|
||||||
var status = passwordEncryption.Decrypt(stream, out decrypted, symmetricKey);
|
var status = passwordEncryption.Decrypt(stream, symmetricKey, out decrypted);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
{
|
{
|
||||||
internal static class DataMapper
|
internal static class DataMapper
|
||||||
{
|
{
|
||||||
internal static void MapTo(this Dictionary<string, object> rawData, Settings settings)
|
internal static void MapTo(this IDictionary<string, object> rawData, Settings settings)
|
||||||
{
|
{
|
||||||
foreach (var kvp in rawData)
|
foreach (var kvp in rawData)
|
||||||
{
|
{
|
||||||
|
@ -26,6 +26,9 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
{
|
{
|
||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
|
case "hashedAdminPassword":
|
||||||
|
settings.MapAdminPasswordHash(value);
|
||||||
|
break;
|
||||||
case "sebConfigPurpose":
|
case "sebConfigPurpose":
|
||||||
settings.MapConfigurationMode(value);
|
settings.MapConfigurationMode(value);
|
||||||
break;
|
break;
|
||||||
|
@ -35,6 +38,14 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void MapAdminPasswordHash(this Settings settings, object value)
|
||||||
|
{
|
||||||
|
if (value is string hash)
|
||||||
|
{
|
||||||
|
settings.AdminPasswordHash = hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void MapConfigurationMode(this Settings settings, object value)
|
private static void MapConfigurationMode(this Settings settings, object value)
|
||||||
{
|
{
|
||||||
if (value is Int32 mode)
|
if (value is Int32 mode)
|
||||||
|
@ -45,9 +56,9 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
|
|
||||||
private static void MapStartUrl(this Settings settings, object value)
|
private static void MapStartUrl(this Settings settings, object value)
|
||||||
{
|
{
|
||||||
if (value is string startUrl)
|
if (value is string url)
|
||||||
{
|
{
|
||||||
settings.Browser.StartUrl = startUrl;
|
settings.Browser.StartUrl = url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ using System.Text;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using SafeExamBrowser.Contracts.Configuration;
|
using SafeExamBrowser.Contracts.Configuration;
|
||||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
|
||||||
using SafeExamBrowser.Contracts.Logging;
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Configuration.DataFormats
|
namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
|
@ -59,9 +58,9 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoadStatus TryParse(Stream data, Settings settings, string password = null, bool passwordIsHash = false)
|
public ParseResult TryParse(Stream data, PasswordInfo passwordInfo)
|
||||||
{
|
{
|
||||||
var status = LoadStatus.InvalidData;
|
var result = new ParseResult { Status = LoadStatus.InvalidData };
|
||||||
var xmlSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
|
var xmlSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
|
||||||
|
|
||||||
data.Seek(0, SeekOrigin.Begin);
|
data.Seek(0, SeekOrigin.Begin);
|
||||||
|
@ -75,15 +74,11 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
if (hasRoot && hasDictionary)
|
if (hasRoot && hasDictionary)
|
||||||
{
|
{
|
||||||
logger.Debug($"Found root node, starting to parse data...");
|
logger.Debug($"Found root node, starting to parse data...");
|
||||||
status = ParseDictionary(reader, rawData);
|
|
||||||
|
|
||||||
if (status == LoadStatus.Success)
|
result.Status = ParseDictionary(reader, rawData);
|
||||||
{
|
result.RawData = rawData;
|
||||||
logger.Debug("Mapping raw settings data...");
|
|
||||||
rawData.MapTo(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug($"Finished parsing -> Result: {status}.");
|
logger.Debug($"Finished parsing -> Result: {result.Status}.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -91,7 +86,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadStatus ParseArray(XmlReader reader, List<object> array)
|
private LoadStatus ParseArray(XmlReader reader, List<object> array)
|
||||||
|
|
|
@ -13,8 +13,6 @@ namespace SafeExamBrowser.Contracts.Communication.Data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum PasswordRequestPurpose
|
public enum PasswordRequestPurpose
|
||||||
{
|
{
|
||||||
Undefined = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The password is to be used as administrator password for an application configuration.
|
/// The password is to be used as administrator password for an application configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -16,6 +16,11 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IRuntimeHost : ICommunicationHost
|
public interface IRuntimeHost : ICommunicationHost
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether another application component may establish a connection with the host.
|
||||||
|
/// </summary>
|
||||||
|
bool AllowConnection { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The startup token used for initial authentication.
|
/// The startup token used for initial authentication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -11,7 +11,7 @@ using System;
|
||||||
namespace SafeExamBrowser.Contracts.Configuration
|
namespace SafeExamBrowser.Contracts.Configuration
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The repository which controls the loading and initializing of configuration data.
|
/// The repository which controls the loading and saving of configuration data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IConfigurationRepository
|
public interface IConfigurationRepository
|
||||||
{
|
{
|
||||||
|
@ -41,9 +41,9 @@ namespace SafeExamBrowser.Contracts.Configuration
|
||||||
void Register(IResourceLoader resourceLoader);
|
void Register(IResourceLoader resourceLoader);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to load settings from the specified resource, using the optional password. As long as the result is not
|
/// Attempts to load settings from the specified resource. As long as the result is not <see cref="LoadStatus.Success"/>,
|
||||||
/// <see cref="LoadStatus.Success"/>, the referenced settings may be <c>null</c> or in an undefinable state!
|
/// the referenced settings may be <c>null</c> or in an undefinable state!
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, string password = null, bool passwordIsHash = false);
|
LoadStatus TryLoadSettings(Uri resource, PasswordInfo passwordInfo, out Settings.Settings settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,8 @@ namespace SafeExamBrowser.Contracts.Configuration
|
||||||
bool CanParse(Stream data);
|
bool CanParse(Stream data);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to parse the given data, using the optional password. As long as the result is not <see cref="LoadStatus.Success"/>,
|
/// Tries to parse the given data.
|
||||||
/// the referenced settings can be in an undefinable state!
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
LoadStatus TryParse(Stream data, Settings.Settings settings, string password = null, bool passwordIsHash = false);
|
ParseResult TryParse(Stream data, PasswordInfo passwordInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,14 @@ namespace SafeExamBrowser.Contracts.Configuration
|
||||||
public enum LoadStatus
|
public enum LoadStatus
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that a resource does not comply with the declared data format.
|
/// Indicates that the current administrator password is needed to be allowed to configure the local client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InvalidData = 1,
|
AdminPasswordNeeded = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that a resource contains invalid data.
|
||||||
|
/// </summary>
|
||||||
|
InvalidData,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that a resource needs to be loaded with the browser.
|
/// Indicates that a resource needs to be loaded with the browser.
|
||||||
|
@ -29,15 +34,20 @@ namespace SafeExamBrowser.Contracts.Configuration
|
||||||
NotSupported,
|
NotSupported,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that a password is needed in order to load the settings.
|
/// Indicates that the settings password is needed in order to decrypt the settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PasswordNeeded,
|
SettingsPasswordNeeded,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The settings were loaded successfully.
|
/// The settings were loaded successfully.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Success,
|
Success,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The settings were loaded and the local client configuration was performed successfully.
|
||||||
|
/// </summary>
|
||||||
|
SuccessConfigureClient,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An unexpected error occurred while trying to load the settings.
|
/// An unexpected error occurred while trying to load the settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
28
SafeExamBrowser.Contracts/Configuration/ParseResult.cs
Normal file
28
SafeExamBrowser.Contracts/Configuration/ParseResult.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Contracts.Configuration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the result of a data parsing operation by an <see cref="IDataFormat"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class ParseResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The parsed settings data. Might be <c>null</c> or in an undefinable state, depending on <see cref="Status"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IDictionary<string, object> RawData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The status result of a parsing operation.
|
||||||
|
/// </summary>
|
||||||
|
public LoadStatus Status { get; set; }
|
||||||
|
}
|
||||||
|
}
|
31
SafeExamBrowser.Contracts/Configuration/PasswordInfo.cs
Normal file
31
SafeExamBrowser.Contracts/Configuration/PasswordInfo.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Contracts.Configuration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Holds all password data necessary to load an application configuration.
|
||||||
|
/// </summary>
|
||||||
|
public class PasswordInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current administrator password in plain text.
|
||||||
|
/// </summary>
|
||||||
|
public string AdminPassword { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The hash code of the current administrator password.
|
||||||
|
/// </summary>
|
||||||
|
public string AdminPasswordHash { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The settings password of the configuration in plain text.
|
||||||
|
/// </summary>
|
||||||
|
public string SettingsPassword { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ namespace SafeExamBrowser.Contracts.I18n
|
||||||
MessageBox_ConfigurationDownloadErrorTitle,
|
MessageBox_ConfigurationDownloadErrorTitle,
|
||||||
MessageBox_InvalidConfigurationData,
|
MessageBox_InvalidConfigurationData,
|
||||||
MessageBox_InvalidConfigurationDataTitle,
|
MessageBox_InvalidConfigurationDataTitle,
|
||||||
|
MessageBox_InvalidPasswordError,
|
||||||
|
MessageBox_InvalidPasswordErrorTitle,
|
||||||
MessageBox_NotSupportedConfigurationResource,
|
MessageBox_NotSupportedConfigurationResource,
|
||||||
MessageBox_NotSupportedConfigurationResourceTitle,
|
MessageBox_NotSupportedConfigurationResourceTitle,
|
||||||
MessageBox_Quit,
|
MessageBox_Quit,
|
||||||
|
|
|
@ -56,6 +56,8 @@
|
||||||
<Compile Include="Configuration\IDataCompressor.cs" />
|
<Compile Include="Configuration\IDataCompressor.cs" />
|
||||||
<Compile Include="Configuration\IDataFormat.cs" />
|
<Compile Include="Configuration\IDataFormat.cs" />
|
||||||
<Compile Include="Configuration\IHashAlgorithm.cs" />
|
<Compile Include="Configuration\IHashAlgorithm.cs" />
|
||||||
|
<Compile Include="Configuration\ParseResult.cs" />
|
||||||
|
<Compile Include="Configuration\PasswordInfo.cs" />
|
||||||
<Compile Include="Core\Events\InstanceTerminatedEventHandler.cs" />
|
<Compile Include="Core\Events\InstanceTerminatedEventHandler.cs" />
|
||||||
<Compile Include="Core\Events\NameChangedEventHandler.cs" />
|
<Compile Include="Core\Events\NameChangedEventHandler.cs" />
|
||||||
<Compile Include="Core\IApplicationController.cs" />
|
<Compile Include="Core\IApplicationController.cs" />
|
||||||
|
|
|
@ -30,6 +30,12 @@
|
||||||
<Entry key="MessageBox_InvalidConfigurationDataTitle">
|
<Entry key="MessageBox_InvalidConfigurationDataTitle">
|
||||||
Configuration Error
|
Configuration Error
|
||||||
</Entry>
|
</Entry>
|
||||||
|
<Entry key="MessageBox_InvalidPasswordError">
|
||||||
|
You failed to enter the correct password within 5 attempts. The application will now terminate...
|
||||||
|
</Entry>
|
||||||
|
<Entry key="MessageBox_InvalidPasswordErrorTitle">
|
||||||
|
Invalid Password
|
||||||
|
</Entry>
|
||||||
<Entry key="MessageBox_NotSupportedConfigurationResource">
|
<Entry key="MessageBox_NotSupportedConfigurationResource">
|
||||||
The configuration resource '%%URI%%' is not supported!
|
The configuration resource '%%URI%%' is not supported!
|
||||||
</Entry>
|
</Entry>
|
||||||
|
|
|
@ -17,8 +17,7 @@ namespace SafeExamBrowser.Runtime.Communication
|
||||||
{
|
{
|
||||||
internal class RuntimeHost : BaseHost, IRuntimeHost
|
internal class RuntimeHost : BaseHost, IRuntimeHost
|
||||||
{
|
{
|
||||||
private bool allowConnection = true;
|
public bool AllowConnection { get; set; }
|
||||||
|
|
||||||
public Guid StartupToken { private get; set; }
|
public Guid StartupToken { private get; set; }
|
||||||
|
|
||||||
public event CommunicationEventHandler ClientDisconnected;
|
public event CommunicationEventHandler ClientDisconnected;
|
||||||
|
@ -35,11 +34,11 @@ namespace SafeExamBrowser.Runtime.Communication
|
||||||
protected override bool OnConnect(Guid? token = null)
|
protected override bool OnConnect(Guid? token = null)
|
||||||
{
|
{
|
||||||
var authenticated = StartupToken == token;
|
var authenticated = StartupToken == token;
|
||||||
var accepted = allowConnection && authenticated;
|
var accepted = AllowConnection && authenticated;
|
||||||
|
|
||||||
if (accepted)
|
if (accepted)
|
||||||
{
|
{
|
||||||
allowConnection = false;
|
AllowConnection = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return accepted;
|
return accepted;
|
||||||
|
@ -48,13 +47,6 @@ namespace SafeExamBrowser.Runtime.Communication
|
||||||
protected override void OnDisconnect()
|
protected override void OnDisconnect()
|
||||||
{
|
{
|
||||||
ClientDisconnected?.Invoke();
|
ClientDisconnected?.Invoke();
|
||||||
// TODO: Handle client crash scenario!
|
|
||||||
// If a client crashes or hangs when terminating (which should not happen!), it could be that it never gets to disconnect from
|
|
||||||
// the RuntimeHost - in that case, allowConnection prohibits restarting a new session as long as it's only set here!
|
|
||||||
// -> Move AllowConnection to interface and reset it in RuntimeController?
|
|
||||||
// -> Only possible as long as just the client connects, with service and client a more elaborate solution will be needed!
|
|
||||||
// -> E.g. ClientId and ServiceId, and then AllowClientConnection and AllowServiceConnection?
|
|
||||||
allowConnection = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Response OnReceive(Message message)
|
protected override Response OnReceive(Message message)
|
||||||
|
|
|
@ -116,7 +116,7 @@ namespace SafeExamBrowser.Runtime
|
||||||
var compressor = new GZipCompressor(new ModuleLogger(logger, nameof(GZipCompressor)));
|
var compressor = new GZipCompressor(new ModuleLogger(logger, nameof(GZipCompressor)));
|
||||||
var repositoryLogger = new ModuleLogger(logger, nameof(ConfigurationRepository));
|
var repositoryLogger = new ModuleLogger(logger, nameof(ConfigurationRepository));
|
||||||
|
|
||||||
configuration = new ConfigurationRepository(repositoryLogger, executable.Location, programCopyright, programTitle, programVersion);
|
configuration = new ConfigurationRepository(new HashAlgorithm(), repositoryLogger, executable.Location, programCopyright, programTitle, programVersion);
|
||||||
appConfig = configuration.InitializeAppConfig();
|
appConfig = configuration.InitializeAppConfig();
|
||||||
|
|
||||||
configuration.Register(new BinaryFormat(compressor, new HashAlgorithm(), new ModuleLogger(logger, nameof(BinaryFormat))));
|
configuration.Register(new BinaryFormat(compressor, new HashAlgorithm(), new ModuleLogger(logger, nameof(BinaryFormat))));
|
||||||
|
|
|
@ -106,12 +106,14 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||||
var token = Context.Next.StartupToken.ToString("D");
|
var token = Context.Next.StartupToken.ToString("D");
|
||||||
|
|
||||||
logger.Info("Starting new client process...");
|
logger.Info("Starting new client process...");
|
||||||
|
runtimeHost.AllowConnection = true;
|
||||||
runtimeHost.ClientReady += clientReadyEventHandler;
|
runtimeHost.ClientReady += clientReadyEventHandler;
|
||||||
ClientProcess = processFactory.StartNew(clientExecutable, clientLogFile, hostUri, token);
|
ClientProcess = processFactory.StartNew(clientExecutable, clientLogFile, hostUri, token);
|
||||||
|
|
||||||
logger.Info("Waiting for client to complete initialization...");
|
logger.Info("Waiting for client to complete initialization...");
|
||||||
clientReady = clientReadyEvent.WaitOne(timeout_ms);
|
clientReady = clientReadyEvent.WaitOne(timeout_ms);
|
||||||
runtimeHost.ClientReady -= clientReadyEventHandler;
|
runtimeHost.ClientReady -= clientReadyEventHandler;
|
||||||
|
runtimeHost.AllowConnection = false;
|
||||||
|
|
||||||
if (!clientReady)
|
if (!clientReady)
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,8 +35,8 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||||
SessionContext sessionContext) : base(sessionContext)
|
SessionContext sessionContext) : base(sessionContext)
|
||||||
{
|
{
|
||||||
this.commandLineArgs = commandLineArgs;
|
this.commandLineArgs = commandLineArgs;
|
||||||
this.logger = logger;
|
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override OperationResult Perform()
|
public override OperationResult Perform()
|
||||||
|
@ -50,7 +50,6 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||||
if (isValidUri)
|
if (isValidUri)
|
||||||
{
|
{
|
||||||
result = LoadSettings(uri);
|
result = LoadSettings(uri);
|
||||||
HandleClientConfiguration(ref result, uri);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -73,7 +72,6 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||||
if (isValidUri)
|
if (isValidUri)
|
||||||
{
|
{
|
||||||
result = LoadSettings(uri);
|
result = LoadSettings(uri);
|
||||||
HandleClientConfiguration(ref result, uri);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -100,36 +98,55 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||||
|
|
||||||
private OperationResult LoadSettings(Uri uri)
|
private OperationResult LoadSettings(Uri uri)
|
||||||
{
|
{
|
||||||
var status = configuration.TryLoadSettings(uri, out Settings settings, Context.Current?.Settings?.AdminPasswordHash, true);
|
var settings = default(Settings);
|
||||||
|
var status = default(LoadStatus);
|
||||||
|
var passwordInfo = new PasswordInfo { AdminPasswordHash = Context.Current?.Settings?.AdminPasswordHash };
|
||||||
|
|
||||||
for (var attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
|
for (int adminAttempts = 0, settingsAttempts = 0; adminAttempts < 5 && settingsAttempts < 5;)
|
||||||
{
|
{
|
||||||
var result = TryGetPassword();
|
status = configuration.TryLoadSettings(uri, passwordInfo, out settings);
|
||||||
|
|
||||||
if (!result.Success)
|
if (status != LoadStatus.AdminPasswordNeeded && status != LoadStatus.SettingsPasswordNeeded)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = TryGetPassword(status, passwordInfo);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
{
|
{
|
||||||
return OperationResult.Aborted;
|
return OperationResult.Aborted;
|
||||||
}
|
}
|
||||||
|
|
||||||
status = configuration.TryLoadSettings(uri, out settings, result.Password);
|
adminAttempts += status == LoadStatus.AdminPasswordNeeded ? 1 : 0;
|
||||||
|
settingsAttempts += status == LoadStatus.SettingsPasswordNeeded ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Context.Next.Settings = settings;
|
||||||
|
|
||||||
if (status == LoadStatus.Success)
|
if (status == LoadStatus.Success)
|
||||||
{
|
{
|
||||||
Context.Next.Settings = settings;
|
return OperationResult.Success;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ShowFailureMessage(status, uri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status == LoadStatus.Success ? OperationResult.Success : OperationResult.Failed;
|
if (status == LoadStatus.SuccessConfigureClient)
|
||||||
|
{
|
||||||
|
return HandleClientConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowFailureMessage(status, uri);
|
||||||
|
|
||||||
|
return OperationResult.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowFailureMessage(LoadStatus status, Uri uri)
|
private void ShowFailureMessage(LoadStatus status, Uri uri)
|
||||||
{
|
{
|
||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
|
case LoadStatus.AdminPasswordNeeded:
|
||||||
|
case LoadStatus.SettingsPasswordNeeded:
|
||||||
|
ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
|
||||||
|
break;
|
||||||
case LoadStatus.InvalidData:
|
case LoadStatus.InvalidData:
|
||||||
ActionRequired?.Invoke(new InvalidDataMessageArgs(uri.ToString()));
|
ActionRequired?.Invoke(new InvalidDataMessageArgs(uri.ToString()));
|
||||||
break;
|
break;
|
||||||
|
@ -142,14 +159,23 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PasswordRequiredEventArgs TryGetPassword()
|
private bool TryGetPassword(LoadStatus status, PasswordInfo passwordInfo)
|
||||||
{
|
{
|
||||||
var purpose = PasswordRequestPurpose.Settings;
|
var purpose = status == LoadStatus.AdminPasswordNeeded ? PasswordRequestPurpose.Administrator : PasswordRequestPurpose.Settings;
|
||||||
var args = new PasswordRequiredEventArgs { Purpose = purpose };
|
var args = new PasswordRequiredEventArgs { Purpose = purpose };
|
||||||
|
|
||||||
ActionRequired?.Invoke(args);
|
ActionRequired?.Invoke(args);
|
||||||
|
|
||||||
return args;
|
if (purpose == PasswordRequestPurpose.Administrator)
|
||||||
|
{
|
||||||
|
passwordInfo.AdminPassword = args.Password;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
passwordInfo.SettingsPassword = args.Password;
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryInitializeSettingsUri(out Uri uri)
|
private bool TryInitializeSettingsUri(out Uri uri)
|
||||||
|
@ -195,37 +221,30 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||||
return isValidUri;
|
return isValidUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleClientConfiguration(ref OperationResult result, Uri uri)
|
private OperationResult HandleClientConfiguration()
|
||||||
{
|
{
|
||||||
var configureMode = Context.Next.Settings?.ConfigurationMode == ConfigurationMode.ConfigureClient;
|
var firstSession = Context.Current == null;
|
||||||
var loadWithBrowser = Context.Next.Settings?.Browser.StartUrl == uri.AbsoluteUri;
|
|
||||||
var successful = result == OperationResult.Success;
|
|
||||||
|
|
||||||
if (successful && configureMode && !loadWithBrowser)
|
if (firstSession)
|
||||||
{
|
{
|
||||||
var args = new ConfigurationCompletedEventArgs();
|
var args = new ConfigurationCompletedEventArgs();
|
||||||
var filePath = Path.Combine(Context.Next.AppConfig.AppDataFolder, Context.Next.AppConfig.DefaultSettingsFileName);
|
|
||||||
|
|
||||||
// TODO: Save / overwrite configuration file in APPDATA directory!
|
|
||||||
// -> Check whether current and new admin passwords are the same! If not, current needs to be verified before overwriting!
|
|
||||||
// -> Special case: If current admin password is same as new settings password, verification is not necessary!
|
|
||||||
// -> Default settings password appears to be string.Empty for local client configuration
|
|
||||||
// -> Any (new?) certificates need to be imported and REMOVED from the settings before the data is saved!
|
|
||||||
// -> DO NOT transform settings, just simply copy the given configuration file to %APPDATA%\SebClientSettings.seb !!!
|
|
||||||
//configuration.SaveSettings(Context.Next.Settings, filePath);
|
|
||||||
|
|
||||||
// TODO: If the client configuration happens while the application is already running, the new configuration should first
|
|
||||||
// be loaded and then the user should have the option to terminate!
|
|
||||||
// -> Introduce flag in Context, e.g. AskForTermination?
|
|
||||||
ActionRequired?.Invoke(args);
|
ActionRequired?.Invoke(args);
|
||||||
|
|
||||||
logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} after successful client configuration.");
|
logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} after successful client configuration.");
|
||||||
|
|
||||||
if (args.AbortStartup)
|
if (args.AbortStartup)
|
||||||
{
|
{
|
||||||
result = OperationResult.Aborted;
|
return OperationResult.Aborted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: If the client configuration happens while the application is already running, the new configuration should first
|
||||||
|
// be loaded and then the user should have the option to terminate!
|
||||||
|
// -> Introduce flag in Context, e.g. AskForTermination?
|
||||||
|
}
|
||||||
|
|
||||||
|
return OperationResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogOperationResult(OperationResult result)
|
private void LogOperationResult(OperationResult result)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using SafeExamBrowser.Contracts.I18n;
|
||||||
|
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
|
||||||
|
|
||||||
|
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||||
|
{
|
||||||
|
internal class InvalidPasswordMessageArgs : MessageEventArgs
|
||||||
|
{
|
||||||
|
internal InvalidPasswordMessageArgs()
|
||||||
|
{
|
||||||
|
Icon = MessageBoxIcon.Error;
|
||||||
|
Message = TextKey.MessageBox_InvalidPasswordError;
|
||||||
|
Title = TextKey.MessageBox_InvalidPasswordErrorTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -91,6 +91,7 @@
|
||||||
<Compile Include="Operations\ConfigurationOperation.cs" />
|
<Compile Include="Operations\ConfigurationOperation.cs" />
|
||||||
<Compile Include="Operations\Events\ConfigurationCompletedEventArgs.cs" />
|
<Compile Include="Operations\Events\ConfigurationCompletedEventArgs.cs" />
|
||||||
<Compile Include="Operations\Events\InvalidDataMessageArgs.cs" />
|
<Compile Include="Operations\Events\InvalidDataMessageArgs.cs" />
|
||||||
|
<Compile Include="Operations\Events\InvalidPasswordMessageArgs.cs" />
|
||||||
<Compile Include="Operations\Events\MessageEventArgs.cs" />
|
<Compile Include="Operations\Events\MessageEventArgs.cs" />
|
||||||
<Compile Include="Operations\Events\NotSupportedMessageArgs.cs" />
|
<Compile Include="Operations\Events\NotSupportedMessageArgs.cs" />
|
||||||
<Compile Include="Operations\Events\PasswordRequiredEventArgs.cs" />
|
<Compile Include="Operations\Events\PasswordRequiredEventArgs.cs" />
|
||||||
|
|
Loading…
Reference in a new issue