From 140abe789eb370fb020545d69779b6a6b31d185d Mon Sep 17 00:00:00 2001 From: dbuechel Date: Fri, 14 Dec 2018 15:30:10 +0100 Subject: [PATCH] SEBWIN-221: Simplified configuration API for loading and client configuration. --- .../ConfigurationRepository.cs | 27 ++++++------ .../DataFormats/BinaryFormat.cs | 41 +++++++++++-------- .../Configuration/IConfigurationRepository.cs | 6 +-- .../Configuration/LoadStatus.cs | 6 +-- .../Configuration/SaveStatus.cs | 8 ++++ SafeExamBrowser.Contracts/I18n/TextKey.cs | 2 + SafeExamBrowser.I18n/Text.xml | 6 +++ .../Operations/ConfigurationOperation.cs | 41 +++++++++++-------- .../ClientConfigurationErrorMessageArgs.cs | 23 +++++++++++ .../SafeExamBrowser.Runtime.csproj | 1 + 10 files changed, 107 insertions(+), 54 deletions(-) create mode 100644 SafeExamBrowser.Runtime/Operations/Events/ClientConfigurationErrorMessageArgs.cs diff --git a/SafeExamBrowser.Configuration/ConfigurationRepository.cs b/SafeExamBrowser.Configuration/ConfigurationRepository.cs index 314f6339..19cd57de 100644 --- a/SafeExamBrowser.Configuration/ConfigurationRepository.cs +++ b/SafeExamBrowser.Configuration/ConfigurationRepository.cs @@ -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 rawData) + private LoadStatus TryParseData(Stream data, out EncryptionParameters encryption, out Format format, out IDictionary rawData, PasswordParameters password = null) { var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data)); var status = LoadStatus.NotSupported; diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs index 184cb202..93a9f381 100644 --- a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs +++ b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs @@ -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); diff --git a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs index 8929f668..5d05d47b 100644 --- a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs +++ b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs @@ -19,9 +19,9 @@ namespace SafeExamBrowser.Contracts.Configuration public interface IConfigurationRepository { /// - /// Saves the given resource as local client configuration. + /// Attempts to save the given resource as local client configuration. /// - void ConfigureClientWith(Uri resource, EncryptionParameters encryption = null); + SaveStatus ConfigureClientWith(Uri resource, PasswordParameters password = null); /// /// Initializes the global configuration information for the currently running application instance. @@ -51,7 +51,7 @@ namespace SafeExamBrowser.Contracts.Configuration /// /// Attempts to load settings from the specified resource. /// - 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); /// /// Attempts to save settings according to the specified parameters. diff --git a/SafeExamBrowser.Contracts/Configuration/LoadStatus.cs b/SafeExamBrowser.Contracts/Configuration/LoadStatus.cs index 1c0292f3..c48bae82 100644 --- a/SafeExamBrowser.Contracts/Configuration/LoadStatus.cs +++ b/SafeExamBrowser.Contracts/Configuration/LoadStatus.cs @@ -29,17 +29,17 @@ namespace SafeExamBrowser.Contracts.Configuration NotSupported, /// - /// Indicates that a password is needed in order to decrypt the settings. + /// Indicates that a password is needed in order to decrypt the configuration. /// PasswordNeeded, /// - /// The settings were loaded successfully. + /// The configuration was loaded successfully. /// Success, /// - /// An unexpected error occurred while trying to load the settings. + /// An unexpected error occurred while trying to load the configuration. /// UnexpectedError } diff --git a/SafeExamBrowser.Contracts/Configuration/SaveStatus.cs b/SafeExamBrowser.Contracts/Configuration/SaveStatus.cs index 480787ef..86b12e6e 100644 --- a/SafeExamBrowser.Contracts/Configuration/SaveStatus.cs +++ b/SafeExamBrowser.Contracts/Configuration/SaveStatus.cs @@ -13,6 +13,14 @@ namespace SafeExamBrowser.Contracts.Configuration /// public enum SaveStatus { + /// + /// The configuration was saved successfully. + /// + Success, + /// + /// An unexpected error occurred while trying to save the configuration. + /// + UnexpectedError } } diff --git a/SafeExamBrowser.Contracts/I18n/TextKey.cs b/SafeExamBrowser.Contracts/I18n/TextKey.cs index 71ad3213..ae9f0245 100644 --- a/SafeExamBrowser.Contracts/I18n/TextKey.cs +++ b/SafeExamBrowser.Contracts/I18n/TextKey.cs @@ -18,6 +18,8 @@ namespace SafeExamBrowser.Contracts.I18n LogWindow_Title, MessageBox_ApplicationError, MessageBox_ApplicationErrorTitle, + MessageBox_ClientConfigurationError, + MessageBox_ClientConfigurationErrorTitle, MessageBox_ClientConfigurationQuestion, MessageBox_ClientConfigurationQuestionTitle, MessageBox_ConfigurationDownloadError, diff --git a/SafeExamBrowser.I18n/Text.xml b/SafeExamBrowser.I18n/Text.xml index 37eb700b..810b609e 100644 --- a/SafeExamBrowser.I18n/Text.xml +++ b/SafeExamBrowser.I18n/Text.xml @@ -12,6 +12,12 @@ Application Error + + The local client configuration has failed! Please consult the application log for more information. The application will now shut down... + + + Client Configuration Error + 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? diff --git a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs index ef62713b..c5c3c5d4 100644 --- a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs @@ -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) { diff --git a/SafeExamBrowser.Runtime/Operations/Events/ClientConfigurationErrorMessageArgs.cs b/SafeExamBrowser.Runtime/Operations/Events/ClientConfigurationErrorMessageArgs.cs new file mode 100644 index 00000000..18f52dea --- /dev/null +++ b/SafeExamBrowser.Runtime/Operations/Events/ClientConfigurationErrorMessageArgs.cs @@ -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; + } + } +} diff --git a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj index 1c1f8682..80687c57 100644 --- a/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj +++ b/SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj @@ -89,6 +89,7 @@ +