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) | 		private void Button_OnClick(InstanceIdentifier id = null) | ||||||
| 		{ | 		{ | ||||||
| 			if (id is null) | 			if (id == null) | ||||||
| 			{ | 			{ | ||||||
| 				CreateNewInstance(); | 				CreateNewInstance(); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -71,9 +71,8 @@ | ||||||
|     <Reference Include="System.Threading.Tasks.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> |     <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> |       <HintPath>..\packages\System.Threading.Tasks.Extensions.4.4.0\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath> | ||||||
|     </Reference> |     </Reference> | ||||||
|     <Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> |     <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> | ||||||
|       <HintPath>..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll</HintPath> |       <HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> | ||||||
|       <Private>True</Private> |  | ||||||
|     </Reference> |     </Reference> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  | @ -96,8 +95,12 @@ | ||||||
|     <Compile Include="ClientControllerTests.cs" /> |     <Compile Include="ClientControllerTests.cs" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <None Include="app.config" /> |     <None Include="app.config"> | ||||||
|     <None Include="packages.config" /> |       <SubType>Designer</SubType> | ||||||
|  |     </None> | ||||||
|  |     <None Include="packages.config"> | ||||||
|  |       <SubType>Designer</SubType> | ||||||
|  |     </None> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <ProjectReference Include="..\SafeExamBrowser.Client\SafeExamBrowser.Client.csproj"> |     <ProjectReference Include="..\SafeExamBrowser.Client\SafeExamBrowser.Client.csproj"> | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
|       </dependentAssembly> |       </dependentAssembly> | ||||||
|       <dependentAssembly> |       <dependentAssembly> | ||||||
|         <assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /> |         <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> |       </dependentAssembly> | ||||||
|     </assemblyBinding> |     </assemblyBinding> | ||||||
|   </runtime> |   </runtime> | ||||||
|  |  | ||||||
|  | @ -5,5 +5,5 @@ | ||||||
|   <package id="MSTest.TestAdapter" version="1.2.0" targetFramework="net461" /> |   <package id="MSTest.TestAdapter" version="1.2.0" targetFramework="net461" /> | ||||||
|   <package id="MSTest.TestFramework" 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.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> | </packages> | ||||||
|  | @ -229,7 +229,7 @@ namespace SafeExamBrowser.Communication.Proxies | ||||||
| 
 | 
 | ||||||
| 		private void FailIfNull(Message message) | 		private void FailIfNull(Message message) | ||||||
| 		{ | 		{ | ||||||
| 			if (message is null) | 			if (message == null) | ||||||
| 			{ | 			{ | ||||||
| 				throw new ArgumentNullException(nameof(message)); | 				throw new ArgumentNullException(nameof(message)); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -98,10 +98,11 @@ namespace SafeExamBrowser.Configuration.Compression | ||||||
| 
 | 
 | ||||||
| 		public byte[] Peek(Stream data, int count) | 		public byte[] Peek(Stream data, int count) | ||||||
| 		{ | 		{ | ||||||
|  | 			var stream = new GZipStream(data, CompressionMode.Decompress); | ||||||
|  | 
 | ||||||
| 			logger.Debug($"Peeking {count} bytes from '{data}'..."); | 			logger.Debug($"Peeking {count} bytes from '{data}'..."); | ||||||
| 			data.Seek(0, SeekOrigin.Begin); | 			data.Seek(0, SeekOrigin.Begin); | ||||||
| 
 | 
 | ||||||
| 			using (var stream = new GZipStream(data, CompressionMode.Decompress)) |  | ||||||
| 			using (var decompressed = new MemoryStream()) | 			using (var decompressed = new MemoryStream()) | ||||||
| 			{ | 			{ | ||||||
| 				var buffer = new byte[count]; | 				var buffer = new byte[count]; | ||||||
|  |  | ||||||
|  | @ -93,6 +93,8 @@ namespace SafeExamBrowser.Configuration | ||||||
| 		{ | 		{ | ||||||
| 			var settings = new Settings(); | 			var settings = new Settings(); | ||||||
| 
 | 
 | ||||||
|  | 			// TODO: Specify default settings | ||||||
|  | 
 | ||||||
| 			settings.KioskMode = KioskMode.None; | 			settings.KioskMode = KioskMode.None; | ||||||
| 			settings.ServicePolicy = ServicePolicy.Optional; | 			settings.ServicePolicy = ServicePolicy.Optional; | ||||||
| 
 | 
 | ||||||
|  | @ -122,7 +124,7 @@ namespace SafeExamBrowser.Configuration | ||||||
| 			resourceLoaders.Add(resourceLoader); | 			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); | 			settings = default(Settings); | ||||||
| 
 | 
 | ||||||
|  | @ -139,7 +141,7 @@ namespace SafeExamBrowser.Configuration | ||||||
| 						case LoadStatus.LoadWithBrowser: | 						case LoadStatus.LoadWithBrowser: | ||||||
| 							return HandleBrowserResource(resource, out settings); | 							return HandleBrowserResource(resource, out settings); | ||||||
| 						case LoadStatus.Success: | 						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; | 			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 status = LoadStatus.NotSupported; | ||||||
| 			var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data)); | 			var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data)); | ||||||
|  | @ -182,7 +184,7 @@ namespace SafeExamBrowser.Configuration | ||||||
| 
 | 
 | ||||||
| 			if (dataFormat != null) | 			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}."); | 				logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {status}."); | ||||||
| 			} | 			} | ||||||
| 			else | 			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 | namespace SafeExamBrowser.Configuration.DataFormats | ||||||
| { | { | ||||||
| 	public class BinaryFormat : IDataFormat | 	public partial class BinaryFormat : IDataFormat | ||||||
| 	{ | 	{ | ||||||
| 		private const int PREFIX_LENGTH = 4; | 		private const int PREFIX_LENGTH = 4; | ||||||
| 
 | 
 | ||||||
|  | @ -37,7 +37,7 @@ namespace SafeExamBrowser.Configuration.DataFormats | ||||||
| 				if (longEnough) | 				if (longEnough) | ||||||
| 				{ | 				{ | ||||||
| 					var prefix = ParsePrefix(data); | 					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."); | 					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; | 			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(); | 			var prefix = ParsePrefix(data); | ||||||
| 			settings.Browser.AllowAddressBar = true; | 			var success = TryDetermineFormat(prefix, out FormatType format); | ||||||
| 			settings.Browser.StartUrl = "www.duckduckgo.com"; |  | ||||||
| 			settings.Browser.AllowConfigurationDownloads = true; |  | ||||||
| 
 | 
 | ||||||
| 			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) | 		private string ParsePrefix(Stream data) | ||||||
|  | @ -81,39 +107,39 @@ namespace SafeExamBrowser.Configuration.DataFormats | ||||||
| 			return Encoding.UTF8.GetString(prefixData); | 			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) | 			switch (prefix) | ||||||
| 			{ | 			{ | ||||||
| 				case "pswd": | 				case "pswd": | ||||||
| 					format = DataFormat.Password; | 					format = FormatType.Password; | ||||||
| 					return true; | 					return true; | ||||||
| 				case "pwcc": | 				case "pwcc": | ||||||
| 					format = DataFormat.PasswordForConfigureClient; | 					format = FormatType.PasswordConfigureClient; | ||||||
| 					return true; | 					return true; | ||||||
| 				case "plnd": | 				case "plnd": | ||||||
| 					format = DataFormat.PlainData; | 					format = FormatType.PlainData; | ||||||
| 					return true; | 					return true; | ||||||
| 				case "pkhs": | 				case "pkhs": | ||||||
| 					format = DataFormat.PublicKeyHash; | 					format = FormatType.PublicKeyHash; | ||||||
| 					return true; | 					return true; | ||||||
| 				case "phsk": | 				case "phsk": | ||||||
| 					format = DataFormat.PublicKeyHashWithSymmetricKey; | 					format = FormatType.PublicKeyHashSymmetricKey; | ||||||
| 					return true; | 					return true; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			return false; | 			return false; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private enum DataFormat | 		private enum FormatType | ||||||
| 		{ | 		{ | ||||||
| 			Password = 1, | 			Password = 1, | ||||||
| 			PasswordForConfigureClient, | 			PasswordConfigureClient, | ||||||
| 			PlainData, | 			PlainData, | ||||||
| 			PublicKeyHash, | 			PublicKeyHash, | ||||||
| 			PublicKeyHashWithSymmetricKey | 			PublicKeyHashSymmetricKey | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ namespace SafeExamBrowser.Configuration.DataFormats | ||||||
| 			return false; | 			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(); | 			throw new System.NotImplementedException(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -49,18 +49,34 @@ | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <Reference Include="System" /> |     <Reference Include="System" /> | ||||||
|     <Reference Include="System.Net.Http" /> |     <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="System.Windows.Forms" /> | ||||||
|     <Reference Include="Microsoft.CSharp" /> |     <Reference Include="Microsoft.CSharp" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <Compile Include="Compression\GZipCompressor.cs" /> |     <Compile Include="Compression\GZipCompressor.cs" /> | ||||||
|     <Compile Include="DataFormats\BinaryFormat.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="DataFormats\XmlFormat.cs" /> | ||||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> |     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||||
|     <Compile Include="ConfigurationRepository.cs" /> |     <Compile Include="ConfigurationRepository.cs" /> | ||||||
|     <Compile Include="ResourceLoaders\FileResourceLoader.cs" /> |     <Compile Include="ResourceLoaders\FileResourceLoader.cs" /> | ||||||
|     <Compile Include="ResourceLoaders\NetworkResourceLoader.cs" /> |     <Compile Include="ResourceLoaders\NetworkResourceLoader.cs" /> | ||||||
|     <Compile Include="SessionConfiguration.cs" /> |     <Compile Include="SessionConfiguration.cs" /> | ||||||
|  |     <Compile Include="SubStream.cs" /> | ||||||
|     <Compile Include="SystemInfo.cs" /> |     <Compile Include="SystemInfo.cs" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  | @ -69,5 +85,8 @@ | ||||||
|       <Name>SafeExamBrowser.Contracts</Name> |       <Name>SafeExamBrowser.Contracts</Name> | ||||||
|     </ProjectReference> |     </ProjectReference> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <None Include="packages.config" /> | ||||||
|  |   </ItemGroup> | ||||||
|   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||||||
| </Project> | </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); | 		void Register(IResourceLoader resourceLoader); | ||||||
| 
 | 
 | ||||||
| 		/// <summary> | 		/// <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! | 		/// <see cref="LoadStatus.Success"/>, the referenced settings may be <c>null</c> or in an undefinable state! | ||||||
| 		/// </summary> | 		/// </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); | 		bool CanParse(Stream data); | ||||||
| 
 | 
 | ||||||
| 		/// <summary> | 		/// <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! | 		/// the referenced settings may be <c>null</c> or in an undefinable state! | ||||||
| 		/// </summary> | 		/// </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> | 	/// </summary> | ||||||
| 	public enum LoadStatus | 	public enum LoadStatus | ||||||
| 	{ | 	{ | ||||||
| 		/// <summary> |  | ||||||
| 		/// Indicates that an admin password is needed in order to load the settings. |  | ||||||
| 		/// </summary> |  | ||||||
| 		AdminPasswordNeeded = 1, |  | ||||||
| 
 |  | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// Indicates that a resource does not comply with the declared data format. | 		/// Indicates that a resource does not comply with the declared data format. | ||||||
| 		/// </summary> | 		/// </summary> | ||||||
| 		InvalidData, | 		InvalidData = 1, | ||||||
| 
 | 
 | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// Indicates that a resource needs to be loaded with the browser. | 		/// Indicates that a resource needs to be loaded with the browser. | ||||||
|  | @ -34,9 +29,9 @@ namespace SafeExamBrowser.Contracts.Configuration | ||||||
| 		NotSupported, | 		NotSupported, | ||||||
| 
 | 
 | ||||||
| 		/// <summary> | 		/// <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> | 		/// </summary> | ||||||
| 		SettingsPasswordNeeded, | 		PasswordNeeded, | ||||||
| 
 | 
 | ||||||
| 		/// <summary> | 		/// <summary> | ||||||
| 		/// The settings were loaded successfully. | 		/// The settings were loaded successfully. | ||||||
|  |  | ||||||
|  | @ -60,14 +60,14 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 			appConfig.ProgramDataFolder = location; | 			appConfig.ProgramDataFolder = location; | ||||||
| 			appConfig.AppDataFolder = 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 = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.Perform(); | 			sut.Perform(); | ||||||
| 
 | 
 | ||||||
| 			var resource = new Uri(url); | 			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] | 		[TestMethod] | ||||||
|  | @ -78,14 +78,14 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 			appConfig.ProgramDataFolder = location; | 			appConfig.ProgramDataFolder = location; | ||||||
| 			appConfig.AppDataFolder = $@"{location}\WRONG"; | 			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 = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.Perform(); | 			sut.Perform(); | ||||||
| 
 | 
 | ||||||
| 			var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); | 			var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); | ||||||
| 
 | 
 | ||||||
| 			repository.Verify(r => r.TryLoadSettings(It.Is<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] | 		[TestMethod] | ||||||
|  | @ -95,14 +95,14 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 
 | 
 | ||||||
| 			appConfig.AppDataFolder = 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(null, repository.Object, logger.Object, sessionContext); | 			sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.Perform(); | 			sut.Perform(); | ||||||
| 
 | 
 | ||||||
| 			var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); | 			var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); | ||||||
| 
 | 
 | ||||||
| 			repository.Verify(r => r.TryLoadSettings(It.Is<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] | 		[TestMethod] | ||||||
|  | @ -127,7 +127,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 		public void MustAbortIfWishedByUser() | 		public void MustAbortIfWishedByUser() | ||||||
| 		{ | 		{ | ||||||
| 			appConfig.ProgramDataFolder = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location), nameof(Operations)); | 			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 = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -146,7 +146,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 		[TestMethod] | 		[TestMethod] | ||||||
| 		public void MustNotAbortIfNotWishedByUser() | 		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 = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -166,7 +166,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 		public void MustNotAllowToAbortIfNotInConfigureClientMode() | 		public void MustNotAllowToAbortIfNotInConfigureClientMode() | ||||||
| 		{ | 		{ | ||||||
| 			settings.ConfigurationMode = ConfigurationMode.Exam; | 			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 = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -218,7 +218,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 		{ | 		{ | ||||||
| 			var url = @"http://www.safeexambrowser.org/whatever.seb"; | 			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 = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -231,7 +231,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 
 | 
 | ||||||
| 			sut.Perform(); | 			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] | 		[TestMethod] | ||||||
|  | @ -239,7 +239,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 		{ | 		{ | ||||||
| 			var url = @"http://www.safeexambrowser.org/whatever.seb"; | 			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 = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -252,7 +252,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 
 | 
 | ||||||
| 			sut.Perform(); | 			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] | 		[TestMethod] | ||||||
|  | @ -261,8 +261,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 			var password = "test"; | 			var password = "test"; | ||||||
| 			var url = @"http://www.safeexambrowser.org/whatever.seb"; | 			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); | ||||||
| 			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, password)).Returns(LoadStatus.Success); | ||||||
| 
 | 
 | ||||||
| 			sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | 			sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -276,8 +276,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 
 | 
 | ||||||
| 			sut.Perform(); | 			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), 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, password), Times.Once); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		[TestMethod] | 		[TestMethod] | ||||||
|  | @ -286,8 +286,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 			var password = "test"; | 			var password = "test"; | ||||||
| 			var url = @"http://www.safeexambrowser.org/whatever.seb"; | 			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); | ||||||
| 			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, password)).Returns(LoadStatus.Success); | ||||||
| 
 | 
 | ||||||
| 			sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | 			sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -301,8 +301,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 
 | 
 | ||||||
| 			sut.Perform(); | 			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), 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, password), Times.Once); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		[TestMethod] | 		[TestMethod] | ||||||
|  | @ -310,7 +310,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 		{ | 		{ | ||||||
| 			var url = @"http://www.safeexambrowser.org/whatever.seb"; | 			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 = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -331,7 +331,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 		{ | 		{ | ||||||
| 			var url = @"http://www.safeexambrowser.org/whatever.seb"; | 			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 = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -354,9 +354,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 			var settingsPassword = "abc"; | 			var settingsPassword = "abc"; | ||||||
| 			var url = @"http://www.safeexambrowser.org/whatever.seb"; | 			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); | ||||||
| 			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, settingsPassword)).Returns(LoadStatus.Success); | ||||||
| 			repository.Setup(r => r.TryLoadSettings(It.IsAny<Uri>(), out settings, adminPassword, settingsPassword)).Returns(LoadStatus.Success); |  | ||||||
| 
 | 
 | ||||||
| 			sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | 			sut = new ConfigurationOperation(new[] { "blubb.exe", url }, repository.Object, logger.Object, sessionContext); | ||||||
| 			sut.ActionRequired += args => | 			sut.ActionRequired += args => | ||||||
|  | @ -370,9 +369,9 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 
 | 
 | ||||||
| 			sut.Perform(); | 			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), 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, 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, settingsPassword), Times.Once); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		[TestMethod] | 		[TestMethod] | ||||||
|  | @ -382,13 +381,13 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 			var resource = new Uri(Path.Combine(location, nameof(Operations), "SettingsDummy.txt")); | 			var resource = new Uri(Path.Combine(location, nameof(Operations), "SettingsDummy.txt")); | ||||||
| 
 | 
 | ||||||
| 			sessionContext.ReconfigurationFilePath = resource.LocalPath; | 			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); | 			sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); | ||||||
| 
 | 
 | ||||||
