SEBWIN-221: Extended loading algorithm with functionality required to save data as local client configuration.

This commit is contained in:
dbuechel 2018-12-14 09:50:10 +01:00
parent f9f2bb3257
commit 75f5994a3b
29 changed files with 572 additions and 280 deletions

View file

@ -11,6 +11,7 @@ using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.UnitTests

View file

@ -10,8 +10,12 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using SafeExamBrowser.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Configuration.DataResources;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging;
@ -29,7 +33,7 @@ namespace SafeExamBrowser.Configuration
private AppConfig appConfig;
private IHashAlgorithm hashAlgorithm;
private IList<IDataFormat> dataFormats;
private IList<IResourceLoader> resourceLoaders;
private IList<IDataResource> dataResources;
private ILogger logger;
public ConfigurationRepository(
@ -41,7 +45,7 @@ namespace SafeExamBrowser.Configuration
string programVersion)
{
dataFormats = new List<IDataFormat>();
resourceLoaders = new List<IResourceLoader>();
dataResources = new List<IDataResource>();
this.hashAlgorithm = hashAlgorithm;
this.logger = logger;
@ -51,6 +55,35 @@ namespace SafeExamBrowser.Configuration
this.programVersion = programVersion ?? string.Empty;
}
public void ConfigureClientWith(Uri resource, EncryptionParameters encryption = null)
{
logger.Info($"Attempting to configure local client settings from '{resource}'...");
try
{
TryLoadData(resource, out Stream stream);
using (stream)
{
// TODO:
//TryParseData(stream, encryption, out _, out _, out var data);
//HandleIdentityCertificates(data);
// 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
// -> Otherwise, the local client configuration must again be encrypted in the same way as the original file!!
}
logger.Info($"Successfully configured local client settings with '{resource}'.");
}
catch (Exception e)
{
logger.Error($"Unexpected error while trying to configure local client settings '{resource}'!", e);
}
}
public AppConfig InitializeAppConfig()
{
var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser));
@ -128,34 +161,38 @@ namespace SafeExamBrowser.Configuration
dataFormats.Add(dataFormat);
}
public void Register(IResourceLoader resourceLoader)
public void Register(IDataResource dataResource)
{
resourceLoaders.Add(resourceLoader);
dataResources.Add(dataResource);
}
public LoadStatus TryLoadSettings(Uri resource, PasswordInfo passwordInfo, out Settings settings)
public LoadStatus TryLoadSettings(Uri resource, PasswordParameters password, out EncryptionParameters encryption, out Format format, out Settings settings)
{
logger.Info($"Attempting to load '{resource}'...");
encryption = default(EncryptionParameters);
format = default(Format);
settings = LoadDefaultSettings();
try
{
var status = TryLoadData(resource, out Stream data);
var status = TryLoadData(resource, out Stream stream);
using (data)
using (stream)
{
if (status == LoadStatus.LoadWithBrowser)
{
return HandleBrowserResource(resource, settings);
}
if (status != LoadStatus.Success)
{
return status;
}
return TryParseData(data, passwordInfo, resource, settings);
status = TryParseData(stream, password, out encryption, out format, out var data);
if (status == LoadStatus.Success)
{
data.MapTo(settings);
}
return status;
}
}
catch (Exception e)
@ -166,39 +203,65 @@ namespace SafeExamBrowser.Configuration
}
}
private void ExtractAndImportCertificates(IDictionary<string, object> data)
public SaveStatus TrySaveSettings(Uri resource, Format format, Settings settings, EncryptionParameters encryption = null)
{
// TODO
throw new NotImplementedException();
}
private LoadStatus HandleBrowserResource(Uri resource, Settings settings)
private void HandleIdentityCertificates(IDictionary<string, object> data)
{
settings.Browser.StartUrl = resource.AbsoluteUri;
logger.Info($"The resource needs authentication or is HTML data, loaded default settings with '{resource}' as startup URL.");
const int IDENTITY_CERTIFICATE = 1;
var hasCertificates = data.TryGetValue("embeddedCertificates", out object value);
return LoadStatus.Success;
}
private void HandleParseSuccess(ParseResult result, Settings settings, PasswordInfo passwordInfo, Uri resource)
{
var appDataFile = new Uri(Path.Combine(appConfig.AppDataFolder, appConfig.DefaultSettingsFileName));
var programDataFile = new Uri(Path.Combine(appConfig.ProgramDataFolder, appConfig.DefaultSettingsFileName));
var isAppDataFile = resource.AbsolutePath.Equals(appDataFile.AbsolutePath, StringComparison.OrdinalIgnoreCase);
var isProgramDataFile = resource.AbsolutePath.Equals(programDataFile.AbsolutePath, StringComparison.OrdinalIgnoreCase);
logger.Info("Mapping settings data...");
result.RawData.MapTo(settings);
if (settings.ConfigurationMode == ConfigurationMode.ConfigureClient && !isAppDataFile && !isProgramDataFile)
if (hasCertificates && value is IList<IDictionary<string, object>> certificates)
{
result.Status = TryConfigureClient(result.RawData, settings, passwordInfo);
var toRemove = new List<IDictionary<string, object>>();
foreach (var certificate in certificates)
{
var isIdentity = certificate.TryGetValue("type", out object t) && t is int type && type == IDENTITY_CERTIFICATE;
var hasData = certificate.TryGetValue("certificateData", out value);
if (isIdentity && hasData && value is byte[] certificateData)
{
ImportIdentityCertificate(certificateData, new X509Store(StoreLocation.CurrentUser));
ImportIdentityCertificate(certificateData, new X509Store(StoreName.TrustedPeople, StoreLocation.LocalMachine));
toRemove.Add(certificate);
}
}
toRemove.ForEach(c => certificates.Remove(c));
}
}
private void ImportIdentityCertificate(byte[] certificateData, X509Store store)
{
try
{
var certificate = new X509Certificate2();
certificate.Import(certificateData, "Di𝈭l𝈖Ch𝈒ah𝉇t𝈁a𝉈Hai1972", X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
logger.Info($"Successfully imported identity certificate into {store.Location}.{store.Name}.");
}
catch (Exception e)
{
logger.Error($"Failed to import identity certificate into {store.Location}.{store.Name}!", e);
}
finally
{
store.Close();
}
}
private LoadStatus TryLoadData(Uri resource, out Stream data)
{
var status = LoadStatus.NotSupported;
var resourceLoader = resourceLoaders.FirstOrDefault(l => l.CanLoad(resource));
var resourceLoader = dataResources.FirstOrDefault(l => l.CanLoad(resource));
data = default(Stream);
@ -215,23 +278,25 @@ namespace SafeExamBrowser.Configuration
return status;
}
private LoadStatus TryParseData(Stream data, PasswordInfo passwordInfo, Uri resource, Settings settings)
private LoadStatus TryParseData(Stream data, PasswordParameters password, out EncryptionParameters encryption, out Format format, out IDictionary<string, object> rawData)
{
var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data));
var status = LoadStatus.NotSupported;
encryption = default(EncryptionParameters);
format = default(Format);
rawData = default(Dictionary<string, object>);
if (dataFormat != null)
{
var result = dataFormat.TryParse(data, passwordInfo);
logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {result.Status}.");
if (result.Status == LoadStatus.Success || result.Status == LoadStatus.SuccessConfigureClient)
{
HandleParseSuccess(result, settings, passwordInfo, resource);
}
var result = dataFormat.TryParse(data, password);
encryption = result.Encryption;
format = result.Format;
rawData = result.RawData;
status = result.Status;
logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {status}.");
}
else
{
@ -241,44 +306,6 @@ namespace SafeExamBrowser.Configuration
return status;
}
private LoadStatus TryConfigureClient(IDictionary<string, object> data, Settings settings, PasswordInfo passwordInfo)
{
logger.Info("Attempting to configure local client settings...");
if (passwordInfo.AdminPasswordHash != null)
{
var adminPasswordHash = passwordInfo.AdminPassword != null ? hashAlgorithm.GenerateHashFor(passwordInfo.AdminPassword) : null;
var settingsPasswordHash = passwordInfo.SettingsPassword != null ? hashAlgorithm.GenerateHashFor(passwordInfo.SettingsPassword) : null;
var enteredCorrectPassword = passwordInfo.AdminPasswordHash.Equals(adminPasswordHash, StringComparison.OrdinalIgnoreCase);
var sameAdminPassword = passwordInfo.AdminPasswordHash.Equals(settings.AdminPasswordHash, StringComparison.OrdinalIgnoreCase);
var knowsAdminPassword = passwordInfo.AdminPasswordHash.Equals(settingsPasswordHash, StringComparison.OrdinalIgnoreCase);
if (sameAdminPassword || knowsAdminPassword || enteredCorrectPassword)
{
logger.Info("Authentication was successful.");
}
else
{
logger.Info("Authentication has failed!");
return LoadStatus.AdminPasswordNeeded;
}
}
else
{
logger.Info("Authentication is not required.");
}
// -> Certificates need to be imported and REMOVED from the settings before the data is saved!
ExtractAndImportCertificates(data);
// Save configuration data as local client config under %APPDATA%!
// -> Default settings password for local client configuration appears to be string.Empty
// -> Local client configuration needs to again be encrypted in the same way as the original file was!!
return LoadStatus.SuccessConfigureClient;
}
private void UpdateAppConfig()
{
appConfig.ClientId = Guid.NewGuid();

View file

@ -10,9 +10,9 @@ using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
namespace SafeExamBrowser.Configuration
namespace SafeExamBrowser.Configuration.Cryptography
{
public class HashAlgorithm : IHashAlgorithm
{

View file

@ -12,7 +12,7 @@ using System.Security.Cryptography;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
namespace SafeExamBrowser.Configuration.Cryptography
{
internal class PasswordEncryption
{
@ -37,7 +37,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
if (password == null)
{
return LoadStatus.SettingsPasswordNeeded;
return LoadStatus.PasswordNeeded;
}
var (version, options) = ParseHeader(data);
@ -112,7 +112,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
{
logger.Debug($"The authentication failed due to an invalid password or corrupted data!");
return LoadStatus.SettingsPasswordNeeded;
return LoadStatus.PasswordNeeded;
}
private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength)

View file

@ -13,7 +13,7 @@ using System.Security.Cryptography.X509Certificates;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
namespace SafeExamBrowser.Configuration.Cryptography
{
internal class PublicKeyHashEncryption
{
@ -26,10 +26,10 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
this.logger = logger;
}
internal virtual LoadStatus Decrypt(Stream data, out Stream decrypted)
internal virtual LoadStatus Decrypt(Stream data, out Stream decrypted, out X509Certificate2 certificate)
{
var keyHash = ParsePublicKeyHash(data);
var found = TryGetCertificateWith(keyHash, out X509Certificate2 certificate);
var found = TryGetCertificateWith(keyHash, out certificate);
decrypted = default(Stream);

View file

@ -12,7 +12,7 @@ using System.Security.Cryptography.X509Certificates;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
namespace SafeExamBrowser.Configuration.Cryptography
{
internal class PublicKeyHashWithSymmetricKeyEncryption : PublicKeyHashEncryption
{
@ -25,10 +25,10 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
this.passwordEncryption = passwordEncryption;
}
internal override LoadStatus Decrypt(Stream data, out Stream decrypted)
internal override LoadStatus Decrypt(Stream data, out Stream decrypted, out X509Certificate2 certificate)
{
var keyHash = ParsePublicKeyHash(data);
var found = TryGetCertificateWith(keyHash, out X509Certificate2 certificate);
var found = TryGetCertificateWith(keyHash, out certificate);
decrypted = default(Stream);

View file

@ -9,10 +9,10 @@
using System;
using System.IO;
using System.IO.Compression;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.DataCompression;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.Compression
namespace SafeExamBrowser.Configuration.DataCompression
{
/// <summary>
/// Data compression using the GNU-Zip format (see https://en.wikipedia.org/wiki/Gzip).

View file

@ -8,9 +8,13 @@
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using SafeExamBrowser.Configuration.DataFormats.Cryptography;
using SafeExamBrowser.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataCompression;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats
@ -56,7 +60,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
return false;
}
public ParseResult TryParse(Stream data, PasswordInfo passwordInfo)
public ParseResult TryParse(Stream data, PasswordParameters password)
{
var prefix = ParsePrefix(data);
var success = TryDetermineFormat(prefix, out FormatType format);
@ -75,13 +79,13 @@ namespace SafeExamBrowser.Configuration.DataFormats
{
case FormatType.Password:
case FormatType.PasswordConfigureClient:
return ParsePasswordBlock(data, passwordInfo, format);
return ParsePasswordBlock(data, format, password);
case FormatType.PlainData:
return ParsePlainDataBlock(data, passwordInfo);
return ParsePlainDataBlock(data);
case FormatType.PublicKeyHash:
return ParsePublicKeyHashBlock(data, passwordInfo);
return ParsePublicKeyHashBlock(data, password);
case FormatType.PublicKeyHashWithSymmetricKey:
return ParsePublicKeyHashWithSymmetricKeyBlock(data, passwordInfo);
return ParsePublicKeyHashWithSymmetricKeyBlock(data, password);
}
}
@ -90,40 +94,35 @@ namespace SafeExamBrowser.Configuration.DataFormats
return new ParseResult { Status = LoadStatus.InvalidData };
}
private ParseResult ParsePasswordBlock(Stream data, PasswordInfo passwordInfo, FormatType format)
private ParseResult ParsePasswordBlock(Stream data, FormatType format, PasswordParameters password)
{
var decrypted = default(Stream);
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
var status = default(LoadStatus);
var encryptionParams = new PasswordParameters();
var result = new ParseResult();
if (format == FormatType.PasswordConfigureClient)
{
status = encryption.Decrypt(data, string.Empty, out decrypted);
if (status == LoadStatus.SettingsPasswordNeeded && passwordInfo.AdminPasswordHash != null)
{
status = encryption.Decrypt(data, passwordInfo.AdminPasswordHash, out decrypted);
}
if (status == LoadStatus.SettingsPasswordNeeded && passwordInfo.SettingsPassword != null)
{
status = encryption.Decrypt(data, hashAlgorithm.GenerateHashFor(passwordInfo.SettingsPassword), out decrypted);
}
encryptionParams.Password = password.IsHash ? password.Password : hashAlgorithm.GenerateHashFor(password.Password);
encryptionParams.IsHash = true;
}
else
{
status = encryption.Decrypt(data, passwordInfo.SettingsPassword, out decrypted);
encryptionParams.Password = password.Password;
encryptionParams.IsHash = password.IsHash;
}
if (status == LoadStatus.Success)
result.Status = encryption.Decrypt(data, encryptionParams.Password, out var decrypted);
if (result.Status == LoadStatus.Success)
{
return ParsePlainDataBlock(decrypted, passwordInfo);
result = ParsePlainDataBlock(decrypted);
result.Encryption = encryptionParams;
}
return new ParseResult { Status = status };
return result;
}
private ParseResult ParsePlainDataBlock(Stream data, PasswordInfo passwordInfo)
private ParseResult ParsePlainDataBlock(Stream data)
{
var xmlFormat = new XmlFormat(logger.CloneFor(nameof(XmlFormat)));
@ -132,35 +131,50 @@ namespace SafeExamBrowser.Configuration.DataFormats
data = compressor.Decompress(data);
}
return xmlFormat.TryParse(data, passwordInfo);
return xmlFormat.TryParse(data);
}
private ParseResult ParsePublicKeyHashBlock(Stream data, PasswordInfo passwordInfo)
private ParseResult ParsePublicKeyHashBlock(Stream data, PasswordParameters password)
{
var encryption = new PublicKeyHashEncryption(logger.CloneFor(nameof(PublicKeyHashEncryption)));
var status = encryption.Decrypt(data, out Stream decrypted);
var result = new ParseResult();
if (status == LoadStatus.Success)
result.Status = encryption.Decrypt(data, out var decrypted, out var certificate);
if (result.Status == LoadStatus.Success)
{
return TryParse(decrypted, passwordInfo);
result = TryParse(decrypted, password);
result.Encryption = new PublicKeyHashParameters
{
Certificate = certificate,
InnerEncryption = result.Encryption as PasswordParameters,
SymmetricEncryption = false
};
}
return new ParseResult { Status = status };
return result;
}
private ParseResult ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, PasswordInfo passwordInfo)
private ParseResult ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, PasswordParameters password)
{
var logger = this.logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption));
var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
var encryption = new PublicKeyHashWithSymmetricKeyEncryption(logger, passwordEncryption);
var status = encryption.Decrypt(data, out Stream decrypted);
var encryption = new PublicKeyHashWithSymmetricKeyEncryption(logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption)), passwordEncryption);
var result = new ParseResult();
if (status == LoadStatus.Success)
result.Status = encryption.Decrypt(data, out Stream decrypted, out X509Certificate2 certificate);
if (result.Status == LoadStatus.Success)
{
return TryParse(decrypted, passwordInfo);
result = TryParse(decrypted, password);
result.Encryption = new PublicKeyHashParameters
{
Certificate = certificate,
InnerEncryption = result.Encryption as PasswordParameters,
SymmetricEncryption = true
};
}
return new ParseResult { Status = status };
return result;
}
private string ParsePrefix(Stream data)

