From 243d32879e371c6cb2adc11142bf74450770f028 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Wed, 28 Nov 2018 15:43:30 +0100 Subject: [PATCH] SEBWIN-221: Found better solution for binary format (separated encryption code) and implemented scaffolding for XML parsing. --- .../ConfigurationRepository.cs | 8 +- .../DataFormats/BinaryFormat.PlainData.cs | 48 -- .../DataFormats/BinaryFormat.cs | 69 +- .../PasswordEncryption.cs} | 68 +- .../PublicKeyHashEncryption.cs} | 13 +- ...ublicKeyHashWithSymmetricKeyEncryption.cs} | 13 +- .../DataFormats/XmlFormat.cs | 180 ++++- .../HashAlgorithm.cs | 31 + .../SafeExamBrowser.Configuration.csproj | 18 +- .../Configuration/IConfigurationRepository.cs | 2 +- .../Configuration/IDataFormat.cs | 2 +- .../Configuration/IHashAlgorithm.cs | 21 + .../Configuration/Settings/Settings.cs | 5 + .../SafeExamBrowser.Contracts.csproj | 1 + .../Operations/ConfigurationOperationTests.cs | 642 +++++++++--------- SafeExamBrowser.Runtime/CompositionRoot.cs | 2 +- .../Operations/ConfigurationOperation.cs | 22 +- .../SessionInitializationOperation.cs | 9 + 18 files changed, 700 insertions(+), 454 deletions(-) delete mode 100644 SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PlainData.cs rename SafeExamBrowser.Configuration/DataFormats/{BinaryFormat.Password.cs => Cryptography/PasswordEncryption.cs} (69%) rename SafeExamBrowser.Configuration/DataFormats/{BinaryFormat.PublicKeyHash.cs => Cryptography/PublicKeyHashEncryption.cs} (57%) rename SafeExamBrowser.Configuration/DataFormats/{BinaryFormat.PublicKeyHashWithSymmetricKey.cs => Cryptography/PublicKeyHashWithSymmetricKeyEncryption.cs} (56%) create mode 100644 SafeExamBrowser.Configuration/HashAlgorithm.cs create mode 100644 SafeExamBrowser.Contracts/Configuration/IHashAlgorithm.cs diff --git a/SafeExamBrowser.Configuration/ConfigurationRepository.cs b/SafeExamBrowser.Configuration/ConfigurationRepository.cs index c566a060..f746e212 100644 --- a/SafeExamBrowser.Configuration/ConfigurationRepository.cs +++ b/SafeExamBrowser.Configuration/ConfigurationRepository.cs @@ -124,7 +124,7 @@ namespace SafeExamBrowser.Configuration resourceLoaders.Add(resourceLoader); } - public LoadStatus TryLoadSettings(Uri resource, out Settings settings, string password = null) + public LoadStatus TryLoadSettings(Uri resource, out Settings settings, string password = null, bool passwordIsHash = false) { settings = default(Settings); @@ -141,7 +141,7 @@ namespace SafeExamBrowser.Configuration case LoadStatus.LoadWithBrowser: return HandleBrowserResource(resource, out settings); case LoadStatus.Success: - return TryParseData(data, out settings, password); + return TryParseData(data, out settings, password, passwordIsHash); } } @@ -175,7 +175,7 @@ namespace SafeExamBrowser.Configuration return status; } - private LoadStatus TryParseData(Stream data, out Settings settings, string password) + private LoadStatus TryParseData(Stream data, out Settings settings, string password = null, bool passwordIsHash = false) { var status = LoadStatus.NotSupported; var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data)); @@ -184,7 +184,7 @@ namespace SafeExamBrowser.Configuration if (dataFormat != null) { - status = dataFormat.TryParse(data, out settings, password); + status = dataFormat.TryParse(data, out settings, password, passwordIsHash); logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {status}."); } else diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PlainData.cs b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PlainData.cs deleted file mode 100644 index f811a4bd..00000000 --- a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PlainData.cs +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -using System.IO; -using System.Text; -using SafeExamBrowser.Contracts.Configuration; -using SafeExamBrowser.Contracts.Configuration.Settings; - -namespace SafeExamBrowser.Configuration.DataFormats -{ - public partial class BinaryFormat - { - private LoadStatus ParsePlainData(Stream data, out Settings settings) - { - if (compressor.IsCompressed(data)) - { - data = compressor.Decompress(data); - } - - var buffer = new byte[4096]; - var bytesRead = 0; - var xmlBuilder = new StringBuilder(); - - data.Seek(0, SeekOrigin.Begin); - - do - { - bytesRead = data.Read(buffer, 0, buffer.Length); - xmlBuilder.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); - } while (bytesRead > 0); - - var xml = xmlBuilder.ToString(); - - // TODO: Parse XML data... - - settings = new Settings(); - settings.Browser.AllowAddressBar = true; - settings.Browser.AllowConfigurationDownloads = true; - - return LoadStatus.Success; - } - } -} diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs index 0e96d1a6..ee01e132 100644 --- a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs +++ b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs @@ -9,6 +9,7 @@ using System; using System.IO; using System.Text; +using SafeExamBrowser.Configuration.DataFormats.Cryptography; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Logging; @@ -20,11 +21,13 @@ namespace SafeExamBrowser.Configuration.DataFormats private const int PREFIX_LENGTH = 4; private IDataCompressor compressor; - private ILogger logger; + private IHashAlgorithm hashAlgorithm; + private IModuleLogger logger; - public BinaryFormat(IDataCompressor compressor, ILogger logger) + public BinaryFormat(IDataCompressor compressor, IHashAlgorithm hashAlgorithm, IModuleLogger logger) { this.compressor = compressor; + this.hashAlgorithm = hashAlgorithm; this.logger = logger; } @@ -54,7 +57,7 @@ namespace SafeExamBrowser.Configuration.DataFormats return false; } - public LoadStatus TryParse(Stream data, out Settings settings, string password = null) + public LoadStatus TryParse(Stream data, out Settings settings, string password = null, bool passwordIsHash = false) { var prefix = ParsePrefix(data); var success = TryDetermineFormat(prefix, out FormatType format); @@ -69,19 +72,19 @@ namespace SafeExamBrowser.Configuration.DataFormats } data = new SubStream(data, PREFIX_LENGTH, data.Length - PREFIX_LENGTH); + logger.Debug($"Attempting to parse '{data}' with format '{prefix}'..."); - // TODO: Try to abstract (Parser -> Binary, Xml, ...; DataBlock -> Password, PlainData, ...) once fully implemented! switch (format) { case FormatType.Password: case FormatType.PasswordConfigureClient: - return ParsePassword(data, format, out settings, password); + return ParsePasswordBlock(data, format, out settings, password, passwordIsHash); case FormatType.PlainData: - return ParsePlainData(data, out settings); + return ParsePlainDataBlock(data, out settings); case FormatType.PublicKeyHash: - return ParsePublicKeyHash(data, out settings, password); - case FormatType.PublicKeyHashSymmetricKey: - return ParsePublicKeyHashWithSymmetricKey(data, out settings, password); + return ParsePublicKeyHashBlock(data, out settings, password, passwordIsHash); + case FormatType.PublicKeyHashWithSymmetricKey: + return ParsePublicKeyHashWithSymmetricKeyBlock(data, out settings, password, passwordIsHash); } } @@ -90,6 +93,50 @@ namespace SafeExamBrowser.Configuration.DataFormats return LoadStatus.InvalidData; } + private LoadStatus ParsePasswordBlock(Stream data, FormatType format, out Settings settings, string password, bool passwordIsHash) + { + var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption))); + + settings = default(Settings); + + // TODO: Check whether the hashing (bool passwordIsHash) can be extracted and moved to ConfigurationOperation! + if (format == FormatType.PasswordConfigureClient && !passwordIsHash) + { + password = hashAlgorithm.GenerateHashFor(password); + } + + var status = encryption.Decrypt(data, out Stream decrypted, password); + + if (status == LoadStatus.Success) + { + return ParsePlainDataBlock(decrypted, out settings); + } + + return status; + } + + private LoadStatus ParsePlainDataBlock(Stream data, out Settings settings) + { + var xmlFormat = new XmlFormat(logger.CloneFor(nameof(XmlFormat))); + + if (compressor.IsCompressed(data)) + { + data = compressor.Decompress(data); + } + + return xmlFormat.TryParse(data, out settings); + } + + private LoadStatus ParsePublicKeyHashBlock(Stream data, out Settings settings, string password, bool passwordIsHash) + { + throw new NotImplementedException(); + } + + private LoadStatus ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, out Settings settings, string password, bool passwordIsHash) + { + throw new NotImplementedException(); + } + private string ParsePrefix(Stream data) { var prefixData = new byte[PREFIX_LENGTH]; @@ -126,7 +173,7 @@ namespace SafeExamBrowser.Configuration.DataFormats format = FormatType.PublicKeyHash; return true; case "phsk": - format = FormatType.PublicKeyHashSymmetricKey; + format = FormatType.PublicKeyHashWithSymmetricKey; return true; } @@ -139,7 +186,7 @@ namespace SafeExamBrowser.Configuration.DataFormats PasswordConfigureClient, PlainData, PublicKeyHash, - PublicKeyHashSymmetricKey + PublicKeyHashWithSymmetricKey } } } diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.Password.cs b/SafeExamBrowser.Configuration/DataFormats/Cryptography/PasswordEncryption.cs similarity index 69% rename from SafeExamBrowser.Configuration/DataFormats/BinaryFormat.Password.cs rename to SafeExamBrowser.Configuration/DataFormats/Cryptography/PasswordEncryption.cs index 000e5871..b8dedd08 100644 --- a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.Password.cs +++ b/SafeExamBrowser.Configuration/DataFormats/Cryptography/PasswordEncryption.cs @@ -10,13 +10,12 @@ using System; using System.IO; using System.Linq; using System.Security.Cryptography; -using System.Text; using SafeExamBrowser.Contracts.Configuration; -using SafeExamBrowser.Contracts.Configuration.Settings; +using SafeExamBrowser.Contracts.Logging; -namespace SafeExamBrowser.Configuration.DataFormats +namespace SafeExamBrowser.Configuration.DataFormats.Cryptography { - public partial class BinaryFormat + internal class PasswordEncryption { private const int BLOCK_SIZE = 16; private const int HEADER_SIZE = 2; @@ -26,9 +25,21 @@ namespace SafeExamBrowser.Configuration.DataFormats private const int SALT_SIZE = 8; private const int VERSION = 0x2; - private LoadStatus ParsePassword(Stream data, FormatType format, out Settings settings, string password = null) + private ILogger logger; + + internal PasswordEncryption(ILogger logger) { - settings = default(Settings); + this.logger = logger; + } + + internal Stream Encrypt(Stream data, string password) + { + throw new NotImplementedException(); + } + + internal LoadStatus Decrypt(Stream data, out Stream decrypted, string password) + { + decrypted = default(Stream); if (password == null) { @@ -39,13 +50,9 @@ namespace SafeExamBrowser.Configuration.DataFormats if (version != VERSION || options != OPTIONS) { - return FailForInvalidPasswordHeader(version, options); - } + logger.Error($"Invalid encryption header! Expected: [{VERSION},{OPTIONS},...] - Actual: [{version},{options},...]"); - if (format == FormatType.PasswordConfigureClient) - { - // TODO: Shouldn't this not only be done for admin password, and not settings password?!? - password = GeneratePasswordHash(password); + return LoadStatus.InvalidData; } var (authenticationKey, encryptionKey) = GenerateKeys(data, password); @@ -53,13 +60,14 @@ namespace SafeExamBrowser.Configuration.DataFormats if (!computedHmac.SequenceEqual(originalHmac)) { - return FailForInvalidPasswordHmac(); + logger.Warn($"The authentication failed due to an invalid password or corrupted data!"); + + return LoadStatus.PasswordNeeded; } - using (var plainData = Decrypt(data, encryptionKey, originalHmac.Length)) - { - return ParsePlainData(plainData, out settings); - } + decrypted = Decrypt(data, encryptionKey, originalHmac.Length); + + return LoadStatus.Success; } private (int version, int options) ParseHeader(Stream data) @@ -72,25 +80,6 @@ namespace SafeExamBrowser.Configuration.DataFormats return (version, options); } - private LoadStatus FailForInvalidPasswordHeader(int version, int options) - { - logger.Error($"Invalid encryption header! Expected: [{VERSION},{OPTIONS},...] - Actual: [{version},{options},...]"); - - return LoadStatus.InvalidData; - } - - private string GeneratePasswordHash(string input) - { - using (var algorithm = new SHA256Managed()) - { - var bytes = Encoding.UTF8.GetBytes(input); - var hash = algorithm.ComputeHash(bytes); - var @string = String.Join(String.Empty, hash.Select(b => b.ToString("x2"))); - - return @string; - } - } - private (byte[] authenticationKey, byte[] encryptionKey) GenerateKeys(Stream data, string password) { var authenticationSalt = new byte[SALT_SIZE]; @@ -125,13 +114,6 @@ namespace SafeExamBrowser.Configuration.DataFormats } } - private LoadStatus FailForInvalidPasswordHmac() - { - logger.Warn($"The authentication failed due to an invalid password or corrupted data!"); - - return LoadStatus.PasswordNeeded; - } - private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength) { var initializationVector = new byte[BLOCK_SIZE]; diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHash.cs b/SafeExamBrowser.Configuration/DataFormats/Cryptography/PublicKeyHashEncryption.cs similarity index 57% rename from SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHash.cs rename to SafeExamBrowser.Configuration/DataFormats/Cryptography/PublicKeyHashEncryption.cs index ed848ffa..7ea90761 100644 --- a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHash.cs +++ b/SafeExamBrowser.Configuration/DataFormats/Cryptography/PublicKeyHashEncryption.cs @@ -8,14 +8,17 @@ using System; using System.IO; -using SafeExamBrowser.Contracts.Configuration; -using SafeExamBrowser.Contracts.Configuration.Settings; -namespace SafeExamBrowser.Configuration.DataFormats +namespace SafeExamBrowser.Configuration.DataFormats.Cryptography { - public partial class BinaryFormat + internal class PublicKeyHashEncryption { - private LoadStatus ParsePublicKeyHash(Stream data, out Settings settings, string password) + internal Stream Encrypt(Stream data, string password) + { + throw new NotImplementedException(); + } + + internal Stream Decrypt(Stream data, string password) { throw new NotImplementedException(); } diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHashWithSymmetricKey.cs b/SafeExamBrowser.Configuration/DataFormats/Cryptography/PublicKeyHashWithSymmetricKeyEncryption.cs similarity index 56% rename from SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHashWithSymmetricKey.cs rename to SafeExamBrowser.Configuration/DataFormats/Cryptography/PublicKeyHashWithSymmetricKeyEncryption.cs index 439f9d8f..d7be15ae 100644 --- a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHashWithSymmetricKey.cs +++ b/SafeExamBrowser.Configuration/DataFormats/Cryptography/PublicKeyHashWithSymmetricKeyEncryption.cs @@ -8,14 +8,17 @@ using System; using System.IO; -using SafeExamBrowser.Contracts.Configuration; -using SafeExamBrowser.Contracts.Configuration.Settings; -namespace SafeExamBrowser.Configuration.DataFormats +namespace SafeExamBrowser.Configuration.DataFormats.Cryptography { - public partial class BinaryFormat + internal class PublicKeyHashWithSymmetricKeyEncryption { - private LoadStatus ParsePublicKeyHashWithSymmetricKey(Stream data, out Settings settings, string password) + internal Stream Encrypt(Stream data, string password) + { + throw new NotImplementedException(); + } + + internal Stream Decrypt(Stream data, string password) { throw new NotImplementedException(); } diff --git a/SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs b/SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs index a3fa0ae3..04ef2552 100644 --- a/SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs +++ b/SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs @@ -6,7 +6,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Linq; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Logging; @@ -15,6 +19,12 @@ namespace SafeExamBrowser.Configuration.DataFormats { public class XmlFormat : IDataFormat { + private const string ARRAY = "array"; + private const string DICTIONARY = "dict"; + private const string KEY = "key"; + private const string ROOT_NODE = "plist"; + private const string XML_PREFIX = " prefixData.Length; + + if (longEnough) + { + data.Seek(0, SeekOrigin.Begin); + data.Read(prefixData, 0, prefixData.Length); + var prefix = Encoding.UTF8.GetString(prefixData); + var success = prefix == XML_PREFIX; + + logger.Debug($"'{data}' starting with '{prefix}' does {(success ? string.Empty : "not ")}match the XML format."); + + return success; + } + + logger.Debug($"'{data}' is not long enough ({data.Length} bytes) to match the XML format."); + } + catch (Exception e) + { + logger.Error($"Failed to determine whether '{data}' with {data.Length / 1000.0} KB data matches the XML format!", e); + } + return false; } - public LoadStatus TryParse(Stream data, out Settings settings, string password = null) + public LoadStatus TryParse(Stream data, out Settings settings, string password = null, bool passwordIsHash = false) { - throw new System.NotImplementedException(); + var status = LoadStatus.InvalidData; + + settings = new Settings(); + data.Seek(0, SeekOrigin.Begin); + + using (var reader = XmlReader.Create(data, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore })) + { + var hasRoot = reader.ReadToFollowing(ROOT_NODE); + + if (hasRoot) + { + logger.Debug($"Found root node, starting to parse data..."); + Parse(reader, settings, out status); + logger.Debug($"Finished parsing -> Result: {status}."); + } + else + { + logger.Error($"Could not find root node '{ROOT_NODE}'!"); + } + } + + return status; + } + + private void Parse(XmlReader reader, Settings settings, out LoadStatus status) + { + status = LoadStatus.Success; + + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case ARRAY: + ParseArray(reader, settings, out status); + break; + case DICTIONARY: + ParseDictionary(reader, settings, out status); + break; + case KEY: + ParseKeyValuePair(reader, settings, out status); + break; + case ROOT_NODE: + break; + default: + status = LoadStatus.InvalidData; + logger.Error($"Detected invalid element '{reader.Name}'!"); + break; + } + } + + reader.Read(); + reader.MoveToContent(); + + if (!reader.EOF && status == LoadStatus.Success) + { + Parse(reader, settings, out status); + } + } + + private void ParseArray(XmlReader reader, Settings settings, out LoadStatus status) + { + reader.Read(); + reader.MoveToContent(); + + while (reader.NodeType == XmlNodeType.Element) + { + if (reader.Name == ARRAY) + { + ParseArray(reader, settings, out status); + } + else if (reader.Name == DICTIONARY) + { + ParseDictionary(reader, settings, out status); + } + else + { + var item = XNode.ReadFrom(reader) as XElement; + + // TODO: Map data... + } + + reader.Read(); + reader.MoveToContent(); + } + + if (reader.NodeType == XmlNodeType.EndElement && reader.Name == ARRAY) + { + status = LoadStatus.Success; + } + else + { + logger.Error($"Expected closing tag for '{ARRAY}', but found '{reader.Name}{reader.Value}'!"); + status = LoadStatus.InvalidData; + } + } + + private void ParseDictionary(XmlReader reader, Settings settings, out LoadStatus status) + { + reader.Read(); + reader.MoveToContent(); + + while (reader.NodeType == XmlNodeType.Element) + { + ParseKeyValuePair(reader, settings, out status); + + reader.Read(); + reader.MoveToContent(); + } + + if (reader.NodeType == XmlNodeType.EndElement && reader.Name == DICTIONARY) + { + status = LoadStatus.Success; + } + else + { + logger.Error($"Expected closing tag for '{DICTIONARY}', but found '{reader.Name}{reader.Value}'!"); + status = LoadStatus.InvalidData; + } + } + + private void ParseKeyValuePair(XmlReader reader, Settings settings, out LoadStatus status) + { + var key = XNode.ReadFrom(reader) as XElement; + + reader.Read(); + reader.MoveToContent(); + + if (reader.Name == ARRAY) + { + ParseArray(reader, settings, out status); + } + else if (reader.Name == DICTIONARY) + { + ParseDictionary(reader, settings, out status); + } + else + { + var value = XNode.ReadFrom(reader) as XElement; + + // TODO: Map data... + + status = LoadStatus.Success; + } } } } diff --git a/SafeExamBrowser.Configuration/HashAlgorithm.cs b/SafeExamBrowser.Configuration/HashAlgorithm.cs new file mode 100644 index 00000000..c1404394 --- /dev/null +++ b/SafeExamBrowser.Configuration/HashAlgorithm.cs @@ -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/. + */ + +using System; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using SafeExamBrowser.Contracts.Configuration; + +namespace SafeExamBrowser.Configuration +{ + public class HashAlgorithm : IHashAlgorithm + { + public string GenerateHashFor(string password) + { + using (var algorithm = new SHA256Managed()) + { + var bytes = Encoding.UTF8.GetBytes(password); + var hash = algorithm.ComputeHash(bytes); + var hashString = String.Join(String.Empty, hash.Select(b => b.ToString("x2"))); + + return hashString; + } + } + } +} diff --git a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj index c3e497ad..1ee5ecdd 100644 --- a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj +++ b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj @@ -54,23 +54,17 @@ + + - - BinaryFormat.cs - - - BinaryFormat.cs - - - BinaryFormat.cs - - - BinaryFormat.cs - + + + + diff --git a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs index fd68ebda..f686709c 100644 --- a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs +++ b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs @@ -44,6 +44,6 @@ namespace SafeExamBrowser.Contracts.Configuration /// Attempts to load settings from the specified resource, using the optional password. As long as the result is not /// , the referenced settings may be null or in an undefinable state! /// - LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, string password = null); + LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, string password = null, bool passwordIsHash = false); } } diff --git a/SafeExamBrowser.Contracts/Configuration/IDataFormat.cs b/SafeExamBrowser.Contracts/Configuration/IDataFormat.cs index dd2c6c37..621541cc 100644 --- a/SafeExamBrowser.Contracts/Configuration/IDataFormat.cs +++ b/SafeExamBrowser.Contracts/Configuration/IDataFormat.cs @@ -24,6 +24,6 @@ namespace SafeExamBrowser.Contracts.Configuration /// Tries to parse the given data, using the optional password. As long as the result is not , /// the referenced settings may be null or in an undefinable state! /// - LoadStatus TryParse(Stream data, out Settings.Settings settings, string password = null); + LoadStatus TryParse(Stream data, out Settings.Settings settings, string password = null, bool passwordIsHash = false); } } diff --git a/SafeExamBrowser.Contracts/Configuration/IHashAlgorithm.cs b/SafeExamBrowser.Contracts/Configuration/IHashAlgorithm.cs new file mode 100644 index 00000000..45d3148f --- /dev/null +++ b/SafeExamBrowser.Contracts/Configuration/IHashAlgorithm.cs @@ -0,0 +1,21 @@ +/* + * 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 +{ + /// + /// Provides functionality to calculate hash codes of different objects. + /// + public interface IHashAlgorithm + { + /// + /// Computes a hash code for the given password. + /// + string GenerateHashFor(string password); + } +} diff --git a/SafeExamBrowser.Contracts/Configuration/Settings/Settings.cs b/SafeExamBrowser.Contracts/Configuration/Settings/Settings.cs index 3d7ab19b..ba11b4e3 100644 --- a/SafeExamBrowser.Contracts/Configuration/Settings/Settings.cs +++ b/SafeExamBrowser.Contracts/Configuration/Settings/Settings.cs @@ -16,6 +16,11 @@ namespace SafeExamBrowser.Contracts.Configuration.Settings [Serializable] public class Settings { + /// + /// The hash code of the administrator password for the settings. + /// + public string AdminPasswordHash { get; set; } + /// /// The mode which determines the configuration behaviour. /// diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index ec08d424..53e764c7 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -55,6 +55,7 @@ + diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs index 786916fe..0e4bb788 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs @@ -51,367 +51,373 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations } [TestMethod] - public void MustUseCommandLineArgumentAs1stPrio() + public void TODO() { - var settings = default(Settings); - var url = @"http://www.safeexambrowser.org/whatever.seb"; - var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); - - appConfig.ProgramDataFolder = location; - appConfig.AppDataFolder = location; - - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); - - sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); - sut.Perform(); - - var resource = new Uri(url); - - repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Once); + Assert.Fail(); } - [TestMethod] - public void MustUseProgramDataAs2ndPrio() - { - var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); + //[TestMethod] + //public void MustUseCommandLineArgumentAs1stPrio() + //{ + // var settings = default(Settings); + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + // var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); - appConfig.ProgramDataFolder = location; - appConfig.AppDataFolder = $@"{location}\WRONG"; + // appConfig.ProgramDataFolder = location; + // appConfig.AppDataFolder = location; - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); - sut.Perform(); + // sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); + // sut.Perform(); - var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); + // var resource = new Uri(url); - repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Once); - } + // repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Once); + //} - [TestMethod] - public void MustUseAppDataAs3rdPrio() - { - var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); + //[TestMethod] + //public void MustUseProgramDataAs2ndPrio() + //{ + // var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); - appConfig.AppDataFolder = location; + // appConfig.ProgramDataFolder = location; + // appConfig.AppDataFolder = $@"{location}\WRONG"; - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); - sut.Perform(); + // sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + // sut.Perform(); - var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); + // var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); - repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Once); - } + // repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Once); + //} - [TestMethod] - public void MustFallbackToDefaultsAsLastPrio() - { - var actualSettings = default(Settings); - var defaultSettings = new Settings(); + //[TestMethod] + //public void MustUseAppDataAs3rdPrio() + //{ + // var location = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); - repository.Setup(r => r.LoadDefaultSettings()).Returns(defaultSettings); - session.SetupSet(s => s.Settings = It.IsAny()).Callback(s => actualSettings = s); + // appConfig.AppDataFolder = location; - sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); - sut.Perform(); + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); - repository.Verify(r => r.LoadDefaultSettings(), Times.Once); - session.VerifySet(s => s.Settings = defaultSettings); + // sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + // sut.Perform(); - Assert.AreSame(defaultSettings, actualSettings); - } + // var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); - [TestMethod] - public void MustAbortIfWishedByUser() - { - appConfig.ProgramDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); + // repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Once); + //} - sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is ConfigurationCompletedEventArgs c) - { - c.AbortStartup = true; - } - }; + //[TestMethod] + //public void MustFallbackToDefaultsAsLastPrio() + //{ + // var actualSettings = default(Settings); + // var defaultSettings = new Settings(); - var result = sut.Perform(); + // repository.Setup(r => r.LoadDefaultSettings()).Returns(defaultSettings); + // session.SetupSet(s => s.Settings = It.IsAny()).Callback(s => actualSettings = s); - Assert.AreEqual(OperationResult.Aborted, result); - } + // sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + // sut.Perform(); - [TestMethod] - public void MustNotAbortIfNotWishedByUser() - { - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); + // repository.Verify(r => r.LoadDefaultSettings(), Times.Once); + // session.VerifySet(s => s.Settings = defaultSettings); - sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is ConfigurationCompletedEventArgs c) - { - c.AbortStartup = false; - } - }; + // Assert.AreSame(defaultSettings, actualSettings); + //} - var result = sut.Perform(); + //[TestMethod] + //public void MustAbortIfWishedByUser() + //{ + // appConfig.ProgramDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); - Assert.AreEqual(OperationResult.Success, result); - } + // sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is ConfigurationCompletedEventArgs c) + // { + // c.AbortStartup = true; + // } + // }; - [TestMethod] - public void MustNotAllowToAbortIfNotInConfigureClientMode() - { - settings.ConfigurationMode = ConfigurationMode.Exam; - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); + // var result = sut.Perform(); - sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is ConfigurationCompletedEventArgs c) - { - Assert.Fail(); - } - }; + // Assert.AreEqual(OperationResult.Aborted, result); + //} - sut.Perform(); - } + //[TestMethod] + //public void MustNotAbortIfNotWishedByUser() + //{ + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); - [TestMethod] - public void MustNotFailWithoutCommandLineArgs() - { - var actualSettings = default(Settings); - var defaultSettings = new Settings(); - - repository.Setup(r => r.LoadDefaultSettings()).Returns(defaultSettings); - session.SetupSet(s => s.Settings = It.IsAny()).Callback(s => actualSettings = s); - - sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); - sut.Perform(); - - repository.Verify(r => r.LoadDefaultSettings(), Times.Once); - - Assert.AreSame(defaultSettings, actualSettings); - - sut = new ConfigurationOperation(new string[] { }, repository.Object, logger.Object, sessionContext); - sut.Perform(); - - repository.Verify(r => r.LoadDefaultSettings(), Times.Exactly(2)); - - Assert.AreSame(defaultSettings, actualSettings); - } - - [TestMethod] - public void MustNotFailWithInvalidUri() - { - var uri = @"an/invalid\uri.'*%yolo/()你好"; - - sut = new ConfigurationOperation(new[] { "blubb.exe", uri }, repository.Object, logger.Object, sessionContext); - sut.Perform(); - } - - [TestMethod] - public void MustOnlyAllowToEnterAdminPasswordFiveTimes() - { - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); - - sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is PasswordRequiredEventArgs p) - { - p.Success = true; - } - }; - - sut.Perform(); - - repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Exactly(5)); - } - - [TestMethod] - public void MustOnlyAllowToEnterSettingsPasswordFiveTimes() - { - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); - - sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is PasswordRequiredEventArgs p) - { - p.Success = true; - } - }; - - sut.Perform(); - - repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Exactly(5)); - } - - [TestMethod] - public void MustSucceedIfAdminPasswordCorrect() - { - var password = "test"; - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, password)).Returns(LoadStatus.Success); - - sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is PasswordRequiredEventArgs p) - { - p.Password = password; - p.Success = true; - } - }; - - sut.Perform(); - - repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Once); - repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, password), Times.Once); - } - - [TestMethod] - public void MustSucceedIfSettingsPasswordCorrect() - { - var password = "test"; - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, password)).Returns(LoadStatus.Success); - - sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is PasswordRequiredEventArgs p) - { - p.Password = password; - p.Success = true; - } - }; - - sut.Perform(); - - repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Once); - repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, password), Times.Once); - } - - [TestMethod] - public void MustAbortAskingForAdminPasswordIfDecidedByUser() - { - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); - - sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is PasswordRequiredEventArgs p) - { - p.Success = false; - } - }; - - var result = sut.Perform(); - - Assert.AreEqual(OperationResult.Aborted, result); - } - - [TestMethod] - public void MustAbortAskingForSettingsPasswordIfDecidedByUser() - { - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); - - sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is PasswordRequiredEventArgs p) - { - p.Success = false; - } - }; - - var result = sut.Perform(); - - Assert.AreEqual(OperationResult.Aborted, result); - } - - [TestMethod] - public void MustAllowEnteringBothPasswords() - { - var adminPassword = "xyz"; - var settingsPassword = "abc"; - var url = @"http://www.safeexambrowser.org/whatever.seb"; - - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, settingsPassword)).Returns(LoadStatus.Success); - - sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); - sut.ActionRequired += args => - { - if (args is PasswordRequiredEventArgs p) - { - p.Password = p.Purpose == PasswordRequestPurpose.Administrator ? adminPassword : settingsPassword; - p.Success = true; - } - }; - - sut.Perform(); - - repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Once); - repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, settingsPassword), Times.Once); - repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, settingsPassword), Times.Once); - } + // sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is ConfigurationCompletedEventArgs c) + // { + // c.AbortStartup = false; + // } + // }; - [TestMethod] - public void MustReconfigureSuccessfullyWithCorrectUri() - { - var location = Path.GetDirectoryName(GetType().Assembly.Location); - var resource = new Uri(Path.Combine(location, nameof(Operations), "SettingsDummy.txt")); + // var result = sut.Perform(); - sessionContext.ReconfigurationFilePath = resource.LocalPath; - repository.Setup(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null)).Returns(LoadStatus.Success); + // Assert.AreEqual(OperationResult.Success, result); + //} - sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + //[TestMethod] + //public void MustNotAllowToAbortIfNotInConfigureClientMode() + //{ + // settings.ConfigurationMode = ConfigurationMode.Exam; + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); - var result = sut.Repeat(); + // sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is ConfigurationCompletedEventArgs c) + // { + // Assert.Fail(); + // } + // }; - repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Once); + // sut.Perform(); + //} - Assert.AreEqual(OperationResult.Success, result); - } + //[TestMethod] + //public void MustNotFailWithoutCommandLineArgs() + //{ + // var actualSettings = default(Settings); + // var defaultSettings = new Settings(); + + // repository.Setup(r => r.LoadDefaultSettings()).Returns(defaultSettings); + // session.SetupSet(s => s.Settings = It.IsAny()).Callback(s => actualSettings = s); + + // sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + // sut.Perform(); + + // repository.Verify(r => r.LoadDefaultSettings(), Times.Once); + + // Assert.AreSame(defaultSettings, actualSettings); + + // sut = new ConfigurationOperation(new string[] { }, repository.Object, logger.Object, sessionContext); + // sut.Perform(); + + // repository.Verify(r => r.LoadDefaultSettings(), Times.Exactly(2)); + + // Assert.AreSame(defaultSettings, actualSettings); + //} + + //[TestMethod] + //public void MustNotFailWithInvalidUri() + //{ + // var uri = @"an/invalid\uri.'*%yolo/()你好"; + + // sut = new ConfigurationOperation(new[] { "blubb.exe", uri }, repository.Object, logger.Object, sessionContext); + // sut.Perform(); + //} + + //[TestMethod] + //public void MustOnlyAllowToEnterAdminPasswordFiveTimes() + //{ + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); + + // sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is PasswordRequiredEventArgs p) + // { + // p.Success = true; + // } + // }; + + // sut.Perform(); + + // repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Exactly(5)); + //} + + //[TestMethod] + //public void MustOnlyAllowToEnterSettingsPasswordFiveTimes() + //{ + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); + + // sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is PasswordRequiredEventArgs p) + // { + // p.Success = true; + // } + // }; + + // sut.Perform(); + + // repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Exactly(5)); + //} + + //[TestMethod] + //public void MustSucceedIfAdminPasswordCorrect() + //{ + // var password = "test"; + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, password)).Returns(LoadStatus.Success); + + // sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is PasswordRequiredEventArgs p) + // { + // p.Password = password; + // p.Success = true; + // } + // }; + + // sut.Perform(); + + // repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Once); + // repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, password), Times.Once); + //} + + //[TestMethod] + //public void MustSucceedIfSettingsPasswordCorrect() + //{ + // var password = "test"; + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, password)).Returns(LoadStatus.Success); + + // sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is PasswordRequiredEventArgs p) + // { + // p.Password = password; + // p.Success = true; + // } + // }; + + // sut.Perform(); + + // repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Once); + // repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, password), Times.Once); + //} + + //[TestMethod] + //public void MustAbortAskingForAdminPasswordIfDecidedByUser() + //{ + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); + + // sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is PasswordRequiredEventArgs p) + // { + // p.Success = false; + // } + // }; + + // var result = sut.Perform(); + + // Assert.AreEqual(OperationResult.Aborted, result); + //} + + //[TestMethod] + //public void MustAbortAskingForSettingsPasswordIfDecidedByUser() + //{ + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); + + // sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is PasswordRequiredEventArgs p) + // { + // p.Success = false; + // } + // }; + + // var result = sut.Perform(); + + // Assert.AreEqual(OperationResult.Aborted, result); + //} + + //[TestMethod] + //public void MustAllowEnteringBothPasswords() + //{ + // var adminPassword = "xyz"; + // var settingsPassword = "abc"; + // var url = @"http://www.safeexambrowser.org/whatever.seb"; + + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded); + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, settingsPassword)).Returns(LoadStatus.Success); + + // sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); + // sut.ActionRequired += args => + // { + // if (args is PasswordRequiredEventArgs p) + // { + // p.Password = p.Purpose == PasswordRequestPurpose.Administrator ? adminPassword : settingsPassword; + // p.Success = true; + // } + // }; + + // sut.Perform(); + + // repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Once); + // repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, settingsPassword), Times.Once); + // repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, settingsPassword), Times.Once); + //} - [TestMethod] - public void MustFailToReconfigureWithInvalidUri() - { - var resource = new Uri("file:///C:/does/not/exist.txt"); + //[TestMethod] + //public void MustReconfigureSuccessfullyWithCorrectUri() + //{ + // var location = Path.GetDirectoryName(GetType().Assembly.Location); + // var resource = new Uri(Path.Combine(location, nameof(Operations), "SettingsDummy.txt")); - sessionContext.ReconfigurationFilePath = null; - repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); + // sessionContext.ReconfigurationFilePath = resource.LocalPath; + // repository.Setup(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + // sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); - var result = sut.Repeat(); + // var result = sut.Repeat(); - repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Never); - Assert.AreEqual(OperationResult.Failed, result); + // repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Once); - sessionContext.ReconfigurationFilePath = resource.LocalPath; - result = sut.Repeat(); + // Assert.AreEqual(OperationResult.Success, result); + //} - repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Never); - Assert.AreEqual(OperationResult.Failed, result); - } + //[TestMethod] + //public void MustFailToReconfigureWithInvalidUri() + //{ + // var resource = new Uri("file:///C:/does/not/exist.txt"); + + // sessionContext.ReconfigurationFilePath = null; + // repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success); + + // sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); + + // var result = sut.Repeat(); + + // repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Never); + // Assert.AreEqual(OperationResult.Failed, result); + + // sessionContext.ReconfigurationFilePath = resource.LocalPath; + // result = sut.Repeat(); + + // repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Never); + // Assert.AreEqual(OperationResult.Failed, result); + //} } } diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 195d0ede..29cb066c 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -119,7 +119,7 @@ namespace SafeExamBrowser.Runtime configuration = new ConfigurationRepository(repositoryLogger, executable.Location, programCopyright, programTitle, programVersion); appConfig = configuration.InitializeAppConfig(); - configuration.Register(new BinaryFormat(compressor, new ModuleLogger(logger, nameof(BinaryFormat)))); + configuration.Register(new BinaryFormat(compressor, new HashAlgorithm(), new ModuleLogger(logger, nameof(BinaryFormat)))); configuration.Register(new XmlFormat(new ModuleLogger(logger, nameof(XmlFormat)))); configuration.Register(new FileResourceLoader(new ModuleLogger(logger, nameof(FileResourceLoader)))); configuration.Register(new NetworkResourceLoader(appConfig, new ModuleLogger(logger, nameof(NetworkResourceLoader)))); diff --git a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs index 34f56dc7..12ed2973 100644 --- a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs @@ -73,6 +73,7 @@ namespace SafeExamBrowser.Runtime.Operations if (isValidUri) { result = LoadSettings(uri); + HandleClientConfiguration(ref result, uri); } else { @@ -99,11 +100,16 @@ namespace SafeExamBrowser.Runtime.Operations private OperationResult LoadSettings(Uri uri) { - var status = configuration.TryLoadSettings(uri, out Settings settings); + var status = configuration.TryLoadSettings(uri, out Settings settings, Context.Current?.Settings?.AdminPasswordHash, true); + + if (status == LoadStatus.PasswordNeeded) + { + status = configuration.TryLoadSettings(uri, out settings, string.Empty); + } for (var attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++) { - var result = TryGetPassword(status); + var result = TryGetPassword(); if (!result.Success) { @@ -141,7 +147,7 @@ namespace SafeExamBrowser.Runtime.Operations } } - private PasswordRequiredEventArgs TryGetPassword(LoadStatus status) + private PasswordRequiredEventArgs TryGetPassword() { var purpose = PasswordRequestPurpose.Settings; var args = new PasswordRequiredEventArgs { Purpose = purpose }; @@ -203,7 +209,17 @@ namespace SafeExamBrowser.Runtime.Operations if (successful && configureMode && !loadWithBrowser) { 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! + // -> 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! + //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); logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} after successful client configuration."); diff --git a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs index a60ed095..56c3a9e9 100644 --- a/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/SessionInitializationOperation.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Core.OperationModel; @@ -51,6 +52,8 @@ namespace SafeExamBrowser.Runtime.Operations public override OperationResult Revert() { + FinalizeSessionConfiguration(); + return OperationResult.Success; } @@ -66,5 +69,11 @@ namespace SafeExamBrowser.Runtime.Operations logger.Info($" -> Runtime-ID: {Context.Next.AppConfig.RuntimeId}"); logger.Info($" -> Session-ID: {Context.Next.Id}"); } + + private void FinalizeSessionConfiguration() + { + Context.Current = null; + Context.Next = null; + } } }