SEBWIN-221: Simplified configuration API for loading and client configuration.

This commit is contained in:
dbuechel 2018-12-14 15:30:10 +01:00
parent 4b634d8e99
commit 140abe789e
10 changed files with 107 additions and 54 deletions

View file

@ -55,21 +55,20 @@ namespace SafeExamBrowser.Configuration
this.programVersion = programVersion ?? string.Empty;
}
public void ConfigureClientWith(Uri resource, EncryptionParameters encryption = null)
public SaveStatus ConfigureClientWith(Uri resource, PasswordParameters password = null)
{
logger.Info($"Attempting to configure local client settings from '{resource}'...");
try
{
TryLoadData(resource, out Stream stream);
TryLoadData(resource, out var stream);
using (stream)
{
// TODO:
//TryParseData(stream, encryption, out _, out _, out var data);
//HandleIdentityCertificates(data);
TryParseData(stream, out var encryption, out var format, out var data, password);
HandleIdentityCertificates(data);
// Save configuration data as local client config under %APPDATA%!
// TODO: Encrypt and save configuration data as local client config under %APPDATA%!
// -> New key will determine whether to use default password or current settings password!
// -> "clientConfigEncryptUsingSettingsPassword"
// -> Default settings password for local client configuration appears to be string.Empty -> passwords.SettingsPassword
@ -77,10 +76,14 @@ namespace SafeExamBrowser.Configuration
}
logger.Info($"Successfully configured local client settings with '{resource}'.");
return SaveStatus.Success;
}
catch (Exception e)
{
logger.Error($"Unexpected error while trying to configure local client settings '{resource}'!", e);
return SaveStatus.UnexpectedError;
}
}
@ -166,17 +169,15 @@ namespace SafeExamBrowser.Configuration
dataResources.Add(dataResource);
}
public LoadStatus TryLoadSettings(Uri resource, PasswordParameters password, out EncryptionParameters encryption, out Format format, out Settings settings)
public LoadStatus TryLoadSettings(Uri resource, out Settings settings, PasswordParameters password = null)
{
logger.Info($"Attempting to load '{resource}'...");
encryption = default(EncryptionParameters);
format = default(Format);
settings = LoadDefaultSettings();
try
{
var status = TryLoadData(resource, out Stream stream);
var status = TryLoadData(resource, out var stream);
using (stream)
{
@ -185,7 +186,7 @@ namespace SafeExamBrowser.Configuration
return status;
}
status = TryParseData(stream, password, out encryption, out format, out var data);
status = TryParseData(stream, out _, out _, out var data, password);
if (status == LoadStatus.Success)
{
@ -219,7 +220,7 @@ namespace SafeExamBrowser.Configuration
foreach (var certificate in certificates)
{
var isIdentity = certificate.TryGetValue("type", out object t) && t is int type && type == IDENTITY_CERTIFICATE;
var isIdentity = certificate.TryGetValue("type", out var o) && o is int type && type == IDENTITY_CERTIFICATE;
var hasData = certificate.TryGetValue("certificateData", out value);
if (isIdentity && hasData && value is byte[] certificateData)
@ -278,7 +279,7 @@ namespace SafeExamBrowser.Configuration
return status;
}
private LoadStatus TryParseData(Stream data, PasswordParameters password, out EncryptionParameters encryption, out Format format, out IDictionary<string, object> rawData)
private LoadStatus TryParseData(Stream data, out EncryptionParameters encryption, out Format format, out IDictionary<string, object> rawData, PasswordParameters password = null)
{
var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data));
var status = LoadStatus.NotSupported;

View file

@ -60,7 +60,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
return false;
}
public ParseResult TryParse(Stream data, PasswordParameters password)
public ParseResult TryParse(Stream data, PasswordParameters password = null)
{
var prefix = ParsePrefix(data);
var success = TryDetermineFormat(prefix, out FormatType format);
@ -94,29 +94,36 @@ namespace SafeExamBrowser.Configuration.DataFormats
return new ParseResult { Status = LoadStatus.InvalidData };
}
private ParseResult ParsePasswordBlock(Stream data, FormatType format, PasswordParameters password)
private ParseResult ParsePasswordBlock(Stream data, FormatType format, PasswordParameters password = null)
{
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
var encryptionParams = new PasswordParameters();
var result = new ParseResult();
if (format == FormatType.PasswordConfigureClient)
if (password != null)
{
encryptionParams.Password = password.IsHash ? password.Password : hashAlgorithm.GenerateHashFor(password.Password);
encryptionParams.IsHash = true;
if (format == FormatType.PasswordConfigureClient)
{
encryptionParams.Password = password.IsHash ? password.Password : hashAlgorithm.GenerateHashFor(password.Password);
encryptionParams.IsHash = true;
}
else
{
encryptionParams.Password = password.Password;
encryptionParams.IsHash = password.IsHash;
}
result.Status = encryption.Decrypt(data, encryptionParams.Password, out var decrypted);
if (result.Status == LoadStatus.Success)
{
result = ParsePlainDataBlock(decrypted);
result.Encryption = encryptionParams;
}
}
else
{
encryptionParams.Password = password.Password;
encryptionParams.IsHash = password.IsHash;
}
result.Status = encryption.Decrypt(data, encryptionParams.Password, out var decrypted);
if (result.Status == LoadStatus.Success)
{
result = ParsePlainDataBlock(decrypted);
result.Encryption = encryptionParams;
result.Status = LoadStatus.PasswordNeeded;
}
return result;
@ -134,7 +141,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
return xmlFormat.TryParse(data);
}
private ParseResult ParsePublicKeyHashBlock(Stream data, PasswordParameters password)
private ParseResult ParsePublicKeyHashBlock(Stream data, PasswordParameters password = null)
{
var encryption = new PublicKeyHashEncryption(logger.CloneFor(nameof(PublicKeyHashEncryption)));
var result = new ParseResult();
@ -155,7 +162,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
return result;
}
private ParseResult ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, PasswordParameters password)
private ParseResult ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, PasswordParameters password = null)
{
var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
var encryption = new PublicKeyHashWithSymmetricKeyEncryption(logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption)), passwordEncryption);

