SEBWIN-221: Implemented basic password decryption of binary configuration files and finally found correct solution for multi-threading issues related to WPF.
This commit is contained in:
parent
b29fd8c2d7
commit
7f38c0b8c3
30 changed files with 589 additions and 164 deletions
|
@ -127,7 +127,7 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
private void Button_OnClick(InstanceIdentifier id = null)
|
||||
{
|
||||
if (id is null)
|
||||
if (id == null)
|
||||
{
|
||||
CreateNewInstance();
|
||||
}
|
||||
|
|
|
@ -71,9 +71,8 @@
|
|||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.4.0\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -96,8 +95,12 @@
|
|||
<Compile Include="ClientControllerTests.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="app.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="packages.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Client\SafeExamBrowser.Client.csproj">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
<package id="MSTest.TestAdapter" version="1.2.0" targetFramework="net461" />
|
||||
<package id="MSTest.TestFramework" version="1.2.0" targetFramework="net461" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.4.0" targetFramework="net461" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net461" />
|
||||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net461" />
|
||||
</packages>
|
|
@ -229,7 +229,7 @@ namespace SafeExamBrowser.Communication.Proxies
|
|||
|
||||
private void FailIfNull(Message message)
|
||||
{
|
||||
if (message is null)
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
|
|
|
@ -98,10 +98,11 @@ namespace SafeExamBrowser.Configuration.Compression
|
|||
|
||||
public byte[] Peek(Stream data, int count)
|
||||
{
|
||||
var stream = new GZipStream(data, CompressionMode.Decompress);
|
||||
|
||||
logger.Debug($"Peeking {count} bytes from '{data}'...");
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using (var stream = new GZipStream(data, CompressionMode.Decompress))
|
||||
using (var decompressed = new MemoryStream())
|
||||
{
|
||||
var buffer = new byte[count];
|
||||
|
|
|
@ -93,6 +93,8 @@ namespace SafeExamBrowser.Configuration
|
|||
{
|
||||
var settings = new Settings();
|
||||
|
||||
// TODO: Specify default settings
|
||||
|
||||
settings.KioskMode = KioskMode.None;
|
||||
settings.ServicePolicy = ServicePolicy.Optional;
|
||||
|
||||
|
@ -122,7 +124,7 @@ namespace SafeExamBrowser.Configuration
|
|||
resourceLoaders.Add(resourceLoader);
|
||||
}
|
||||
|
||||
public LoadStatus TryLoadSettings(Uri resource, out Settings settings, string adminPassword = null, string settingsPassword = null)
|
||||
public LoadStatus TryLoadSettings(Uri resource, out Settings settings, string password = null)
|
||||
{
|
||||
settings = default(Settings);
|
||||
|
||||
|
@ -139,7 +141,7 @@ namespace SafeExamBrowser.Configuration
|
|||
case LoadStatus.LoadWithBrowser:
|
||||
return HandleBrowserResource(resource, out settings);
|
||||
case LoadStatus.Success:
|
||||
return TryParseData(data, out settings, adminPassword, settingsPassword);
|
||||
return TryParseData(data, out settings, password);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,7 +175,7 @@ namespace SafeExamBrowser.Configuration
|
|||
return status;
|
||||
}
|
||||
|
||||
private LoadStatus TryParseData(Stream data, out Settings settings, string adminPassword, string settingsPassword)
|
||||
private LoadStatus TryParseData(Stream data, out Settings settings, string password)
|
||||
{
|
||||
var status = LoadStatus.NotSupported;
|
||||
var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data));
|
||||
|
@ -182,7 +184,7 @@ namespace SafeExamBrowser.Configuration
|
|||
|
||||
if (dataFormat != null)
|
||||
{
|
||||
status = dataFormat.TryParse(data, out settings, adminPassword, settingsPassword);
|
||||
status = dataFormat.TryParse(data, out settings, password);
|
||||
logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {status}.");
|
||||
}
|
||||
else
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* 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.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public partial class BinaryFormat
|
||||
{
|
||||
private const int BLOCK_SIZE = 16;
|
||||
private const int HEADER_SIZE = 2;
|
||||
private const int ITERATIONS = 10000;
|
||||
private const int KEY_SIZE = 32;
|
||||
private const int OPTIONS = 0x1;
|
||||
private const int SALT_SIZE = 8;
|
||||
private const int VERSION = 0x2;
|
||||
|
||||
private LoadStatus ParsePassword(Stream data, FormatType format, out Settings settings, string password = null)
|
||||
{
|
||||
settings = default(Settings);
|
||||
|
||||
if (password == null)
|
||||
{
|
||||
return LoadStatus.PasswordNeeded;
|
||||
}
|
||||
|
||||
var (version, options) = ParseHeader(data);
|
||||
|
||||
if (version != VERSION || options != OPTIONS)
|
||||
{
|
||||
return FailForInvalidPasswordHeader(version, options);
|
||||
}
|
||||
|
||||
if (format == FormatType.PasswordConfigureClient)
|
||||
{
|
||||
// TODO: Shouldn't this not only be done for admin password, and not settings password?!?
|
||||
password = GeneratePasswordHash(password);
|
||||
}
|
||||
|
||||
var (authenticationKey, encryptionKey) = GenerateKeys(data, password);
|
||||
var (originalHmac, computedHmac) = GenerateHmac(data, authenticationKey);
|
||||
|
||||
if (!computedHmac.SequenceEqual(originalHmac))
|
||||
{
|
||||
return FailForInvalidPasswordHmac();
|
||||
}
|
||||
|
||||
using (var plainData = Decrypt(data, encryptionKey, originalHmac.Length))
|
||||
{
|
||||
return ParsePlainData(plainData, out settings);
|
||||
}
|
||||
}
|
||||
|
||||
private (int version, int options) ParseHeader(Stream data)
|
||||
{
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var version = data.ReadByte();
|
||||
var options = data.ReadByte();
|
||||
|
||||
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];
|
||||
var encryptionSalt = new byte[SALT_SIZE];
|
||||
|
||||
data.Seek(HEADER_SIZE, SeekOrigin.Begin);
|
||||
data.Read(encryptionSalt, 0, SALT_SIZE);
|
||||
data.Read(authenticationSalt, 0, SALT_SIZE);
|
||||
|
||||
using (var authenticationGenerator = new Rfc2898DeriveBytes(password, authenticationSalt, ITERATIONS))
|
||||
using (var encryptionGenerator = new Rfc2898DeriveBytes(password, encryptionSalt, ITERATIONS))
|
||||
{
|
||||
var authenticationKey = authenticationGenerator.GetBytes(KEY_SIZE);
|
||||
var encryptionKey = encryptionGenerator.GetBytes(KEY_SIZE);
|
||||
|
||||
return (authenticationKey, encryptionKey);
|
||||
}
|
||||
}
|
||||
|
||||
private (byte[] originalHmac, byte[] computedHmac) GenerateHmac(Stream data, byte[] authenticationKey)
|
||||
{
|
||||
using (var hmac = new HMACSHA256(authenticationKey))
|
||||
{
|
||||
var originalHmac = new byte[hmac.HashSize / 8];
|
||||
var hashStream = new SubStream(data, 0, data.Length - originalHmac.Length);
|
||||
var computedHmac = hmac.ComputeHash(hashStream);
|
||||
|
||||
data.Seek(originalHmac.Length, SeekOrigin.End);
|
||||
data.Read(originalHmac, 0, originalHmac.Length);
|
||||
|
||||
return (originalHmac, computedHmac);
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
data.Seek(HEADER_SIZE + 2 * SALT_SIZE, SeekOrigin.Begin);
|
||||
data.Read(initializationVector, 0, BLOCK_SIZE);
|
||||
|
||||
var decryptedData = new MemoryStream();
|
||||
var encryptedData = new SubStream(data, data.Position, data.Length - data.Position - hmacLength);
|
||||
|
||||
using (var aes = new AesManaged { KeySize = KEY_SIZE * 8, BlockSize = BLOCK_SIZE * 8, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
|
||||
using (var decryptor = aes.CreateDecryptor(encryptionKey, initializationVector))
|
||||
using (var cryptoStream = new CryptoStream(encryptedData, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
cryptoStream.CopyTo(decryptedData);
|
||||
}
|
||||
|
||||
return decryptedData;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public partial class BinaryFormat
|
||||
{
|
||||
private LoadStatus ParsePublicKeyHash(Stream data, out Settings settings, string password)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public partial class BinaryFormat
|
||||
{
|
||||
private LoadStatus ParsePublicKeyHashWithSymmetricKey(Stream data, out Settings settings, string password)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ using SafeExamBrowser.Contracts.Logging;
|
|||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public class BinaryFormat : IDataFormat
|
||||
public partial class BinaryFormat : IDataFormat
|
||||
{
|
||||
private const int PREFIX_LENGTH = 4;
|
||||
|
||||
|
@ -37,7 +37,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
if (longEnough)
|
||||
{
|
||||
var prefix = ParsePrefix(data);
|
||||
var success = TryDetermineFormat(prefix, out DataFormat format);
|
||||
var success = TryDetermineFormat(prefix, out FormatType format);
|
||||
|
||||
logger.Debug($"'{data}' starting with '{prefix}' does {(success ? string.Empty : "not ")}match the binary format.");
|
||||
|
||||
|
@ -54,14 +54,40 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
return false;
|
||||
}
|
||||
|
||||
public LoadStatus TryParse(Stream data, out Settings settings, string adminPassword = null, string settingsPassword = null)
|
||||
public LoadStatus TryParse(Stream data, out Settings settings, string password = null)
|
||||
{
|
||||
settings = new Settings();
|
||||
settings.Browser.AllowAddressBar = true;
|
||||
settings.Browser.StartUrl = "www.duckduckgo.com";
|
||||
settings.Browser.AllowConfigurationDownloads = true;
|
||||
var prefix = ParsePrefix(data);
|
||||
var success = TryDetermineFormat(prefix, out FormatType format);
|
||||
|
||||
return LoadStatus.Success;
|
||||
settings = default(Settings);
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (compressor.IsCompressed(data))
|
||||
{
|
||||
data = compressor.Decompress(data);
|
||||
}
|
||||
|
||||
data = new SubStream(data, PREFIX_LENGTH, data.Length - PREFIX_LENGTH);
|
||||
|
||||
// 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);
|
||||
case FormatType.PlainData:
|
||||
return ParsePlainData(data, out settings);
|
||||
case FormatType.PublicKeyHash:
|
||||
return ParsePublicKeyHash(data, out settings, password);
|
||||
case FormatType.PublicKeyHashSymmetricKey:
|
||||
return ParsePublicKeyHashWithSymmetricKey(data, out settings, password);
|
||||
}
|
||||
}
|
||||
|
||||
logger.Error($"'{data}' starting with '{prefix}' does not match the binary format!");
|
||||
|
||||
return LoadStatus.InvalidData;
|
||||
}
|
||||
|
||||
private string ParsePrefix(Stream data)
|
||||
|
@ -81,39 +107,39 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
return Encoding.UTF8.GetString(prefixData);
|
||||
}
|
||||
|
||||
private bool TryDetermineFormat(string prefix, out DataFormat format)
|
||||
private bool TryDetermineFormat(string prefix, out FormatType format)
|
||||
{
|
||||
format = default(DataFormat);
|
||||
format = default(FormatType);
|
||||
|
||||
switch (prefix)
|
||||
{
|
||||
case "pswd":
|
||||
format = DataFormat.Password;
|
||||
format = FormatType.Password;
|
||||
return true;
|
||||
case "pwcc":
|
||||
format = DataFormat.PasswordForConfigureClient;
|
||||
format = FormatType.PasswordConfigureClient;
|
||||
return true;
|
||||
case "plnd":
|
||||
format = DataFormat.PlainData;
|
||||
format = FormatType.PlainData;
|
||||
return true;
|
||||
case "pkhs":
|
||||
format = DataFormat.PublicKeyHash;
|
||||
format = FormatType.PublicKeyHash;
|
||||
return true;
|
||||
case "phsk":
|
||||
format = DataFormat.PublicKeyHashWithSymmetricKey;
|
||||
format = FormatType.PublicKeyHashSymmetricKey;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private enum DataFormat
|
||||
private enum FormatType
|
||||
{
|
||||
Password = 1,
|
||||
PasswordForConfigureClient,
|
||||
PasswordConfigureClient,
|
||||
PlainData,
|
||||
PublicKeyHash,
|
||||
PublicKeyHashWithSymmetricKey
|
||||
PublicKeyHashSymmetricKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
return false;
|
||||
}
|
||||
|
||||
public LoadStatus TryParse(Stream data, out Settings settings, string adminPassword = null, string settingsPassword = null)
|
||||
public LoadStatus TryParse(Stream data, out Settings settings, string password = null)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
|
|
@ -49,18 +49,34 @@
|
|||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Compression\GZipCompressor.cs" />
|
||||
<Compile Include="DataFormats\BinaryFormat.cs" />
|
||||
<Compile Include="DataFormats\BinaryFormat.Password.cs">
|
||||
<DependentUpon>BinaryFormat.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="DataFormats\BinaryFormat.PlainData.cs">
|
||||
<DependentUpon>BinaryFormat.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="DataFormats\BinaryFormat.PublicKeyHash.cs">
|
||||
<DependentUpon>BinaryFormat.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="DataFormats\BinaryFormat.PublicKeyHashWithSymmetricKey.cs">
|
||||
<DependentUpon>BinaryFormat.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="DataFormats\XmlFormat.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ConfigurationRepository.cs" />
|
||||
<Compile Include="ResourceLoaders\FileResourceLoader.cs" />
|
||||
<Compile Include="ResourceLoaders\NetworkResourceLoader.cs" />
|
||||
<Compile Include="SessionConfiguration.cs" />
|
||||
<Compile Include="SubStream.cs" />
|
||||
<Compile Include="SystemInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -69,5 +85,8 @@
|
|||
<Name>SafeExamBrowser.Contracts</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
141
SafeExamBrowser.Configuration/SubStream.cs
Normal file
141
SafeExamBrowser.Configuration/SubStream.cs
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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.IO;
|
||||
|
||||
namespace SafeExamBrowser.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// A read-only wrapper for a subsection of another, larger stream.
|
||||
/// TODO: Unit Test!
|
||||
/// </summary>
|
||||
internal class SubStream : Stream
|
||||
{
|
||||
private long length;
|
||||
private long offset;
|
||||
private Stream original;
|
||||
|
||||
public override bool CanRead => original.CanRead;
|
||||
public override bool CanSeek => original.CanSeek;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => length;
|
||||
public override long Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new wrapper for the specified subsection of the given stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// Below an example of a subsection within a stream:
|
||||
///
|
||||
/// +==============+==============================================================+==============================+
|
||||
/// | ... |####################### subsection ###########################| ... |
|
||||
/// +==============+==============================================================+==============================+
|
||||
/// ^ ^ ^ ^
|
||||
/// | | | |
|
||||
/// | + offset + length |
|
||||
/// | |
|
||||
/// + start of original end of original +
|
||||
///
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentException">In case the original stream does not support <see cref="Stream.CanRead"/>.</exception>
|
||||
/// <exception cref="ArgumentException">In case the original stream does not support <see cref="Stream.CanSeek"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">In case the specified subsection is outside the bounds of the original stream.</exception>
|
||||
public SubStream(Stream original, long offset, long length)
|
||||
{
|
||||
this.original = original;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
|
||||
if (!original.CanRead)
|
||||
{
|
||||
throw new ArgumentException("The original stream must support reading!", nameof(original));
|
||||
}
|
||||
|
||||
if (!original.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("The original stream must support seeking!", nameof(original));
|
||||
}
|
||||
|
||||
if (original.Length < offset + length || offset < 0 || length < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Specified subsection is outside the bounds of the original stream!");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var originalPosition = original.Position;
|
||||
|
||||
if (Position < 0 || Position >= Length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Position + count >= Length)
|
||||
{
|
||||
count = Convert.ToInt32(Length - Position);
|
||||
}
|
||||
|
||||
original.Seek(this.offset + Position, SeekOrigin.Begin);
|
||||
|
||||
var bytesRead = original.Read(buffer, offset, count);
|
||||
|
||||
Position += bytesRead;
|
||||
original.Seek(originalPosition, SeekOrigin.Begin);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (Position < 0 || Position >= Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return base.ReadByte();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
Position = offset;
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
Position += offset;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
Position = length - offset;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Seeking from position '{origin}' is not implemented!");
|
||||
}
|
||||
|
||||
return Position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
4
SafeExamBrowser.Configuration/packages.config
Normal file
4
SafeExamBrowser.Configuration/packages.config
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net452" />
|
||||
</packages>
|
|
@ -41,9 +41,9 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
void Register(IResourceLoader resourceLoader);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load settings from the specified resource, using the optional passwords. As long as the result is not
|
||||
/// Attempts to load settings from the specified resource, using the optional password. As long as the result is not
|
||||
/// <see cref="LoadStatus.Success"/>, the referenced settings may be <c>null</c> or in an undefinable state!
|
||||
/// </summary>
|
||||
LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, string adminPassword = null, string settingsPassword = null);
|
||||
LoadStatus TryLoadSettings(Uri resource, out Settings.Settings settings, string password = null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
bool CanParse(Stream data);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse the given data, using the optional passwords. As long as the result is not <see cref="LoadStatus.Success"/>,
|
||||
/// Tries to parse the given data, using the optional password. As long as the result is not <see cref="LoadStatus.Success"/>,
|
||||
/// the referenced settings may be <c>null</c> or in an undefinable state!
|
||||
/// </summary>
|
||||
LoadStatus TryParse(Stream data, out Settings.Settings settings, string adminPassword = null, string settingsPassword = null);
|
||||
LoadStatus TryParse(Stream data, out Settings.Settings settings, string password = null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,15 +13,10 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// </summary>
|
||||
public enum LoadStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that an admin password is needed in order to load the settings.
|
||||
/// </summary>
|
||||
AdminPasswordNeeded = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a resource does not comply with the declared data format.
|
||||
/// </summary>
|
||||
InvalidData,
|
||||
InvalidData = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a resource needs to be loaded with the browser.
|
||||
|
@ -34,9 +29,9 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
NotSupported,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a settings password is needed in order to load the settings.
|
||||
/// Indicates that a password is needed in order to load the settings.
|
||||
/// </summary>
|
||||
SettingsPasswordNeeded,
|
||||
PasswordNeeded,
|
||||
|
||||
/// <summary>
|
||||
/// The settings were loaded successfully.
|
||||
|
|
|
@ -60,14 +60,14 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
appConfig.ProgramDataFolder = location;
|
||||
appConfig.AppDataFolder = location;
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), 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<Uri>(u => u.Equals(resource)), out settings, null, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, null), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -78,14 +78,14 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
appConfig.ProgramDataFolder = location;
|
||||
appConfig.AppDataFolder = $@"{location}\WRONG";
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.Perform();
|
||||
|
||||
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt"));
|
||||
|
||||
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, null, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, null), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -95,14 +95,14 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
appConfig.AppDataFolder = location;
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.Perform();
|
||||
|
||||
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt"));
|
||||
|
||||
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, null, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, null), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -127,7 +127,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
public void MustAbortIfWishedByUser()
|
||||
{
|
||||
appConfig.ProgramDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations));
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -146,7 +146,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
[TestMethod]
|
||||
public void MustNotAbortIfNotWishedByUser()
|
||||
{
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -166,7 +166,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
public void MustNotAllowToAbortIfNotInConfigureClientMode()
|
||||
{
|
||||
settings.ConfigurationMode = ConfigurationMode.Exam;
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -218,7 +218,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
{
|
||||
var url = @"http://www.safeexambrowser.org/whatever.seb";
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.AdminPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -231,7 +231,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
sut.Perform();
|
||||
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null), Times.Exactly(5));
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null), Times.Exactly(5));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -239,7 +239,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
{
|
||||
var url = @"http://www.safeexambrowser.org/whatever.seb";
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -252,7 +252,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
sut.Perform();
|
||||
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null), Times.Exactly(5));
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null), Times.Exactly(5));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -261,8 +261,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
var password = "test";
|
||||
var url = @"http://www.safeexambrowser.org/whatever.seb";
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.AdminPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, password, null)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, password)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -276,8 +276,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
sut.Perform();
|
||||
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, password, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, password), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -286,8 +286,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
var password = "test";
|
||||
var url = @"http://www.safeexambrowser.org/whatever.seb";
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, password)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, password)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -301,8 +301,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
sut.Perform();
|
||||
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, password), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, password), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -310,7 +310,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
{
|
||||
var url = @"http://www.safeexambrowser.org/whatever.seb";
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.AdminPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -331,7 +331,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
{
|
||||
var url = @"http://www.safeexambrowser.org/whatever.seb";
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -354,9 +354,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
var settingsPassword = "abc";
|
||||
var url = @"http://www.safeexambrowser.org/whatever.seb";
|
||||
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, settingsPassword)).Returns(LoadStatus.AdminPasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, adminPassword, settingsPassword)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, settingsPassword)).Returns(LoadStatus.Success);
|
||||
|
||||
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
|
||||
sut.ActionRequired += args =>
|
||||
|
@ -370,9 +369,9 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
sut.Perform();
|
||||
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, settingsPassword), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, adminPassword, settingsPassword), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, settingsPassword), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, settingsPassword), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -382,13 +381,13 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
var resource = new Uri(Path.Combine(location, nameof(Operations), "SettingsDummy.txt"));
|
||||
|
||||
sessionContext.ReconfigurationFilePath = resource.LocalPath;
|
||||
repository.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), 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<Uri>(u => u.Equals(resource)), out settings, null, null), Times.Once);
|
||||
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, null), Times.Once);
|
||||
|
||||
Assert.AreEqual(OperationResult.Success, result);
|
||||
}
|
||||
|
@ -399,19 +398,19 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
var resource = new Uri("file:///C:/does/not/exist.txt");
|
||||
|
||||
sessionContext.ReconfigurationFilePath = null;
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, null, null)).Returns(LoadStatus.Success);
|
||||
repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), 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<Uri>(u => u.Equals(resource)), out settings, null, null), Times.Never);
|
||||
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(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<Uri>(u => u.Equals(resource)), out settings, null, null), Times.Never);
|
||||
repository.Verify(r => r.TryLoadSettings(It.Is<Uri>(u => u.Equals(resource)), out settings, null), Times.Never);
|
||||
Assert.AreEqual(OperationResult.Failed, result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,9 +73,8 @@
|
|||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.4.0\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
<package id="MSTest.TestAdapter" version="1.2.0" targetFramework="net461" />
|
||||
<package id="MSTest.TestFramework" version="1.2.0" targetFramework="net461" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.4.0" targetFramework="net461" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net461" />
|
||||
<package id="System.ValueTuple" version="4.5.0" targetFramework="net461" />
|
||||
</packages>
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
namespace SafeExamBrowser.Runtime
|
||||
|
@ -60,6 +61,11 @@ namespace SafeExamBrowser.Runtime
|
|||
instances.BuildObjectGraph(Shutdown);
|
||||
instances.LogStartupInformation();
|
||||
|
||||
Task.Run(new Action(TryStart));
|
||||
}
|
||||
|
||||
private void TryStart()
|
||||
{
|
||||
var success = instances.RuntimeController.TryStart();
|
||||
|
||||
if (!success)
|
||||
|
@ -70,18 +76,15 @@ namespace SafeExamBrowser.Runtime
|
|||
|
||||
public new void Shutdown()
|
||||
{
|
||||
void shutdown()
|
||||
{
|
||||
instances.RuntimeController.Terminate();
|
||||
instances.LogShutdownInformation();
|
||||
Task.Run(new Action(ShutdownInternal));
|
||||
}
|
||||
|
||||
// TODO: Which UI operation is being cancelled without the timeout? Is this only a debugger issue? Same problem with client? -> Debug!
|
||||
Thread.Sleep(20);
|
||||
private void ShutdownInternal()
|
||||
{
|
||||
instances.RuntimeController.Terminate();
|
||||
instances.LogShutdownInformation();
|
||||
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
Dispatcher.BeginInvoke(new Action(shutdown));
|
||||
Dispatcher.Invoke(base.Shutdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,34 +159,37 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
var terminatedEvent = new AutoResetEvent(false);
|
||||
var terminatedEventHandler = new ProcessTerminatedEventHandler((_) => terminatedEvent.Set());
|
||||
|
||||
runtimeHost.ClientDisconnected += disconnectedEventHandler;
|
||||
ClientProcess.Terminated += terminatedEventHandler;
|
||||
|
||||
logger.Info("Instructing client to initiate shutdown procedure.");
|
||||
ClientProxy.InitiateShutdown();
|
||||
|
||||
logger.Info("Disconnecting from client communication host.");
|
||||
ClientProxy.Disconnect();
|
||||
|
||||
logger.Info("Waiting for client to disconnect from runtime communication host...");
|
||||
disconnected = disconnectedEvent.WaitOne(timeout_ms);
|
||||
|
||||
if (!disconnected)
|
||||
if (ClientProxy != null)
|
||||
{
|
||||
logger.Error($"Client failed to disconnect within {timeout_ms / 1000} seconds!");
|
||||
runtimeHost.ClientDisconnected += disconnectedEventHandler;
|
||||
ClientProcess.Terminated += terminatedEventHandler;
|
||||
|
||||
logger.Info("Instructing client to initiate shutdown procedure.");
|
||||
ClientProxy.InitiateShutdown();
|
||||
|
||||
logger.Info("Disconnecting from client communication host.");
|
||||
ClientProxy.Disconnect();
|
||||
|
||||
logger.Info("Waiting for client to disconnect from runtime communication host...");
|
||||
disconnected = disconnectedEvent.WaitOne(timeout_ms);
|
||||
|
||||
if (!disconnected)
|
||||
{
|
||||
logger.Error($"Client failed to disconnect within {timeout_ms / 1000} seconds!");
|
||||
}
|
||||
|
||||
logger.Info("Waiting for client process to terminate...");
|
||||
terminated = terminatedEvent.WaitOne(timeout_ms);
|
||||
|
||||
if (!terminated)
|
||||
{
|
||||
logger.Error($"Client failed to terminate within {timeout_ms / 1000} seconds!");
|
||||
}
|
||||
|
||||
runtimeHost.ClientDisconnected -= disconnectedEventHandler;
|
||||
ClientProcess.Terminated -= terminatedEventHandler;
|
||||
}
|
||||
|
||||
logger.Info("Waiting for client process to terminate...");
|
||||
terminated = terminatedEvent.WaitOne(timeout_ms);
|
||||
|
||||
if (!terminated)
|
||||
{
|
||||
logger.Error($"Client failed to terminate within {timeout_ms / 1000} seconds!");
|
||||
}
|
||||
|
||||
runtimeHost.ClientDisconnected -= disconnectedEventHandler;
|
||||
ClientProcess.Terminated -= terminatedEventHandler;
|
||||
|
||||
if (disconnected && terminated)
|
||||
{
|
||||
logger.Info("Client has been successfully terminated.");
|
||||
|
|
|
@ -99,33 +99,18 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
private OperationResult LoadSettings(Uri uri)
|
||||
{
|
||||
var adminPassword = default(string);
|
||||
var settingsPassword = default(string);
|
||||
var settings = default(Settings);
|
||||
var status = default(LoadStatus);
|
||||
var status = configuration.TryLoadSettings(uri, out Settings settings);
|
||||
|
||||
for (int adminAttempts = 0, settingsAttempts = 0; adminAttempts < 5 && settingsAttempts < 5;)
|
||||
for (var attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
|
||||
{
|
||||
status = configuration.TryLoadSettings(uri, out settings, adminPassword, settingsPassword);
|
||||
var result = TryGetPassword(status);
|
||||
|
||||
if (status == LoadStatus.AdminPasswordNeeded || status == LoadStatus.SettingsPasswordNeeded)
|
||||
if (!result.Success)
|
||||
{
|
||||
var result = TryGetPassword(status);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return OperationResult.Aborted;
|
||||
}
|
||||
|
||||
adminAttempts += status == LoadStatus.AdminPasswordNeeded ? 1 : 0;
|
||||
adminPassword = status == LoadStatus.AdminPasswordNeeded ? result.Password : adminPassword;
|
||||
settingsAttempts += status == LoadStatus.SettingsPasswordNeeded ? 1 : 0;
|
||||
settingsPassword = status == LoadStatus.SettingsPasswordNeeded ? result.Password : settingsPassword;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
return OperationResult.Aborted;
|
||||
}
|
||||
|
||||
status = configuration.TryLoadSettings(uri, out settings, result.Password);
|
||||
}
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
|
@ -158,7 +143,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
private PasswordRequiredEventArgs TryGetPassword(LoadStatus status)
|
||||
{
|
||||
var purpose = status == LoadStatus.AdminPasswordNeeded ? PasswordRequestPurpose.Administrator : PasswordRequestPurpose.Settings;
|
||||
var purpose = PasswordRequestPurpose.Settings;
|
||||
var args = new PasswordRequiredEventArgs { Purpose = purpose };
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Window.Resources>
|
||||
<Grid>
|
||||
<Grid FocusManager.FocusedElement="{Binding ElementName=Password}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="4*" />
|
||||
<RowDefinition Height="2*" />
|
||||
<RowDefinition Height="1*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0">
|
||||
<Grid>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using SafeExamBrowser.Contracts.I18n;
|
||||
using SafeExamBrowser.Contracts.UserInterface.Taskbar.Events;
|
||||
using SafeExamBrowser.Contracts.UserInterface.Windows;
|
||||
|
@ -72,6 +73,7 @@ namespace SafeExamBrowser.UserInterface.Classic
|
|||
ConfirmButton.Click += ConfirmButton_Click;
|
||||
|
||||
Closing += (o, args) => closing?.Invoke();
|
||||
Password.KeyUp += Password_KeyUp;
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
|
@ -86,6 +88,15 @@ namespace SafeExamBrowser.UserInterface.Classic
|
|||
Close();
|
||||
}
|
||||
|
||||
private void Password_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private class PasswordDialogResult : IPasswordDialogResult
|
||||
{
|
||||
public string Password { get; set; }
|
||||
|
|
|
@ -141,6 +141,10 @@ namespace SafeExamBrowser.UserInterface.Classic
|
|||
StatusTextBlock.DataContext = model;
|
||||
|
||||
Closing += (o, args) => args.Cancel = !allowClose;
|
||||
|
||||
#if DEBUG
|
||||
Topmost = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void RuntimeWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
|
|
|
@ -90,26 +90,7 @@ namespace SafeExamBrowser.UserInterface.Classic
|
|||
|
||||
public IRuntimeWindow CreateRuntimeWindow(AppConfig appConfig)
|
||||
{
|
||||
RuntimeWindow runtimeWindow = null;
|
||||
var windowReadyEvent = new AutoResetEvent(false);
|
||||
var runtimeWindowThread = new Thread(() =>
|
||||
{
|
||||
runtimeWindow = new RuntimeWindow(appConfig, text);
|
||||
runtimeWindow.Closed += (o, args) => runtimeWindow.Dispatcher.InvokeShutdown();
|
||||
|
||||
windowReadyEvent.Set();
|
||||
|
||||
System.Windows.Threading.Dispatcher.Run();
|
||||
});
|
||||
|
||||
runtimeWindowThread.SetApartmentState(ApartmentState.STA);
|
||||
runtimeWindowThread.Name = nameof(RuntimeWindow);
|
||||
runtimeWindowThread.IsBackground = true;
|
||||
runtimeWindowThread.Start();
|
||||
|
||||
windowReadyEvent.WaitOne();
|
||||
|
||||
return runtimeWindow;
|
||||
return Application.Current.Dispatcher.Invoke(() => new RuntimeWindow(appConfig, text));
|
||||
}
|
||||
|
||||
public ISplashScreen CreateSplashScreen(AppConfig appConfig = null)
|
||||
|
|
Loading…
Reference in a new issue