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; 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}'..."); logger.Info($"Attempting to configure local client settings from '{resource}'...");
try try
{ {
TryLoadData(resource, out Stream stream); TryLoadData(resource, out var stream);
using (stream) using (stream)
{ {
// TODO: TryParseData(stream, out var encryption, out var format, out var data, password);
//TryParseData(stream, encryption, out _, out _, out var data); HandleIdentityCertificates(data);
//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! // -> New key will determine whether to use default password or current settings password!
// -> "clientConfigEncryptUsingSettingsPassword" // -> "clientConfigEncryptUsingSettingsPassword"
// -> Default settings password for local client configuration appears to be string.Empty -> passwords.SettingsPassword // -> 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}'."); logger.Info($"Successfully configured local client settings with '{resource}'.");
return SaveStatus.Success;
} }
catch (Exception e) catch (Exception e)
{ {
logger.Error($"Unexpected error while trying to configure local client settings '{resource}'!", 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); 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}'..."); logger.Info($"Attempting to load '{resource}'...");
encryption = default(EncryptionParameters);
format = default(Format);
settings = LoadDefaultSettings(); settings = LoadDefaultSettings();
try try
{ {
var status = TryLoadData(resource, out Stream stream); var status = TryLoadData(resource, out var stream);
using (stream) using (stream)
{ {
@ -185,7 +186,7 @@ namespace SafeExamBrowser.Configuration
return status; 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) if (status == LoadStatus.Success)
{ {
@ -219,7 +220,7 @@ namespace SafeExamBrowser.Configuration
foreach (var certificate in certificates) 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); var hasData = certificate.TryGetValue("certificateData", out value);
if (isIdentity && hasData && value is byte[] certificateData) if (isIdentity && hasData && value is byte[] certificateData)
@ -278,7 +279,7 @@ namespace SafeExamBrowser.Configuration
return status; 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 dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data));
var status = LoadStatus.NotSupported; var status = LoadStatus.NotSupported;

View file

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

View file

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

View file

@ -29,17 +29,17 @@ namespace SafeExamBrowser.Contracts.Configuration
NotSupported, NotSupported,
/// <summary> /// <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> /// </summary>
PasswordNeeded, PasswordNeeded,
/// <summary> /// <summary>
/// The settings were loaded successfully. /// The configuration was loaded successfully.
/// </summary> /// </summary>
Success, Success,
/// <summary> /// <summary>
/// An unexpected error occurred while trying to load the settings. /// An unexpected error occurred while trying to load the configuration.
/// </summary> /// </summary>
UnexpectedError UnexpectedError
} }

View file

@ -13,6 +13,14 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary> /// </summary>
public enum SaveStatus 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, LogWindow_Title,
MessageBox_ApplicationError, MessageBox_ApplicationError,
MessageBox_ApplicationErrorTitle, MessageBox_ApplicationErrorTitle,
MessageBox_ClientConfigurationError,
MessageBox_ClientConfigurationErrorTitle,
MessageBox_ClientConfigurationQuestion, MessageBox_ClientConfigurationQuestion,
MessageBox_ClientConfigurationQuestionTitle, MessageBox_ClientConfigurationQuestionTitle,
MessageBox_ConfigurationDownloadError, MessageBox_ConfigurationDownloadError,

View file

@ -12,6 +12,12 @@
<Entry key="MessageBox_ApplicationErrorTitle"> <Entry key="MessageBox_ApplicationErrorTitle">
Application Error Application Error
</Entry> </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"> <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? 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> </Entry>

View file

@ -11,7 +11,6 @@ using System.IO;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography; using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.Core.OperationModel.Events;
@ -114,14 +113,14 @@ namespace SafeExamBrowser.Runtime.Operations
private OperationResult LoadSettings(Uri uri) private OperationResult LoadSettings(Uri uri)
{ {
var passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true }; 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) if (status == LoadStatus.PasswordNeeded && Context.Current?.Settings.AdminPasswordHash != null)
{ {
passwordParams.Password = Context.Current.Settings.AdminPasswordHash; passwordParams.Password = Context.Current.Settings.AdminPasswordHash;
passwordParams.IsHash = true; 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++) for (int attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
@ -138,15 +137,15 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Aborted; 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; 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) if (status == LoadStatus.LoadWithBrowser)
{ {
@ -155,7 +154,7 @@ namespace SafeExamBrowser.Runtime.Operations
if (status == LoadStatus.Success && settings.ConfigurationMode == ConfigurationMode.ConfigureClient) if (status == LoadStatus.Success && settings.ConfigurationMode == ConfigurationMode.ConfigureClient)
{ {
return HandleClientConfiguration(uri, password, encryption, format); return HandleClientConfiguration(uri, password);
} }
if (status == LoadStatus.Success) if (status == LoadStatus.Success)
@ -176,7 +175,7 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success; 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 isAppDataFile = Path.GetFullPath(resource.AbsolutePath).Equals(AppDataFile, StringComparison.OrdinalIgnoreCase);
var isProgramDataFile = Path.GetFullPath(resource.AbsolutePath).Equals(ProgramDataFile, 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."); 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) if (isFirstSession)
{ {
var result = HandleClientConfigurationSuccess(); var result = HandleClientConfigurationOnStartup();
if (result != OperationResult.Success) if (result != OperationResult.Success)
{ {
@ -270,21 +277,19 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success; 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(); var args = new ConfigurationCompletedEventArgs();
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")} startup after successful client configuration.");
if (args.AbortStartup) 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\ClientOperation.cs" />
<Compile Include="Operations\ClientTerminationOperation.cs" /> <Compile Include="Operations\ClientTerminationOperation.cs" />
<Compile Include="Operations\ConfigurationOperation.cs" /> <Compile Include="Operations\ConfigurationOperation.cs" />
<Compile Include="Operations\Events\ClientConfigurationErrorMessageArgs.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\InvalidPasswordMessageArgs.cs" />