View file

@ -19,9 +19,9 @@ namespace SafeExamBrowser.Contracts.Configuration
public interface IConfigurationRepository
{
/// <summary>
/// Saves the given resource as local client configuration.
/// Attempts to save the given resource as local client configuration.
/// </summary>
void ConfigureClientWith(Uri resource, EncryptionParameters encryption = null);
SaveStatus ConfigureClientWith(Uri resource, PasswordParameters password = null);
/// <summary>
/// Initializes the global configuration information for the currently running application instance.
@ -51,7 +51,7 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary>
/// Attempts to load settings from the specified resource.
/// </summary>
LoadStatus TryLoadSettings(Uri resource, PasswordParameters password, out EncryptionParameters encryption, out Format format, out Settings.Settings settings);
LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, PasswordParameters password = null);
/// <summary>
/// Attempts to save settings according to the specified parameters.

View file

@ -29,17 +29,17 @@ namespace SafeExamBrowser.Contracts.Configuration
NotSupported,
/// <summary>
/// Indicates that a password is needed in order to decrypt the settings.
/// Indicates that a password is needed in order to decrypt the configuration.
/// </summary>
PasswordNeeded,
/// <summary>
/// The settings were loaded successfully.
/// The configuration was loaded successfully.
/// </summary>
Success,
/// <summary>
/// An unexpected error occurred while trying to load the settings.
/// An unexpected error occurred while trying to load the configuration.
/// </summary>
UnexpectedError
}

View file

@ -13,6 +13,14 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary>
public enum SaveStatus
{
/// <summary>
/// The configuration was saved successfully.
/// </summary>
Success,
/// <summary>
/// An unexpected error occurred while trying to save the configuration.
/// </summary>
UnexpectedError
}
}

View file

@ -18,6 +18,8 @@ namespace SafeExamBrowser.Contracts.I18n
LogWindow_Title,
MessageBox_ApplicationError,
MessageBox_ApplicationErrorTitle,
MessageBox_ClientConfigurationError,
MessageBox_ClientConfigurationErrorTitle,
MessageBox_ClientConfigurationQuestion,
MessageBox_ClientConfigurationQuestionTitle,
MessageBox_ConfigurationDownloadError,

View file

@ -12,6 +12,12 @@
<Entry key="MessageBox_ApplicationErrorTitle">
Application Error
</Entry>
<Entry key="MessageBox_ClientConfigurationError">
The local client configuration has failed! Please consult the application log for more information. The application will now shut down...
</Entry>
<Entry key="MessageBox_ClientConfigurationErrorTitle">
Client Configuration Error
</Entry>
<Entry key="MessageBox_ClientConfigurationQuestion">
The client configuration has been saved and will be used when you start the application the next time. Do you want to quit for now?
</Entry>

View file