| 			var result = sut.Repeat(); | 			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); | 			Assert.AreEqual(OperationResult.Success, result); | ||||||
| 		} | 		} | ||||||
|  | @ -399,19 +398,19 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations | ||||||
| 			var resource = new Uri("file:///C:/does/not/exist.txt"); | 			var resource = new Uri("file:///C:/does/not/exist.txt"); | ||||||
| 
 | 
 | ||||||
| 			sessionContext.ReconfigurationFilePath = null; | 			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); | 			sut = new ConfigurationOperation(null, repository.Object, logger.Object, sessionContext); | ||||||
| 
 | 
 | ||||||
| 			var result = sut.Repeat(); | 			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); | 			Assert.AreEqual(OperationResult.Failed, result); | ||||||
| 
 | 
 | ||||||
| 			sessionContext.ReconfigurationFilePath = resource.LocalPath; | 			sessionContext.ReconfigurationFilePath = resource.LocalPath; | ||||||
| 			result = sut.Repeat(); | 			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); | 			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"> |     <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> |       <HintPath>..\packages\System.Threading.Tasks.Extensions.4.4.0\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath> | ||||||
|     </Reference> |     </Reference> | ||||||
|     <Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> |     <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL"> | ||||||
|       <HintPath>..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll</HintPath> |       <HintPath>..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath> | ||||||
|       <Private>True</Private> |  | ||||||
|     </Reference> |     </Reference> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ | ||||||
|       </dependentAssembly> |       </dependentAssembly> | ||||||
|       <dependentAssembly> |       <dependentAssembly> | ||||||
|         <assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /> |         <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> |       </dependentAssembly> | ||||||
|     </assemblyBinding> |     </assemblyBinding> | ||||||
|   </runtime> |   </runtime> | ||||||
|  |  | ||||||
|  | @ -5,5 +5,5 @@ | ||||||
|   <package id="MSTest.TestAdapter" version="1.2.0" targetFramework="net461" /> |   <package id="MSTest.TestAdapter" version="1.2.0" targetFramework="net461" /> | ||||||
|   <package id="MSTest.TestFramework" 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.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> | </packages> | ||||||
|  | @ -8,6 +8,7 @@ | ||||||
| 
 | 
 | ||||||