View file

@ -13,6 +13,8 @@ using System.Text;
using System.Xml;
using System.Xml.Linq;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats
@ -58,7 +60,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
return false;
}
public ParseResult TryParse(Stream data, PasswordInfo passwordInfo)
public ParseResult TryParse(Stream data, PasswordParameters password = null)
{
var result = new ParseResult { Status = LoadStatus.InvalidData };
var xmlSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
@ -218,6 +220,11 @@ namespace SafeExamBrowser.Configuration.DataFormats
{
value = null;
if (element.IsEmpty)
{
return LoadStatus.Success;
}
switch (element.Name.LocalName)
{
case DataTypes.DATA:

View file

@ -9,15 +9,16 @@
using System;
using System.IO;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.DataResources;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.ResourceLoaders
namespace SafeExamBrowser.Configuration.DataResources
{
public class FileResourceLoader : IResourceLoader
public class FileResource: IDataResource
{
private ILogger logger;
public FileResourceLoader(ILogger logger)
public FileResource(ILogger logger)
{
this.logger = logger;
}
@ -46,5 +47,10 @@ namespace SafeExamBrowser.Configuration.ResourceLoaders
return LoadStatus.Success;
}
public SaveStatus TrySave(Uri resource, Stream data)
{
throw new NotImplementedException();
}
}
}

View file

@ -14,11 +14,12 @@ using System.Net.Http;
using System.Net.Mime;
using System.Threading.Tasks;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.DataResources;
using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.ResourceLoaders
namespace SafeExamBrowser.Configuration.DataResources
{
public class NetworkResourceLoader : IResourceLoader
public class NetworkResource : IDataResource
{
private AppConfig appConfig;
private ILogger logger;
@ -40,7 +41,7 @@ namespace SafeExamBrowser.Configuration.ResourceLoaders
Uri.UriSchemeHttps
};
public NetworkResourceLoader(AppConfig appConfig, ILogger logger)
public NetworkResource(AppConfig appConfig, ILogger logger)
{
this.appConfig = appConfig;
this.logger = logger;
@ -85,6 +86,11 @@ namespace SafeExamBrowser.Configuration.ResourceLoaders
return LoadStatus.Success;
}
public SaveStatus TrySave(Uri resource, Stream data)
{
throw new NotImplementedException();
}
private Uri BuildUriFor(Uri resource)
{
var scheme = GetSchemeFor(resource);

View file

@ -58,18 +58,18 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="Compression\GZipCompressor.cs" />
<Compile Include="DataCompression\GZipCompressor.cs" />
<Compile Include="DataFormats\BinaryFormat.cs" />
<Compile Include="DataFormats\Cryptography\PasswordEncryption.cs" />
<Compile Include="DataFormats\Cryptography\PublicKeyHashEncryption.cs" />
<Compile Include="DataFormats\Cryptography\PublicKeyHashWithSymmetricKeyEncryption.cs" />
<Compile Include="Cryptography\PasswordEncryption.cs" />
<Compile Include="Cryptography\PublicKeyHashEncryption.cs" />
<Compile Include="Cryptography\PublicKeyHashWithSymmetricKeyEncryption.cs" />
<Compile Include="DataFormats\XmlFormat.cs" />
<Compile Include="DataFormats\DataMapper.cs" />
<Compile Include="HashAlgorithm.cs" />
<Compile Include="Cryptography\HashAlgorithm.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ConfigurationRepository.cs" />
<Compile Include="ResourceLoaders\FileResourceLoader.cs" />
<Compile Include="ResourceLoaders\NetworkResourceLoader.cs" />
<Compile Include="DataResources\FileResource.cs" />
<Compile Include="DataResources\NetworkResource.cs" />
<Compile Include="SessionConfiguration.cs" />
<Compile Include="SubStream.cs" />
<Compile Include="SystemInfo.cs" />

View file

@ -0,0 +1,17 @@
/*
* 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.Cryptography
{
/// <summary>
/// Holds the cryptographic parameters used to encrypt configuration data.
/// </summary>
public abstract class EncryptionParameters
{
}
}

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Configuration
namespace SafeExamBrowser.Contracts.Configuration.Cryptography
{
/// <summary>
/// Provides functionality to calculate hash codes of different objects.

View file

@ -0,0 +1,26 @@
/*
* 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.Cryptography
{
/// <summary>
/// Holds all parameters for data encryption by password.
/// </summary>
public class PasswordParameters : EncryptionParameters
{
/// <summary>
/// The password in plain text.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Indicates whether the password is a hash code.
/// </summary>
public bool IsHash { get; set; }
}
}

View file

@ -0,0 +1,33 @@
/*
* 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.Security.Cryptography.X509Certificates;
namespace SafeExamBrowser.Contracts.Configuration.Cryptography
{
/// <summary>
/// Holds all parameters for data encryption by certificate.
/// </summary>
public class PublicKeyHashParameters : EncryptionParameters
{
/// <summary>
/// The certificate holding the public key used for encryption.
/// </summary>
public X509Certificate2 Certificate { get; set; }
/// <summary>
/// The encryption parameters of the inner data, if available.
/// </summary>
public PasswordParameters InnerEncryption { get; set; }
/// <summary>
/// Determines the usage of symmetric vs. asymmetric encryption.
/// </summary>
public bool SymmetricEncryption { get; set; }
}
}

View file

@ -8,7 +8,7 @@
using System.IO;
namespace SafeExamBrowser.Contracts.Configuration
namespace SafeExamBrowser.Contracts.Configuration.DataCompression
{
/// <summary>
/// Defines the functionality for data compression and decompression.

View file

@ -0,0 +1,19 @@
/*
* 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.DataFormats
{
/// <summary>
/// Defines all supported data formats.
/// </summary>
public enum Format
{
Binary = 1,
Xml
}
}

View file

@ -7,8 +7,9 @@
*/
using System.IO;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
namespace SafeExamBrowser.Contracts.Configuration
namespace SafeExamBrowser.Contracts.Configuration.DataFormats
{
/// <summary>
/// Provides functionality to parse configuration data with a particular format.
@ -23,6 +24,6 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary>
/// Tries to parse the given data.
/// </summary>
ParseResult TryParse(Stream data, PasswordInfo passwordInfo);
ParseResult TryParse(Stream data, PasswordParameters password = null);
}
}

View file

@ -7,14 +7,25 @@
*/
using System.Collections.Generic;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
namespace SafeExamBrowser.Contracts.Configuration
namespace SafeExamBrowser.Contracts.Configuration.DataFormats
{
/// <summary>
/// Defines the result of a data parsing operation by an <see cref="IDataFormat"/>.
/// </summary>
public class ParseResult
{
/// <summary>
/// The encryption parameters which were used to decrypt the data, or <c>null</c> if it was not encrypted.
/// </summary>
public EncryptionParameters Encryption { get; set; }
/// <summary>
/// The original format of the data.
/// </summary>
public Format Format { get; set; }
/// <summary>
/// The parsed settings data. Might be <c>null</c> or in an undefinable state, depending on <see cref="Status"/>.
/// </summary>

View file

@ -9,15 +9,15 @@
using System;
using System.IO;
namespace SafeExamBrowser.Contracts.Configuration
namespace SafeExamBrowser.Contracts.Configuration.DataResources
{
/// <summary>
/// Provides functionality to load configuration data from a particular resource.
/// Provides functionality to load and save configuration data from / as a particular resource.
/// </summary>
public interface IResourceLoader
public interface IDataResource
{
/// <summary>
/// Indicates whether the resource loader is able to load data from the specified resource.
/// Indicates whether data can be loaded from the specified resource.
/// </summary>
bool CanLoad(Uri resource);
@ -25,5 +25,10 @@ namespace SafeExamBrowser.Contracts.Configuration
/// Tries to load the configuration data from the specified resource.
/// </summary>
LoadStatus TryLoad(Uri resource, out Stream data);
/// <summary>
/// Tries to save the given configuration data as the specified resource.
/// </summary>
SaveStatus TrySave(Uri resource, Stream data);
}
}

View file

@ -7,6 +7,9 @@
*/
using System;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Configuration.DataResources;
namespace SafeExamBrowser.Contracts.Configuration
{
@ -15,6 +18,11 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary>
public interface IConfigurationRepository
{
/// <summary>
/// Saves the given resource as local client configuration.
/// </summary>
void ConfigureClientWith(Uri resource, EncryptionParameters encryption = null);
/// <summary>
/// Initializes the global configuration information for the currently running application instance.
/// </summary>
@ -31,19 +39,23 @@ namespace SafeExamBrowser.Contracts.Configuration
Settings.Settings LoadDefaultSettings();
/// <summary>
/// Registers the specified <see cref="IDataFormat"/> as option to parse data from a configuration resource.
/// Registers the specified <see cref="IDataFormat"/> to be used when loading or saving configuration data.
/// </summary>
void Register(IDataFormat dataFormat);
/// <summary>
/// Registers the specified <see cref="IResourceLoader"/> as option to load data from a configuration resource.
/// Registers the specified <see cref="IDataResource"/> to be used when loading or saving configuration data.
/// </summary>
void Register(IResourceLoader resourceLoader);
void Register(IDataResource dataResource);
/// <summary>
/// Attempts to load settings from the specified resource. As long as the result is not <see cref="LoadStatus.Success"/>,
/// the referenced settings may be <c>null</c> or in an undefinable state!
/// Attempts to load settings from the specified resource.
/// </summary>
LoadStatus TryLoadSettings(Uri resource, PasswordInfo passwordInfo, out Settings.Settings settings);
LoadStatus TryLoadSettings(Uri resource, PasswordParameters password, out EncryptionParameters encryption, out Format format, out Settings.Settings settings);
/// <summary>
/// Attempts to save settings according to the specified parameters.
/// </summary>
SaveStatus TrySaveSettings(Uri resource, Format format, Settings.Settings settings, EncryptionParameters encryption = null);
}
}

View file

@ -13,11 +13,6 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary>
public enum LoadStatus
{
/// <summary>
/// Indicates that the current administrator password is needed to be allowed to configure the local client.
/// </summary>
AdminPasswordNeeded = 1,
/// <summary>
/// Indicates that a resource contains invalid data.
/// </summary>
@ -34,20 +29,15 @@ namespace SafeExamBrowser.Contracts.Configuration
NotSupported,
/// <summary>
/// Indicates that the settings password is needed in order to decrypt the settings.
/// Indicates that a password is needed in order to decrypt the settings.
/// </summary>
SettingsPasswordNeeded,
PasswordNeeded,
/// <summary>
/// The settings were loaded successfully.
/// </summary>
Success,
/// <summary>
/// The settings were loaded and the local client configuration was performed successfully.
/// </summary>
SuccessConfigureClient,
/// <summary>
/// An unexpected error occurred while trying to load the settings.
/// </summary>

View file

@ -1,31 +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/.
*/
namespace SafeExamBrowser.Contracts.Configuration
{
/// <summary>
/// Holds all password data necessary to load an application configuration.
/// </summary>
public class PasswordInfo
{
/// <summary>
/// The current administrator password in plain text.
/// </summary>
public string AdminPassword { get; set; }
/// <summary>
/// The hash code of the current administrator password.
/// </summary>
public string AdminPasswordHash { get; set; }
/// <summary>
/// The settings password of the configuration in plain text.
/// </summary>
public string SettingsPassword { get; set; }
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Configuration
{
/// <summary>
/// Defines all possible results of an attempt to save an application configuration.
/// </summary>
public enum SaveStatus
{
}
}

View file

@ -53,11 +53,15 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" />
<Compile Include="Configuration\IDataCompressor.cs" />
<Compile Include="Configuration\IDataFormat.cs" />
<Compile Include="Configuration\IHashAlgorithm.cs" />
<Compile Include="Configuration\ParseResult.cs" />
<Compile Include="Configuration\PasswordInfo.cs" />
<Compile Include="Configuration\Cryptography\EncryptionParameters.cs" />
<Compile Include="Configuration\Cryptography\PasswordParameters.cs" />
<Compile Include="Configuration\Cryptography\PublicKeyHashParameters.cs" />
<Compile Include="Configuration\DataFormats\Format.cs" />
<Compile Include="Configuration\DataCompression\IDataCompressor.cs" />
<Compile Include="Configuration\DataFormats\IDataFormat.cs" />
<Compile Include="Configuration\Cryptography\IHashAlgorithm.cs" />
<Compile Include="Configuration\DataFormats\ParseResult.cs" />
<Compile Include="Configuration\SaveStatus.cs" />
<Compile Include="Core\Events\InstanceTerminatedEventHandler.cs" />
<Compile Include="Core\Events\NameChangedEventHandler.cs" />
<Compile Include="Core\IApplicationController.cs" />
@ -112,7 +116,7 @@
<Compile Include="Communication\Data\SimpleResponsePurport.cs" />
<Compile Include="Communication\Data\SimpleResponse.cs" />
<Compile Include="Configuration\ClientConfiguration.cs" />
<Compile Include="Configuration\IResourceLoader.cs" />
<Compile Include="Configuration\DataResources\IDataResource.cs" />
<Compile Include="Configuration\LoadStatus.cs" />
<Compile Include="Configuration\AppConfig.cs" />
<Compile Include="Configuration\ISessionConfiguration.cs" />

View file

@ -13,9 +13,10 @@ using System.Reflection;
using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Communication.Proxies;
using SafeExamBrowser.Configuration;
using SafeExamBrowser.Configuration.Compression;
using SafeExamBrowser.Configuration.Cryptography;
using SafeExamBrowser.Configuration.DataCompression;
using SafeExamBrowser.Configuration.DataFormats;
using SafeExamBrowser.Configuration.ResourceLoaders;
using SafeExamBrowser.Configuration.DataResources;
using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core;
using SafeExamBrowser.Contracts.Core.OperationModel;
@ -75,7 +76,7 @@ namespace SafeExamBrowser.Runtime
bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger));
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost, sessionContext));
sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, logger, sessionContext));
sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, new HashAlgorithm(), logger, sessionContext));
sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, FIFTEEN_SECONDS));
sessionOperations.Enqueue(new KioskModeTerminationOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new ServiceOperation(logger, serviceProxy, sessionContext));
@ -121,8 +122,8 @@ namespace SafeExamBrowser.Runtime
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))));
configuration.Register(new FileResource(new ModuleLogger(logger, nameof(FileResource))));
configuration.Register(new NetworkResource(appConfig, new ModuleLogger(logger, nameof(NetworkResource))));
}
private void InitializeLogging()