@ -11,7 +11,6 @@ using System.IO;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
@ -114,14 +113,14 @@ namespace SafeExamBrowser.Runtime.Operations
private OperationResult LoadSettings(Uri uri)
{
var passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true };
var status = configuration.TryLoadSettings(uri, passwordParams, out var encryption, out var format, out var settings);
var status = configuration.TryLoadSettings(uri, out var settings, passwordParams);
if (status == LoadStatus.PasswordNeeded && Context.Current?.Settings.AdminPasswordHash != null)
{
passwordParams.Password = Context.Current.Settings.AdminPasswordHash;
passwordParams.IsHash = true;
status = configuration.TryLoadSettings(uri, passwordParams, out encryption, out format, out settings);
status = configuration.TryLoadSettings(uri, out settings, passwordParams);
}
for (int attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
@ -138,15 +137,15 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Aborted;
}
status = configuration.TryLoadSettings(uri, passwordParams, out encryption, out format, out settings);
status = configuration.TryLoadSettings(uri, out settings, passwordParams);
}
Context.Next.Settings = settings;
return HandleLoadResult(uri, settings, status, passwordParams, encryption, format);
return HandleLoadResult(uri, settings, status, passwordParams);
}
private OperationResult HandleLoadResult(Uri uri, Settings settings, LoadStatus status, PasswordParameters password, EncryptionParameters encryption, Format format)
private OperationResult HandleLoadResult(Uri uri, Settings settings, LoadStatus status, PasswordParameters password)
{
if (status == LoadStatus.LoadWithBrowser)
{
@ -155,7 +154,7 @@ namespace SafeExamBrowser.Runtime.Operations
if (status == LoadStatus.Success && settings.ConfigurationMode == ConfigurationMode.ConfigureClient)
{
return HandleClientConfiguration(uri, password, encryption, format);
return HandleClientConfiguration(uri, password);
}
if (status == LoadStatus.Success)
@ -176,7 +175,7 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success;
}
private OperationResult HandleClientConfiguration(Uri resource, PasswordParameters password, EncryptionParameters encryption, Format format)
private OperationResult HandleClientConfiguration(Uri resource, PasswordParameters password)
{
var isAppDataFile = Path.GetFullPath(resource.AbsolutePath).Equals(AppDataFile, StringComparison.OrdinalIgnoreCase);
var isProgramDataFile = Path.GetFullPath(resource.AbsolutePath).Equals(ProgramDataFile, StringComparison.OrdinalIgnoreCase);
@ -202,11 +201,19 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Info("Authentication is not required.");
}
configuration.ConfigureClientWith(resource, encryption);
var status = configuration.ConfigureClientWith(resource, password);
if (status != SaveStatus.Success)
{
logger.Error($"Client configuration failed with status '{status}'!");
ActionRequired?.Invoke(new ClientConfigurationErrorMessageArgs());
return OperationResult.Failed;
}
if (isFirstSession)
{
var result = HandleClientConfigurationSuccess();
var result = HandleClientConfigurationOnStartup();
if (result != OperationResult.Success)
{
@ -270,21 +277,19 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success;
}
else
{
logger.Info("Authentication has failed!");
ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
return OperationResult.Failed;
}
logger.Info("Authentication has failed!");
ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
return OperationResult.Failed;
}
private OperationResult HandleClientConfigurationSuccess()
private OperationResult HandleClientConfigurationOnStartup()
{
var args = new ConfigurationCompletedEventArgs();
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")} startup after successful client configuration.");
if (args.AbortStartup)
{

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
namespace SafeExamBrowser.Runtime.Operations.Events
{
internal class ClientConfigurationErrorMessageArgs : MessageEventArgs
{
internal ClientConfigurationErrorMessageArgs()
{
Icon = MessageBoxIcon.Error;
Message = TextKey.MessageBox_ClientConfigurationError;
Title = TextKey.MessageBox_ClientConfigurationErrorTitle;
}
}
}

View file

@ -89,6 +89,7 @@
<Compile Include="Operations\ClientOperation.cs" />
<Compile Include="Operations\ClientTerminationOperation.cs" />
<Compile Include="Operations\ConfigurationOperation.cs" />
<Compile Include="Operations\Events\ClientConfigurationErrorMessageArgs.cs" />
<Compile Include="Operations\Events\ConfigurationCompletedEventArgs.cs" />
<Compile Include="Operations\Events\InvalidDataMessageArgs.cs" />
<Compile Include="Operations\Events\InvalidPasswordMessageArgs.cs" />