diff --git a/SafeExamBrowser.Browser/BrowserApplicationController.cs b/SafeExamBrowser.Browser/BrowserApplicationController.cs
index 063c9f59..81fc220f 100644
--- a/SafeExamBrowser.Browser/BrowserApplicationController.cs
+++ b/SafeExamBrowser.Browser/BrowserApplicationController.cs
@@ -127,7 +127,7 @@ namespace SafeExamBrowser.Browser
private void Button_OnClick(InstanceIdentifier id = null)
{
- if (id is null)
+ if (id == null)
{
CreateNewInstance();
}
diff --git a/SafeExamBrowser.Client.UnitTests/SafeExamBrowser.Client.UnitTests.csproj b/SafeExamBrowser.Client.UnitTests/SafeExamBrowser.Client.UnitTests.csproj
index 08a78944..f555208c 100644
--- a/SafeExamBrowser.Client.UnitTests/SafeExamBrowser.Client.UnitTests.csproj
+++ b/SafeExamBrowser.Client.UnitTests/SafeExamBrowser.Client.UnitTests.csproj
@@ -71,9 +71,8 @@
..\packages\System.Threading.Tasks.Extensions.4.4.0\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll
-
- ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll
- True
+
+ ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll
@@ -96,8 +95,12 @@
-
-
+
+ Designer
+
+
+ Designer
+
diff --git a/SafeExamBrowser.Client.UnitTests/app.config b/SafeExamBrowser.Client.UnitTests/app.config
index 7b7e0134..0f9345fa 100644
--- a/SafeExamBrowser.Client.UnitTests/app.config
+++ b/SafeExamBrowser.Client.UnitTests/app.config
@@ -8,7 +8,7 @@
-
+
diff --git a/SafeExamBrowser.Client.UnitTests/packages.config b/SafeExamBrowser.Client.UnitTests/packages.config
index 3912bdfc..19774a67 100644
--- a/SafeExamBrowser.Client.UnitTests/packages.config
+++ b/SafeExamBrowser.Client.UnitTests/packages.config
@@ -5,5 +5,5 @@
-
+
\ No newline at end of file
diff --git a/SafeExamBrowser.Communication/Proxies/BaseProxy.cs b/SafeExamBrowser.Communication/Proxies/BaseProxy.cs
index bb31c3c6..75d9cb39 100644
--- a/SafeExamBrowser.Communication/Proxies/BaseProxy.cs
+++ b/SafeExamBrowser.Communication/Proxies/BaseProxy.cs
@@ -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));
}
diff --git a/SafeExamBrowser.Configuration/Compression/GZipCompressor.cs b/SafeExamBrowser.Configuration/Compression/GZipCompressor.cs
index 43d09b93..e10d542d 100644
--- a/SafeExamBrowser.Configuration/Compression/GZipCompressor.cs
+++ b/SafeExamBrowser.Configuration/Compression/GZipCompressor.cs
@@ -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];
diff --git a/SafeExamBrowser.Configuration/ConfigurationRepository.cs b/SafeExamBrowser.Configuration/ConfigurationRepository.cs
index 141be2c6..c566a060 100644
--- a/SafeExamBrowser.Configuration/ConfigurationRepository.cs
+++ b/SafeExamBrowser.Configuration/ConfigurationRepository.cs
@@ -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
diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.Password.cs b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.Password.cs
new file mode 100644
index 00000000..000e5871
--- /dev/null
+++ b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.Password.cs
@@ -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;
+ }
+ }
+}
diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PlainData.cs b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PlainData.cs
new file mode 100644
index 00000000..f811a4bd
--- /dev/null
+++ b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PlainData.cs
@@ -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;
+ }
+ }
+}
diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHash.cs b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHash.cs
new file mode 100644
index 00000000..ed848ffa
--- /dev/null
+++ b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHash.cs
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+using 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();
+ }
+ }
+}
diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHashWithSymmetricKey.cs b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHashWithSymmetricKey.cs
new file mode 100644
index 00000000..439f9d8f
--- /dev/null
+++ b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.PublicKeyHashWithSymmetricKey.cs
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+using 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();
+ }
+ }
+}
diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs
index ecbb1e3a..0e96d1a6 100644
--- a/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs
+++ b/SafeExamBrowser.Configuration/DataFormats/BinaryFormat.cs
@@ -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
}
}
}
diff --git a/SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs b/SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs
index 5b882fc7..a3fa0ae3 100644
--- a/SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs
+++ b/SafeExamBrowser.Configuration/DataFormats/XmlFormat.cs
@@ -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();
}
diff --git a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
index aa22a3c4..c3e497ad 100644
--- a/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
+++ b/SafeExamBrowser.Configuration/SafeExamBrowser.Configuration.csproj
@@ -49,18 +49,34 @@
+
+ ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll
+
+
+ BinaryFormat.cs
+
+
+ BinaryFormat.cs
+
+
+ BinaryFormat.cs
+
+
+ BinaryFormat.cs
+
+
@@ -69,5 +85,8 @@
SafeExamBrowser.Contracts
+
+
+
\ No newline at end of file
diff --git a/SafeExamBrowser.Configuration/SubStream.cs b/SafeExamBrowser.Configuration/SubStream.cs
new file mode 100644
index 00000000..7cdfaf41
--- /dev/null
+++ b/SafeExamBrowser.Configuration/SubStream.cs
@@ -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
+{
+ ///
+ /// A read-only wrapper for a subsection of another, larger stream.
+ /// TODO: Unit Test!
+ ///
+ 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; }
+
+ ///
+ /// Creates a new wrapper for the specified subsection of the given stream.
+ ///
+ ///
+ ///
+ /// Below an example of a subsection within a stream:
+ ///
+ /// +==============+==============================================================+==============================+
+ /// | ... |####################### subsection ###########################| ... |
+ /// +==============+==============================================================+==============================+
+ /// ^ ^ ^ ^
+ /// | | | |
+ /// | + offset + length |
+ /// | |
+ /// + start of original end of original +
+ ///
+ ///
+ /// In case the original stream does not support .
+ /// In case the original stream does not support .
+ /// In case the specified subsection is outside the bounds of the original stream.
+ 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();
+ }
+ }
+}
diff --git a/SafeExamBrowser.Configuration/packages.config b/SafeExamBrowser.Configuration/packages.config
new file mode 100644
index 00000000..8653b8e4
--- /dev/null
+++ b/SafeExamBrowser.Configuration/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs
index f2362488..fd68ebda 100644
--- a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs
+++ b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs
@@ -41,9 +41,9 @@ namespace SafeExamBrowser.Contracts.Configuration
void Register(IResourceLoader resourceLoader);
///
- /// 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
/// , the referenced settings may be null or in an undefinable state!
///
- 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);
}
}
diff --git a/SafeExamBrowser.Contracts/Configuration/IDataFormat.cs b/SafeExamBrowser.Contracts/Configuration/IDataFormat.cs
index 668f6289..dd2c6c37 100644
--- a/SafeExamBrowser.Contracts/Configuration/IDataFormat.cs
+++ b/SafeExamBrowser.Contracts/Configuration/IDataFormat.cs
@@ -21,9 +21,9 @@ namespace SafeExamBrowser.Contracts.Configuration
bool CanParse(Stream data);
///
- /// Tries to parse the given data, using the optional passwords. As long as the result is not ,
+ /// Tries to parse the given data, using the optional password. As long as the result is not ,
/// the referenced settings may be null or in an undefinable state!
///
- LoadStatus TryParse(Stream data, out Settings.Settings settings, string adminPassword = null, string settingsPassword = null);
+ LoadStatus TryParse(Stream data, out Settings.Settings settings, string password = null);
}
}
diff --git a/SafeExamBrowser.Contracts/Configuration/LoadStatus.cs b/SafeExamBrowser.Contracts/Configuration/LoadStatus.cs
index 698d08f2..295d661d 100644
--- a/SafeExamBrowser.Contracts/Configuration/LoadStatus.cs
+++ b/SafeExamBrowser.Contracts/Configuration/LoadStatus.cs
@@ -13,15 +13,10 @@ namespace SafeExamBrowser.Contracts.Configuration
///
public enum LoadStatus
{
- ///
- /// Indicates that an admin password is needed in order to load the settings.
- ///
- AdminPasswordNeeded = 1,
-
///
/// Indicates that a resource does not comply with the declared data format.
///
- InvalidData,
+ InvalidData = 1,
///
/// Indicates that a resource needs to be loaded with the browser.
@@ -34,9 +29,9 @@ namespace SafeExamBrowser.Contracts.Configuration
NotSupported,
///
- /// 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.
///
- SettingsPasswordNeeded,
+ PasswordNeeded,
///
/// The settings were loaded successfully.
diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs
index 58d17b7f..786916fe 100644
--- a/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs
+++ b/SafeExamBrowser.Runtime.UnitTests/Operations/ConfigurationOperationTests.cs
@@ -60,14 +60,14 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
appConfig.ProgramDataFolder = location;
appConfig.AppDataFolder = location;
- repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null, null)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
sut.Perform();
var resource = new Uri(url);
- repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null, null), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.Is(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(), out settings, null, null)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
sut.Perform();
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt"));
- repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null, null), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.Is(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(), out settings, null, null)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
sut.Perform();
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt"));
- repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null, null), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.Is(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(), out settings, null, null)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
sut.ActionRequired += args =>
@@ -146,7 +146,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod]
public void MustNotAbortIfNotWishedByUser()
{
- repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null, null)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
sut.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(), out settings, null, null)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
sut.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(), out settings, null, null)).Returns(LoadStatus.AdminPasswordNeeded);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
sut.ActionRequired += args =>
@@ -231,7 +231,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
sut.Perform();
- repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null, null), Times.Exactly(5));
+ repository.Verify(r => r.TryLoadSettings(It.IsAny(), 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(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
sut.ActionRequired += args =>
@@ -252,7 +252,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
sut.Perform();
- repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null, null), Times.Exactly(5));
+ repository.Verify(r => r.TryLoadSettings(It.IsAny(), 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(), out settings, null, null)).Returns(LoadStatus.AdminPasswordNeeded);
- repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, password, null)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, password)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
sut.ActionRequired += args =>
@@ -276,8 +276,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
sut.Perform();
- repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null, null), Times.Once);
- repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, password, null), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, password), Times.Once);
}
[TestMethod]
@@ -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(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
- repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null, password)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, password)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
sut.ActionRequired += args =>
@@ -301,8 +301,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
sut.Perform();
- repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null, null), Times.Once);
- repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null, password), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, password), Times.Once);
}
[TestMethod]
@@ -310,7 +310,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var url = @"http://www.safeexambrowser.org/whatever.seb";
- repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null, null)).Returns(LoadStatus.AdminPasswordNeeded);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
sut.ActionRequired += args =>
@@ -331,7 +331,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var url = @"http://www.safeexambrowser.org/whatever.seb";
- repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
sut.ActionRequired += args =>
@@ -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(), out settings, null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
- repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null, settingsPassword)).Returns(LoadStatus.AdminPasswordNeeded);
- repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, adminPassword, settingsPassword)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.PasswordNeeded);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, settingsPassword)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext);
sut.ActionRequired += args =>
@@ -370,9 +369,9 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
sut.Perform();
- repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null, null), Times.Once);
- repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null, settingsPassword), Times.Once);
- repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, adminPassword, settingsPassword), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, null), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, settingsPassword), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.IsAny(), out settings, settingsPassword), Times.Once);
}
[TestMethod]
@@ -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(u => u.Equals(resource)), out settings, null, null)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
var result = sut.Repeat();
- repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null, null), Times.Once);
+ repository.Verify(r => r.TryLoadSettings(It.Is(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(), out settings, null, null)).Returns(LoadStatus.Success);
+ repository.Setup(r => r.TryLoadSettings(It.IsAny(), out settings, null)).Returns(LoadStatus.Success);
sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext);
var result = sut.Repeat();
- repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null, null), Times.Never);
+ repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Never);
Assert.AreEqual(OperationResult.Failed, result);
sessionContext.ReconfigurationFilePath = resource.LocalPath;
result = sut.Repeat();
- repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null, null), Times.Never);
+ repository.Verify(r => r.TryLoadSettings(It.Is(u => u.Equals(resource)), out settings, null), Times.Never);
Assert.AreEqual(OperationResult.Failed, result);
}
}
diff --git a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj
index db89bd65..bf2e1a3e 100644
--- a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj
+++ b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj
@@ -73,9 +73,8 @@
..\packages\System.Threading.Tasks.Extensions.4.4.0\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll
-
- ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll
- True
+
+ ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll
diff --git a/SafeExamBrowser.Runtime.UnitTests/app.config b/SafeExamBrowser.Runtime.UnitTests/app.config
index 7b7e0134..0f9345fa 100644
--- a/SafeExamBrowser.Runtime.UnitTests/app.config
+++ b/SafeExamBrowser.Runtime.UnitTests/app.config
@@ -8,7 +8,7 @@
-
+
diff --git a/SafeExamBrowser.Runtime.UnitTests/packages.config b/SafeExamBrowser.Runtime.UnitTests/packages.config
index 3912bdfc..19774a67 100644
--- a/SafeExamBrowser.Runtime.UnitTests/packages.config
+++ b/SafeExamBrowser.Runtime.UnitTests/packages.config
@@ -5,5 +5,5 @@
-
+
\ No newline at end of file
diff --git a/SafeExamBrowser.Runtime/App.cs b/SafeExamBrowser.Runtime/App.cs
index 8dc0f138..d79a2c3e 100644
--- a/SafeExamBrowser.Runtime/App.cs
+++ b/SafeExamBrowser.Runtime/App.cs
@@ -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);
}
}
}
diff --git a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs
index e21a5fdd..81815abf 100644
--- a/SafeExamBrowser.Runtime/Operations/ClientOperation.cs
+++ b/SafeExamBrowser.Runtime/Operations/ClientOperation.cs
@@ -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.");
diff --git a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs
index 858f1bf0..34f56dc7 100644
--- a/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs
+++ b/SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs
@@ -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);
diff --git a/SafeExamBrowser.UserInterface.Classic/PasswordDialog.xaml b/SafeExamBrowser.UserInterface.Classic/PasswordDialog.xaml
index c541bbaf..a8a44544 100644
--- a/SafeExamBrowser.UserInterface.Classic/PasswordDialog.xaml
+++ b/SafeExamBrowser.UserInterface.Classic/PasswordDialog.xaml
@@ -13,10 +13,10 @@
-
+
-
+
diff --git a/SafeExamBrowser.UserInterface.Classic/PasswordDialog.xaml.cs b/SafeExamBrowser.UserInterface.Classic/PasswordDialog.xaml.cs
index 60c88e00..e637b40e 100644
--- a/SafeExamBrowser.UserInterface.Classic/PasswordDialog.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Classic/PasswordDialog.xaml.cs
@@ -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; }
diff --git a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs
index 5fc4d1ba..f731a7f5 100644
--- a/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs
+++ b/SafeExamBrowser.UserInterface.Classic/RuntimeWindow.xaml.cs
@@ -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)
diff --git a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs
index 5415680e..f2c513a6 100644
--- a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs
+++ b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs
@@ -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)