| using System; | using System; | ||||||
| using System.Threading; | using System.Threading; | ||||||
|  | using System.Threading.Tasks; | ||||||
| using System.Windows; | using System.Windows; | ||||||
| 
 | 
 | ||||||
| namespace SafeExamBrowser.Runtime | namespace SafeExamBrowser.Runtime | ||||||
|  | @ -60,6 +61,11 @@ namespace SafeExamBrowser.Runtime | ||||||
| 			instances.BuildObjectGraph(Shutdown); | 			instances.BuildObjectGraph(Shutdown); | ||||||
| 			instances.LogStartupInformation(); | 			instances.LogStartupInformation(); | ||||||
| 
 | 
 | ||||||
|  | 			Task.Run(new Action(TryStart)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		private void TryStart() | ||||||
|  | 		{ | ||||||
| 			var success = instances.RuntimeController.TryStart(); | 			var success = instances.RuntimeController.TryStart(); | ||||||
| 
 | 
 | ||||||
| 			if (!success) | 			if (!success) | ||||||
|  | @ -70,18 +76,15 @@ namespace SafeExamBrowser.Runtime | ||||||
| 
 | 
 | ||||||
| 		public new void Shutdown() | 		public new void Shutdown() | ||||||
| 		{ | 		{ | ||||||
| 			void shutdown() | 			Task.Run(new Action(ShutdownInternal)); | ||||||
| 			{ | 		} | ||||||
| 				instances.RuntimeController.Terminate(); |  | ||||||
| 				instances.LogShutdownInformation(); |  | ||||||
| 
 | 
 | ||||||
| 				// TODO: Which UI operation is being cancelled without the timeout? Is this only a debugger issue? Same problem with client? -> Debug! | 		private void ShutdownInternal() | ||||||
| 				Thread.Sleep(20); | 		{ | ||||||
|  | 			instances.RuntimeController.Terminate(); | ||||||
|  | 			instances.LogShutdownInformation(); | ||||||
| 
 | 
 | ||||||
| 				base.Shutdown(); | 			Dispatcher.Invoke(base.Shutdown); | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			Dispatcher.BeginInvoke(new Action(shutdown)); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -159,34 +159,37 @@ namespace SafeExamBrowser.Runtime.Operations | ||||||
| 			var terminatedEvent = new AutoResetEvent(false); | 			var terminatedEvent = new AutoResetEvent(false); | ||||||
| 			var terminatedEventHandler = new ProcessTerminatedEventHandler((_) => terminatedEvent.Set()); | 			var terminatedEventHandler = new ProcessTerminatedEventHandler((_) => terminatedEvent.Set()); | ||||||
| 
 | 
 | ||||||
| 			runtimeHost.ClientDisconnected += disconnectedEventHandler; | 			if (ClientProxy != null) | ||||||
| 			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!"); | 				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) | 			if (disconnected && terminated) | ||||||
| 			{ | 			{ | ||||||
| 				logger.Info("Client has been successfully terminated."); | 				logger.Info("Client has been successfully terminated."); | ||||||
|  |  | ||||||
|  | @ -99,33 +99,18 @@ namespace SafeExamBrowser.Runtime.Operations | ||||||
| 
 | 
 | ||||||
| 		private OperationResult LoadSettings(Uri uri) | 		private OperationResult LoadSettings(Uri uri) | ||||||
| 		{ | 		{ | ||||||
| 			var adminPassword = default(string); | 			var status = configuration.TryLoadSettings(uri, out Settings settings); | ||||||
| 			var settingsPassword = default(string); |  | ||||||
| 			var settings = default(Settings); |  | ||||||
| 			var status = default(LoadStatus); |  | ||||||
| 
 | 
 | ||||||
| 			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); | 					return OperationResult.Aborted; | ||||||
| 
 |  | ||||||
| 					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; |  | ||||||
| 				} | 				} | ||||||
|  | 
 | ||||||
|  | 				status = configuration.TryLoadSettings(uri, out settings, result.Password); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if (status == LoadStatus.Success) | 			if (status == LoadStatus.Success) | ||||||
|  | @ -158,7 +143,7 @@ namespace SafeExamBrowser.Runtime.Operations | ||||||
| 
 | 
 | ||||||
| 		private PasswordRequiredEventArgs TryGetPassword(LoadStatus status) | 		private PasswordRequiredEventArgs TryGetPassword(LoadStatus status) | ||||||
| 		{ | 		{ | ||||||
| 			var purpose = status == LoadStatus.AdminPasswordNeeded ? PasswordRequestPurpose.Administrator : PasswordRequestPurpose.Settings; | 			var purpose = PasswordRequestPurpose.Settings; | ||||||
| 			var args = new PasswordRequiredEventArgs { Purpose = purpose }; | 			var args = new PasswordRequiredEventArgs { Purpose = purpose }; | ||||||
| 
 | 
 | ||||||
| 			ActionRequired?.Invoke(args); | 			ActionRequired?.Invoke(args); | ||||||
|  |  | ||||||
|  | @ -13,10 +13,10 @@ | ||||||
|             </ResourceDictionary.MergedDictionaries> |             </ResourceDictionary.MergedDictionaries> | ||||||
|         </ResourceDictionary> |         </ResourceDictionary> | ||||||
|     </Window.Resources> |     </Window.Resources> | ||||||
|     <Grid> |     <Grid FocusManager.FocusedElement="{Binding ElementName=Password}"> | ||||||
|         <Grid.RowDefinitions> |         <Grid.RowDefinitions> | ||||||
|             <RowDefinition Height="4*" /> |  | ||||||
|             <RowDefinition Height="2*" /> |             <RowDefinition Height="2*" /> | ||||||
|  |             <RowDefinition Height="1*" /> | ||||||
|         </Grid.RowDefinitions> |         </Grid.RowDefinitions> | ||||||
|         <Grid Grid.Row="0"> |         <Grid Grid.Row="0"> | ||||||
|             <Grid> |             <Grid> | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| using System.Windows; | using System.Windows; | ||||||
|  | using System.Windows.Input; | ||||||
| using SafeExamBrowser.Contracts.I18n; | using SafeExamBrowser.Contracts.I18n; | ||||||
| using SafeExamBrowser.Contracts.UserInterface.Taskbar.Events; | using SafeExamBrowser.Contracts.UserInterface.Taskbar.Events; | ||||||
| using SafeExamBrowser.Contracts.UserInterface.Windows; | using SafeExamBrowser.Contracts.UserInterface.Windows; | ||||||
|  | @ -72,6 +73,7 @@ namespace SafeExamBrowser.UserInterface.Classic | ||||||
| 			ConfirmButton.Click += ConfirmButton_Click; | 			ConfirmButton.Click += ConfirmButton_Click; | ||||||
| 
 | 
 | ||||||
| 			Closing += (o, args) => closing?.Invoke(); | 			Closing += (o, args) => closing?.Invoke(); | ||||||
|  | 			Password.KeyUp += Password_KeyUp; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private void CancelButton_Click(object sender, RoutedEventArgs e) | 		private void CancelButton_Click(object sender, RoutedEventArgs e) | ||||||
|  | @ -86,6 +88,15 @@ namespace SafeExamBrowser.UserInterface.Classic | ||||||
| 			Close(); | 			Close(); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		private void Password_KeyUp(object sender, KeyEventArgs e) | ||||||
|  | 		{ | ||||||
|  | 			if (e.Key == Key.Enter) | ||||||
|  | 			{ | ||||||
|  | 				DialogResult = true; | ||||||
|  | 				Close(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		private class PasswordDialogResult : IPasswordDialogResult | 		private class PasswordDialogResult : IPasswordDialogResult | ||||||
| 		{ | 		{ | ||||||
| 			public string Password { get; set; } | 			public string Password { get; set; } | ||||||
|  |  | ||||||
|  | @ -141,6 +141,10 @@ namespace SafeExamBrowser.UserInterface.Classic | ||||||
| 			StatusTextBlock.DataContext = model; | 			StatusTextBlock.DataContext = model; | ||||||
| 
 | 
 | ||||||
| 			Closing += (o, args) => args.Cancel = !allowClose; | 			Closing += (o, args) => args.Cancel = !allowClose; | ||||||
|  | 
 | ||||||
|  | #if DEBUG | ||||||
|  | 			Topmost = false; | ||||||
|  | #endif | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		private void RuntimeWindow_Loaded(object sender, RoutedEventArgs e) | 		private void RuntimeWindow_Loaded(object sender, RoutedEventArgs e) | ||||||
|  |  | ||||||
|  | @ -90,26 +90,7 @@ namespace SafeExamBrowser.UserInterface.Classic | ||||||
| 
 | 
 | ||||||
| 		public IRuntimeWindow CreateRuntimeWindow(AppConfig appConfig) | 		public IRuntimeWindow CreateRuntimeWindow(AppConfig appConfig) | ||||||
| 		{ | 		{ | ||||||
| 			RuntimeWindow runtimeWindow = null; | 			return Application.Current.Dispatcher.Invoke(() => new RuntimeWindow(appConfig, text)); | ||||||
| 			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; |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public ISplashScreen CreateSplashScreen(AppConfig appConfig = null) | 		public ISplashScreen CreateSplashScreen(AppConfig appConfig = null) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 dbuechel
						dbuechel