View file

@ -10,6 +10,8 @@ using System;
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;
@ -23,19 +25,32 @@ namespace SafeExamBrowser.Runtime.Operations
{
private string[] commandLineArgs;
private IConfigurationRepository configuration;
private IHashAlgorithm hashAlgorithm;
private ILogger logger;
private string AppDataFile
{
get { return Path.Combine(Context.Next.AppConfig.AppDataFolder, Context.Next.AppConfig.DefaultSettingsFileName); }
}
private string ProgramDataFile
{
get { return Path.Combine(Context.Next.AppConfig.ProgramDataFolder, Context.Next.AppConfig.DefaultSettingsFileName); }
}
public override event ActionRequiredEventHandler ActionRequired;
public override event StatusChangedEventHandler StatusChanged;
public ConfigurationOperation(
string[] commandLineArgs,
IConfigurationRepository configuration,
IHashAlgorithm hashAlgorithm,
ILogger logger,
SessionContext sessionContext) : base(sessionContext)
{
this.commandLineArgs = commandLineArgs;
this.configuration = configuration;
this.hashAlgorithm = hashAlgorithm;
this.logger = logger;
}
@ -98,53 +113,191 @@ namespace SafeExamBrowser.Runtime.Operations
private OperationResult LoadSettings(Uri uri)
{
var settings = default(Settings);
var status = default(LoadStatus);
var passwordInfo = new PasswordInfo { AdminPasswordHash = Context.Current?.Settings?.AdminPasswordHash };
var passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true };
var status = configuration.TryLoadSettings(uri, passwordParams, out var encryption, out var format, out var settings);
for (int adminAttempts = 0, settingsAttempts = 0; adminAttempts < 5 && settingsAttempts < 5;)
if (status == LoadStatus.PasswordNeeded && Context.Current?.Settings.AdminPasswordHash != null)
{
status = configuration.TryLoadSettings(uri, passwordInfo, out settings);
passwordParams.Password = Context.Current.Settings.AdminPasswordHash;
passwordParams.IsHash = true;
if (status != LoadStatus.AdminPasswordNeeded && status != LoadStatus.SettingsPasswordNeeded)
status = configuration.TryLoadSettings(uri, passwordParams, out encryption, out format, out settings);
}
for (int attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
{
var success = TryGetPassword(PasswordRequestPurpose.Settings, out var password);
if (success)
{
break;
passwordParams.Password = password;
passwordParams.IsHash = false;
}
var success = TryGetPassword(status, passwordInfo);
if (!success)
else
{
return OperationResult.Aborted;
}
adminAttempts += status == LoadStatus.AdminPasswordNeeded ? 1 : 0;
settingsAttempts += status == LoadStatus.SettingsPasswordNeeded ? 1 : 0;
status = configuration.TryLoadSettings(uri, passwordParams, out encryption, out format, out settings);
}
Context.Next.Settings = settings;
return HandleLoadResult(uri, settings, status, passwordParams, encryption, format);
}
private OperationResult HandleLoadResult(Uri uri, Settings settings, LoadStatus status, PasswordParameters password, EncryptionParameters encryption, Format format)
{
if (status == LoadStatus.LoadWithBrowser)
{
return HandleBrowserResource(uri);
}
if (status == LoadStatus.Success && settings.ConfigurationMode == ConfigurationMode.ConfigureClient)
{
return HandleClientConfiguration(uri, password, encryption, format);
}
if (status == LoadStatus.Success)
{
return OperationResult.Success;
}
if (status == LoadStatus.SuccessConfigureClient)
{
return HandleClientConfiguration();
}
ShowFailureMessage(status, uri);
return OperationResult.Failed;
}
private OperationResult HandleBrowserResource(Uri uri)
{
Context.Next.Settings.Browser.StartUrl = uri.AbsoluteUri;
logger.Info($"The configuration resource needs authentication or is a webpage, using '{uri}' as startup URL for the browser.");
return OperationResult.Success;
}
private OperationResult HandleClientConfiguration(Uri resource, PasswordParameters password, EncryptionParameters encryption, Format format)
{
var isAppDataFile = Path.GetFullPath(resource.AbsolutePath).Equals(AppDataFile, StringComparison.OrdinalIgnoreCase);
var isProgramDataFile = Path.GetFullPath(resource.AbsolutePath).Equals(ProgramDataFile, StringComparison.OrdinalIgnoreCase);
if (!isAppDataFile && !isProgramDataFile)
{
var isFirstSession = Context.Current == null;
var requiresAuthentication = IsAuthenticationRequiredForClientConfiguration(password);
logger.Info("Starting client configuration...");
if (requiresAuthentication)
{
var result = HandleClientConfigurationAuthentication();
if (result != OperationResult.Success)
{
return result;
}
}
else
{
logger.Info("Authentication is not required.");
}
configuration.ConfigureClientWith(resource, encryption);
if (isFirstSession)
{
var result = HandleClientConfigurationSuccess();
if (result != OperationResult.Success)
{
return result;
}
}
}
return OperationResult.Success;
}
private bool IsAuthenticationRequiredForClientConfiguration(PasswordParameters password)
{
var requiresAuthentication = Context.Current?.Settings.AdminPasswordHash != null;
if (requiresAuthentication)
{
var currentPassword = Context.Current.Settings.AdminPasswordHash;
var nextPassword = Context.Next.Settings.AdminPasswordHash;
var hasSettingsPassword = password.Password != null;
var sameAdminPassword = currentPassword.Equals(nextPassword, StringComparison.OrdinalIgnoreCase);
requiresAuthentication = !sameAdminPassword;
if (requiresAuthentication && hasSettingsPassword)
{
var settingsPassword = password.IsHash ? password.Password : hashAlgorithm.GenerateHashFor(password.Password);
var knowsAdminPassword = currentPassword.Equals(settingsPassword, StringComparison.OrdinalIgnoreCase);
requiresAuthentication = !knowsAdminPassword;
}
}
return requiresAuthentication;
}
private OperationResult HandleClientConfigurationAuthentication()
{
var currentPassword = Context.Current.Settings.AdminPasswordHash;
var isSamePassword = false;
for (int attempts = 0; attempts < 5 && !isSamePassword; attempts++)
{
var success = TryGetPassword(PasswordRequestPurpose.Administrator, out var password);
if (success)
{
isSamePassword = currentPassword.Equals(hashAlgorithm.GenerateHashFor(password), StringComparison.OrdinalIgnoreCase);
}
else
{
logger.Info("Authentication was aborted.");
return OperationResult.Aborted;
}
}
if (isSamePassword)
{
logger.Info("Authentication was successful.");
return OperationResult.Success;
}
else
{
logger.Info("Authentication has failed!");
return OperationResult.Failed;
}
}
private OperationResult HandleClientConfigurationSuccess()
{
var args = new ConfigurationCompletedEventArgs();
ActionRequired?.Invoke(args);
logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} after successful client configuration.");
if (args.AbortStartup)
{
return OperationResult.Aborted;
}
return OperationResult.Success;
}
private void ShowFailureMessage(LoadStatus status, Uri uri)
{
switch (status)
{
case LoadStatus.AdminPasswordNeeded:
case LoadStatus.SettingsPasswordNeeded:
case LoadStatus.PasswordNeeded:
ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
break;
case LoadStatus.InvalidData:
@ -159,31 +312,20 @@ namespace SafeExamBrowser.Runtime.Operations
}
}
private bool TryGetPassword(LoadStatus status, PasswordInfo passwordInfo)
private bool TryGetPassword(PasswordRequestPurpose purpose, out string password)
{
var purpose = status == LoadStatus.AdminPasswordNeeded ? PasswordRequestPurpose.Administrator : PasswordRequestPurpose.Settings;
var args = new PasswordRequiredEventArgs { Purpose = purpose };
ActionRequired?.Invoke(args);
if (purpose == PasswordRequestPurpose.Administrator)
{
passwordInfo.AdminPassword = args.Password;
}
else
{
passwordInfo.SettingsPassword = args.Password;
}
password = args.Password;
return args.Success;
}
private bool TryInitializeSettingsUri(out Uri uri)
{
var path = string.Empty;
var path = default(string);
var isValidUri = false;
var programDataSettings = Path.Combine(Context.Next.AppConfig.ProgramDataFolder, Context.Next.AppConfig.DefaultSettingsFileName);
var appDataSettings = Path.Combine(Context.Next.AppConfig.AppDataFolder, Context.Next.AppConfig.DefaultSettingsFileName);
uri = null;
@ -194,16 +336,16 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Info($"Found command-line argument for configuration resource: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}.");
}
if (!isValidUri && File.Exists(programDataSettings))
if (!isValidUri && File.Exists(ProgramDataFile))
{
path = programDataSettings;
path = ProgramDataFile;
isValidUri = Uri.TryCreate(path, UriKind.Absolute, out uri);
logger.Info($"Found configuration file in PROGRAMDATA: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}.");
}
if (!isValidUri && File.Exists(appDataSettings))
if (!isValidUri && File.Exists(AppDataFile))
{
path = appDataSettings;
path = AppDataFile;
isValidUri = Uri.TryCreate(path, UriKind.Absolute, out uri);
logger.Info($"Found configuration file in APPDATA: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}.");
}
@ -221,32 +363,6 @@ namespace SafeExamBrowser.Runtime.Operations
return isValidUri;
}
private OperationResult HandleClientConfiguration()
{
var firstSession = Context.Current == null;
if (firstSession)
{
var args = new ConfigurationCompletedEventArgs();
ActionRequired?.Invoke(args);
logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} after successful client configuration.");
if (args.AbortStartup)
{
return OperationResult.Aborted;
}
}
else
{
// TODO: If the client configuration happens while the application is already running, the new configuration should first
// be loaded and then the user should have the option to terminate!
// -> Introduce flag in Context, e.g. AskForTermination?
}
return OperationResult.Success;
}
private void LogOperationResult(OperationResult result)
{
switch (result)

View file

@ -398,6 +398,8 @@ namespace SafeExamBrowser.Runtime
private void ShowMessageBox(MessageEventArgs args)
{
var isStartup = !SessionIsRunning;
var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.KioskMode == KioskMode.DisableExplorerShell;
var message = text.Get(args.Message);
var title = text.Get(args.Title);
@ -411,7 +413,14 @@ namespace SafeExamBrowser.Runtime
title = title.Replace(placeholder.Key, placeholder.Value);
}
messageBox.Show(message, title, MessageBoxAction.Confirm, args.Icon, runtimeWindow);
if (isStartup || isRunningOnDefaultDesktop)
{
messageBox.Show(message, title, MessageBoxAction.Confirm, args.Icon, runtimeWindow);
}
else
{
// TODO
}
}
private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args)