From eb47cb362b7f43e8c59c98222049546c6d10e712 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Wed, 27 Jun 2018 14:02:16 +0200 Subject: [PATCH] SEBWIN-220: Finished draft of (re-)configuration mechanism. --- .../Data/AuthenticationResponse.cs | 2 +- .../Data/ConfigurationResponse.cs | 2 +- .../Data/DisconnectionResponse.cs | 2 +- .../Data/PasswordReplyMessage.cs | 41 +++++ .../Data/PasswordRequestMessage.cs | 35 ++++ .../Data/PasswordRequestPurpose.cs | 28 +++ .../Data/ReconfigurationResponse.cs | 2 +- .../Communication/Data/Response.cs | 2 +- .../Communication/Events/PasswordEventArgs.cs | 33 ++++ .../Communication/Hosts/IRuntimeHost.cs | 5 + .../Communication/Proxies/IClientProxy.cs | 7 + .../Configuration/IConfigurationRepository.cs | 2 +- SafeExamBrowser.Contracts/I18n/TextKey.cs | 4 + .../SafeExamBrowser.Contracts.csproj | 6 + .../UserInterface/IUserInterfaceFactory.cs | 9 +- .../UserInterface/Windows/IPasswordDialog.cs | 21 +++ .../Windows/IPasswordDialogResult.cs | 26 +++ .../{BaseHostImpl.cs => BaseHostStub.cs} | 4 +- .../Communication/Hosts/BaseHostTests.cs | 4 +- .../SafeExamBrowser.Core.UnitTests.csproj | 2 +- .../Communication/Proxies/ClientProxy.cs | 7 + SafeExamBrowser.Core/I18n/Text.xml | 12 ++ .../Operations/ConfigurationOperationTests.cs | 160 ++++++++++++---- .../Operations/PasswordDialogResultStub.cs | 18 ++ .../SafeExamBrowser.Runtime.UnitTests.csproj | 1 + .../Operations/ConfigurationOperation.cs | 174 ++++++++++++------ .../Communication/RuntimeHost.cs | 4 + SafeExamBrowser.Runtime/CompositionRoot.cs | 2 +- .../UserInterfaceFactory.cs | 9 +- .../UserInterfaceFactory.cs | 6 + 30 files changed, 529 insertions(+), 101 deletions(-) create mode 100644 SafeExamBrowser.Contracts/Communication/Data/PasswordReplyMessage.cs create mode 100644 SafeExamBrowser.Contracts/Communication/Data/PasswordRequestMessage.cs create mode 100644 SafeExamBrowser.Contracts/Communication/Data/PasswordRequestPurpose.cs create mode 100644 SafeExamBrowser.Contracts/Communication/Events/PasswordEventArgs.cs create mode 100644 SafeExamBrowser.Contracts/UserInterface/Windows/IPasswordDialog.cs create mode 100644 SafeExamBrowser.Contracts/UserInterface/Windows/IPasswordDialogResult.cs rename SafeExamBrowser.Core.UnitTests/Communication/Hosts/{BaseHostImpl.cs => BaseHostStub.cs} (93%) create mode 100644 SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/PasswordDialogResultStub.cs diff --git a/SafeExamBrowser.Contracts/Communication/Data/AuthenticationResponse.cs b/SafeExamBrowser.Contracts/Communication/Data/AuthenticationResponse.cs index 9c2c0d4f..81434a44 100644 --- a/SafeExamBrowser.Contracts/Communication/Data/AuthenticationResponse.cs +++ b/SafeExamBrowser.Contracts/Communication/Data/AuthenticationResponse.cs @@ -11,7 +11,7 @@ using System; namespace SafeExamBrowser.Contracts.Communication.Data { /// - /// The response to be used to reply to an authentication request (see ). + /// The response to be used to reply to an authentication request (see ). /// [Serializable] public class AuthenticationResponse : Response diff --git a/SafeExamBrowser.Contracts/Communication/Data/ConfigurationResponse.cs b/SafeExamBrowser.Contracts/Communication/Data/ConfigurationResponse.cs index e9b4a29d..5b68fd3d 100644 --- a/SafeExamBrowser.Contracts/Communication/Data/ConfigurationResponse.cs +++ b/SafeExamBrowser.Contracts/Communication/Data/ConfigurationResponse.cs @@ -12,7 +12,7 @@ using SafeExamBrowser.Contracts.Configuration; namespace SafeExamBrowser.Contracts.Communication.Data { /// - /// The response to be used to reply to a configuration request (see ). + /// The response to be used to reply to a configuration request (see ). /// [Serializable] public class ConfigurationResponse : Response diff --git a/SafeExamBrowser.Contracts/Communication/Data/DisconnectionResponse.cs b/SafeExamBrowser.Contracts/Communication/Data/DisconnectionResponse.cs index fcca0e79..ea218f71 100644 --- a/SafeExamBrowser.Contracts/Communication/Data/DisconnectionResponse.cs +++ b/SafeExamBrowser.Contracts/Communication/Data/DisconnectionResponse.cs @@ -11,7 +11,7 @@ using System; namespace SafeExamBrowser.Contracts.Communication.Data { /// - /// The response transmitted to a + /// The response transmitted to a /// [Serializable] public class DisconnectionResponse : Response diff --git a/SafeExamBrowser.Contracts/Communication/Data/PasswordReplyMessage.cs b/SafeExamBrowser.Contracts/Communication/Data/PasswordReplyMessage.cs new file mode 100644 index 00000000..99aaf52c --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Data/PasswordReplyMessage.cs @@ -0,0 +1,41 @@ +/* + * 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; + +namespace SafeExamBrowser.Contracts.Communication.Data +{ + /// + /// The reply to a . + /// + [Serializable] + public class PasswordReplyMessage : Message + { + /// + /// The password entered by the user, or null if the user interaction was unsuccessful. + /// + public string Password { get; private set; } + + /// + /// The unique identifier for the password request. + /// + public Guid RequestId { get; private set; } + + /// + /// Determines whether the user interaction was successful or not. + /// + public bool Success { get; private set; } + + public PasswordReplyMessage(string password, Guid requestId, bool success) + { + Password = password; + RequestId = requestId; + Success = success; + } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Data/PasswordRequestMessage.cs b/SafeExamBrowser.Contracts/Communication/Data/PasswordRequestMessage.cs new file mode 100644 index 00000000..8d04707b --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Data/PasswordRequestMessage.cs @@ -0,0 +1,35 @@ +/* + * 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; + +namespace SafeExamBrowser.Contracts.Communication.Data +{ + /// + /// This message is transmitted to the client to request a password input by the user. + /// + [Serializable] + public class PasswordRequestMessage : Message + { + /// + /// The purpose of the password request. + /// + public PasswordRequestPurpose Purpose { get; private set; } + + /// + /// The unique identifier for the password request. + /// + public Guid RequestId { get; private set; } + + public PasswordRequestMessage(PasswordRequestPurpose purpose, Guid requestId) + { + Purpose = purpose; + RequestId = requestId; + } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Data/PasswordRequestPurpose.cs b/SafeExamBrowser.Contracts/Communication/Data/PasswordRequestPurpose.cs new file mode 100644 index 00000000..9cd49f82 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Data/PasswordRequestPurpose.cs @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +namespace SafeExamBrowser.Contracts.Communication.Data +{ + /// + /// Defines all possible reasons for a . + /// + public enum PasswordRequestPurpose + { + Undefined = 0, + + /// + /// The password is to be used as administrator password for an application configuration. + /// + Administrator, + + /// + /// The password is to be used as settings password for an application configuration. + /// + Settings + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Data/ReconfigurationResponse.cs b/SafeExamBrowser.Contracts/Communication/Data/ReconfigurationResponse.cs index 7fa5f22a..65671f70 100644 --- a/SafeExamBrowser.Contracts/Communication/Data/ReconfigurationResponse.cs +++ b/SafeExamBrowser.Contracts/Communication/Data/ReconfigurationResponse.cs @@ -11,7 +11,7 @@ using System; namespace SafeExamBrowser.Contracts.Communication.Data { /// - /// The response to a . + /// The response to a . /// [Serializable] public class ReconfigurationResponse : Response diff --git a/SafeExamBrowser.Contracts/Communication/Data/Response.cs b/SafeExamBrowser.Contracts/Communication/Data/Response.cs index e338b4dc..20459fc8 100644 --- a/SafeExamBrowser.Contracts/Communication/Data/Response.cs +++ b/SafeExamBrowser.Contracts/Communication/Data/Response.cs @@ -11,7 +11,7 @@ using System; namespace SafeExamBrowser.Contracts.Communication.Data { /// - /// The base class for respones, from which a response must inherit in order to be sent to an interlocutor as reply to . + /// The base class for respones, from which a response must inherit in order to be sent to an interlocutor as reply to . /// [Serializable] public abstract class Response diff --git a/SafeExamBrowser.Contracts/Communication/Events/PasswordEventArgs.cs b/SafeExamBrowser.Contracts/Communication/Events/PasswordEventArgs.cs new file mode 100644 index 00000000..742c0520 --- /dev/null +++ b/SafeExamBrowser.Contracts/Communication/Events/PasswordEventArgs.cs @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using System; + +namespace SafeExamBrowser.Contracts.Communication.Events +{ + /// + /// The event arguments used for the password input event fired by the . + /// + public class PasswordEventArgs : CommunicationEventArgs + { + /// + /// The password entered by the user, or null if not available. + /// + public string Password { get; set; } + + /// + /// Identifies the password request. + /// + public Guid RequestId { get; set; } + + /// + /// Indicates whether the password has been successfully entered by the user. + /// + public bool Success { get; set; } + } +} diff --git a/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs b/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs index 070a6d1b..a2839f48 100644 --- a/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs +++ b/SafeExamBrowser.Contracts/Communication/Hosts/IRuntimeHost.cs @@ -31,6 +31,11 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts /// event CommunicationEventHandler ClientReady; + /// + /// Event fired when the client transmitted a password entered by the user. + /// + event CommunicationEventHandler PasswordReceived; + /// /// Event fired when the client requested a reconfiguration of the application. /// diff --git a/SafeExamBrowser.Contracts/Communication/Proxies/IClientProxy.cs b/SafeExamBrowser.Contracts/Communication/Proxies/IClientProxy.cs index 40324cfe..d62abb93 100644 --- a/SafeExamBrowser.Contracts/Communication/Proxies/IClientProxy.cs +++ b/SafeExamBrowser.Contracts/Communication/Proxies/IClientProxy.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using SafeExamBrowser.Contracts.Communication.Data; namespace SafeExamBrowser.Contracts.Communication.Proxies @@ -26,5 +27,11 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies /// /// If the communication failed. AuthenticationResponse RequestAuthentication(); + + /// + /// Requests the client to render a password dialog and subsequently return the interaction result as separate message. + /// + /// If the communication failed. + void RequestPassword(PasswordRequestPurpose purpose, Guid requestId); } } diff --git a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs index 4fd13f9c..0e8f7f94 100644 --- a/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs +++ b/SafeExamBrowser.Contracts/Configuration/IConfigurationRepository.cs @@ -51,7 +51,7 @@ namespace SafeExamBrowser.Contracts.Configuration /// Attempts to load settings from the specified resource, using the optional passwords. Returns a /// indicating the result of the operation. /// - LoadStatus LoadSettings(Uri resource, string settingsPassword = null, string adminPassword = null); + LoadStatus LoadSettings(Uri resource, string adminPassword = null, string settingsPassword = null); /// /// Loads the default settings. diff --git a/SafeExamBrowser.Contracts/I18n/TextKey.cs b/SafeExamBrowser.Contracts/I18n/TextKey.cs index 95d25833..1685a828 100644 --- a/SafeExamBrowser.Contracts/I18n/TextKey.cs +++ b/SafeExamBrowser.Contracts/I18n/TextKey.cs @@ -44,6 +44,10 @@ namespace SafeExamBrowser.Contracts.I18n MessageBox_StartupErrorTitle, Notification_AboutTooltip, Notification_LogTooltip, + PasswordDialog_AdminPasswordRequired, + PasswordDialog_AdminPasswordRequiredTitle, + PasswordDialog_SettingsPasswordRequired, + PasswordDialog_SettingsPasswordRequiredTitle, ProgressIndicator_CloseRuntimeConnection, ProgressIndicator_EmptyClipboard, ProgressIndicator_FinalizeServiceSession, diff --git a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj index a5465de4..ca417339 100644 --- a/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj +++ b/SafeExamBrowser.Contracts/SafeExamBrowser.Contracts.csproj @@ -60,8 +60,12 @@ + + + + @@ -139,6 +143,8 @@ + + diff --git a/SafeExamBrowser.Contracts/UserInterface/IUserInterfaceFactory.cs b/SafeExamBrowser.Contracts/UserInterface/IUserInterfaceFactory.cs index 16a6fe7c..1555e823 100644 --- a/SafeExamBrowser.Contracts/UserInterface/IUserInterfaceFactory.cs +++ b/SafeExamBrowser.Contracts/UserInterface/IUserInterfaceFactory.cs @@ -37,6 +37,11 @@ namespace SafeExamBrowser.Contracts.UserInterface /// IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings); + /// + /// Creates a system control which allows to change the keyboard layout of the computer. + /// + ISystemKeyboardLayoutControl CreateKeyboardLayoutControl(); + /// /// Creates a new log window which runs on its own thread. /// @@ -48,9 +53,9 @@ namespace SafeExamBrowser.Contracts.UserInterface INotificationButton CreateNotification(INotificationInfo info); /// - /// Creates a system control which allows to change the keyboard layout of the computer. + /// Creates a password dialog with the given message and title. /// - ISystemKeyboardLayoutControl CreateKeyboardLayoutControl(); + IPasswordDialog CreatePasswordDialog(string message, string title); /// /// Creates a system control displaying the power supply status of the computer. diff --git a/SafeExamBrowser.Contracts/UserInterface/Windows/IPasswordDialog.cs b/SafeExamBrowser.Contracts/UserInterface/Windows/IPasswordDialog.cs new file mode 100644 index 00000000..baf02339 --- /dev/null +++ b/SafeExamBrowser.Contracts/UserInterface/Windows/IPasswordDialog.cs @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +namespace SafeExamBrowser.Contracts.UserInterface.Windows +{ + /// + /// Defines the functionality of a password dialog. + /// + public interface IPasswordDialog : IWindow + { + /// + /// Shows the dialog as topmost window. If a parent window is specified, the dialog is rendered modally for the given parent. + /// + IPasswordDialogResult Show(IWindow parent = null); + } +} diff --git a/SafeExamBrowser.Contracts/UserInterface/Windows/IPasswordDialogResult.cs b/SafeExamBrowser.Contracts/UserInterface/Windows/IPasswordDialogResult.cs new file mode 100644 index 00000000..83757917 --- /dev/null +++ b/SafeExamBrowser.Contracts/UserInterface/Windows/IPasswordDialogResult.cs @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +namespace SafeExamBrowser.Contracts.UserInterface.Windows +{ + /// + /// Defines the user interaction result of an . + /// + public interface IPasswordDialogResult + { + /// + /// The password entered by the user, or null if the interaction was unsuccessful. + /// + string Password { get; } + + /// + /// Indicates whether the user confirmed the dialog or not. + /// + bool Success { get; } + } +} diff --git a/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostImpl.cs b/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostStub.cs similarity index 93% rename from SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostImpl.cs rename to SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostStub.cs index b0fb5a11..f0c1165b 100644 --- a/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostImpl.cs +++ b/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostStub.cs @@ -14,14 +14,14 @@ using SafeExamBrowser.Core.Communication.Hosts; namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts { - internal class BaseHostImpl : BaseHost + internal class BaseHostStub : BaseHost { public Func OnConnectStub { get; set; } public Action OnDisconnectStub { get; set; } public Func OnReceiveStub { get; set; } public Func OnReceiveSimpleMessageStub { get; set; } - public BaseHostImpl(string address, IHostObjectFactory factory, ILogger logger) : base(address, factory, logger) + public BaseHostStub(string address, IHostObjectFactory factory, ILogger logger) : base(address, factory, logger) { } diff --git a/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostTests.cs b/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostTests.cs index 50198e23..68d65007 100644 --- a/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostTests.cs +++ b/SafeExamBrowser.Core.UnitTests/Communication/Hosts/BaseHostTests.cs @@ -24,7 +24,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts private Mock hostObject; private Mock hostObjectFactory; private Mock logger; - private BaseHostImpl sut; + private BaseHostStub sut; [TestInitialize] public void Initialize() @@ -35,7 +35,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts hostObjectFactory.Setup(f => f.CreateObject(It.IsAny(), It.IsAny())).Returns(hostObject.Object); - sut = new BaseHostImpl("net.pipe://some/address/here", hostObjectFactory.Object, logger.Object); + sut = new BaseHostStub("net.pipe://some/address/here", hostObjectFactory.Object, logger.Object); } [TestMethod] diff --git a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj index 382a44ab..bab8b185 100644 --- a/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj +++ b/SafeExamBrowser.Core.UnitTests/SafeExamBrowser.Core.UnitTests.csproj @@ -83,7 +83,7 @@ - + diff --git a/SafeExamBrowser.Core/Communication/Proxies/ClientProxy.cs b/SafeExamBrowser.Core/Communication/Proxies/ClientProxy.cs index 71f243d2..8d4fe460 100644 --- a/SafeExamBrowser.Core/Communication/Proxies/ClientProxy.cs +++ b/SafeExamBrowser.Core/Communication/Proxies/ClientProxy.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using System.ServiceModel; using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Proxies; @@ -43,5 +44,11 @@ namespace SafeExamBrowser.Core.Communication.Proxies throw new CommunicationException($"Did not receive authentication response! Received: {ToString(response)}."); } + + public void RequestPassword(PasswordRequestPurpose purpose, Guid requestId) + { + // TODO + throw new NotImplementedException(); + } } } diff --git a/SafeExamBrowser.Core/I18n/Text.xml b/SafeExamBrowser.Core/I18n/Text.xml index 2e71af35..5374a81e 100644 --- a/SafeExamBrowser.Core/I18n/Text.xml +++ b/SafeExamBrowser.Core/I18n/Text.xml @@ -84,6 +84,18 @@ Application Log + + Please enter the administrator password for the application configuration: + + + Administrator Password Required + + + Please enter the settings password for the application configuration: + + + Settings Password Required + Closing runtime connection diff --git a/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ConfigurationOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ConfigurationOperationTests.cs index 6c3cdbe3..6083e357 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ConfigurationOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/ConfigurationOperationTests.cs @@ -11,11 +11,14 @@ using System.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using SafeExamBrowser.Contracts.Behaviour.OperationModel; +using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.Logging; +using SafeExamBrowser.Contracts.UserInterface; using SafeExamBrowser.Contracts.UserInterface.MessageBox; +using SafeExamBrowser.Contracts.UserInterface.Windows; using SafeExamBrowser.Runtime.Behaviour.Operations; namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations @@ -23,12 +26,15 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations [TestClass] public class ConfigurationOperationTests { + private RuntimeInfo info; private Mock logger; private Mock messageBox; - private RuntimeInfo info; + private Mock passwordDialog; private Mock repository; + private Mock runtimeHost; private Settings settings; private Mock text; + private Mock uiFactory; private ConfigurationOperation sut; [TestInitialize] @@ -37,42 +43,24 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations info = new RuntimeInfo(); logger = new Mock(); messageBox = new Mock(); + passwordDialog = new Mock(); repository = new Mock(); + runtimeHost = new Mock(); settings = new Settings(); text = new Mock(); + uiFactory = new Mock(); info.AppDataFolder = @"C:\Not\Really\AppData"; info.DefaultSettingsFileName = "SettingsDummy.txt"; info.ProgramDataFolder = @"C:\Not\Really\ProgramData"; - } - [TestMethod] - public void MustNotFailWithoutCommandLineArgs() - { - repository.Setup(r => r.LoadDefaultSettings()); - - sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, info, text.Object, null); - sut.Perform(); - - sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, info, text.Object, new string[] { }); - sut.Perform(); - - repository.Verify(r => r.LoadDefaultSettings(), Times.Exactly(2)); - } - - [TestMethod] - public void MustNotFailWithInvalidUri() - { - var path = @"an/invalid\path.'*%yolo/()"; - - sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, info, text.Object, new[] { "blubb.exe", path }); - sut.Perform(); + uiFactory.Setup(f => f.CreatePasswordDialog(It.IsAny(), It.IsAny())).Returns(passwordDialog.Object); } [TestMethod] public void MustUseCommandLineArgumentAs1stPrio() { - var path = @"http://www.safeexambrowser.org/whatever.seb"; + var url = @"http://www.safeexambrowser.org/whatever.seb"; var location = Path.GetDirectoryName(GetType().Assembly.Location); info.ProgramDataFolder = location; @@ -81,10 +69,10 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations repository.SetupGet(r => r.CurrentSettings).Returns(settings); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, info, text.Object, new[] { "blubb.exe", path }); + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); sut.Perform(); - var resource = new Uri(path); + var resource = new Uri(url); repository.Verify(r => r.LoadSettings(It.Is(u => u.Equals(resource)), null, null), Times.Once); } @@ -100,7 +88,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations repository.SetupGet(r => r.CurrentSettings).Returns(settings); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, info, text.Object, null); + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, null); sut.Perform(); var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); @@ -118,7 +106,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations repository.SetupGet(r => r.CurrentSettings).Returns(settings); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, info, text.Object, null); + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, null); sut.Perform(); var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); @@ -129,7 +117,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations [TestMethod] public void MustFallbackToDefaultsAsLastPrio() { - sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, info, text.Object, null); + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, null); sut.Perform(); repository.Verify(r => r.LoadDefaultSettings(), Times.Once); @@ -139,11 +127,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations public void MustAbortIfWishedByUser() { info.ProgramDataFolder = Path.GetDirectoryName(GetType().Assembly.Location); - messageBox.Setup(u => u.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(MessageBoxResult.Yes); + messageBox.Setup(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(MessageBoxResult.Yes); repository.SetupGet(r => r.CurrentSettings).Returns(settings); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, info, text.Object, null); + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, null); var result = sut.Perform(); @@ -153,15 +141,121 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations [TestMethod] public void MustNotAbortIfNotWishedByUser() { - messageBox.Setup(u => u.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(MessageBoxResult.No); + messageBox.Setup(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(MessageBoxResult.No); repository.SetupGet(r => r.CurrentSettings).Returns(settings); repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); - sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, info, text.Object, null); + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, null); var result = sut.Perform(); Assert.AreEqual(OperationResult.Success, result); } + + [TestMethod] + public void MustNotAllowToAbortIfNotInConfigureClientMode() + { + settings.ConfigurationMode = ConfigurationMode.Exam; + repository.SetupGet(r => r.CurrentSettings).Returns(settings); + repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.Success); + + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, null); + sut.Perform(); + + messageBox.Verify(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [TestMethod] + public void MustNotFailWithoutCommandLineArgs() + { + repository.Setup(r => r.LoadDefaultSettings()); + + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, null); + sut.Perform(); + + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, new string[] { }); + sut.Perform(); + + repository.Verify(r => r.LoadDefaultSettings(), Times.Exactly(2)); + } + + [TestMethod] + public void MustNotFailWithInvalidUri() + { + var uri = @"an/invalid\uri.'*%yolo/()你好"; + + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, new[] { "blubb.exe", uri }); + sut.Perform(); + } + + [TestMethod] + public void MustOnlyAllowToEnterAdminPasswordFiveTimes() + { + var result = new PasswordDialogResultStub { Success = true }; + var url = @"http://www.safeexambrowser.org/whatever.seb"; + + passwordDialog.Setup(d => d.Show(null)).Returns(result); + repository.SetupGet(r => r.CurrentSettings).Returns(settings); + repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.AdminPasswordNeeded); + + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut.Perform(); + + repository.Verify(r => r.LoadSettings(It.IsAny(), null, null), Times.Exactly(5)); + } + + [TestMethod] + public void MustOnlyAllowToEnterSettingsPasswordFiveTimes() + { + var result = new PasswordDialogResultStub { Success = true }; + var url = @"http://www.safeexambrowser.org/whatever.seb"; + + passwordDialog.Setup(d => d.Show(null)).Returns(result); + repository.SetupGet(r => r.CurrentSettings).Returns(settings); + repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); + + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut.Perform(); + + repository.Verify(r => r.LoadSettings(It.IsAny(), null, null), Times.Exactly(5)); + } + + [TestMethod] + public void MustSucceedIfAdminPasswordCorrect() + { + var password = "test"; + var result = new PasswordDialogResultStub { Password = password, Success = true }; + var url = @"http://www.safeexambrowser.org/whatever.seb"; + + passwordDialog.Setup(d => d.Show(null)).Returns(result); + repository.SetupGet(r => r.CurrentSettings).Returns(settings); + repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.AdminPasswordNeeded); + repository.Setup(r => r.LoadSettings(It.IsAny(), password, null)).Returns(LoadStatus.Success); + + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut.Perform(); + + repository.Verify(r => r.LoadSettings(It.IsAny(), null, null), Times.Exactly(1)); + repository.Verify(r => r.LoadSettings(It.IsAny(), password, null), Times.Exactly(1)); + } + + [TestMethod] + public void MustSucceedIfSettingsPasswordCorrect() + { + var password = "test"; + var result = new PasswordDialogResultStub { Password = password, Success = true }; + var url = @"http://www.safeexambrowser.org/whatever.seb"; + + passwordDialog.Setup(d => d.Show(null)).Returns(result); + repository.SetupGet(r => r.CurrentSettings).Returns(settings); + repository.Setup(r => r.LoadSettings(It.IsAny(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded); + repository.Setup(r => r.LoadSettings(It.IsAny(), null, password)).Returns(LoadStatus.Success); + + sut = new ConfigurationOperation(repository.Object, logger.Object, messageBox.Object, runtimeHost.Object, info, text.Object, uiFactory.Object, new[] { "blubb.exe", url }); + sut.Perform(); + + repository.Verify(r => r.LoadSettings(It.IsAny(), null, null), Times.Exactly(1)); + repository.Verify(r => r.LoadSettings(It.IsAny(), null, password), Times.Exactly(1)); + } } } diff --git a/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/PasswordDialogResultStub.cs b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/PasswordDialogResultStub.cs new file mode 100644 index 00000000..ed553b45 --- /dev/null +++ b/SafeExamBrowser.Runtime.UnitTests/Behaviour/Operations/PasswordDialogResultStub.cs @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using SafeExamBrowser.Contracts.UserInterface.Windows; + +namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations +{ + internal class PasswordDialogResultStub : IPasswordDialogResult + { + public string Password { get; set; } + public bool Success { get; set; } + } +} diff --git a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj index 28350f1a..9c24e99f 100644 --- a/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj +++ b/SafeExamBrowser.Runtime.UnitTests/SafeExamBrowser.Runtime.UnitTests.csproj @@ -81,6 +81,7 @@ + diff --git a/SafeExamBrowser.Runtime/Behaviour/Operations/ConfigurationOperation.cs b/SafeExamBrowser.Runtime/Behaviour/Operations/ConfigurationOperation.cs index f459e75a..a5caf563 100644 --- a/SafeExamBrowser.Runtime/Behaviour/Operations/ConfigurationOperation.cs +++ b/SafeExamBrowser.Runtime/Behaviour/Operations/ConfigurationOperation.cs @@ -8,7 +8,11 @@ using System; using System.IO; +using System.Threading; using SafeExamBrowser.Contracts.Behaviour.OperationModel; +using SafeExamBrowser.Contracts.Communication.Data; +using SafeExamBrowser.Contracts.Communication.Events; +using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.I18n; @@ -23,8 +27,10 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations private IConfigurationRepository repository; private ILogger logger; private IMessageBox messageBox; - private IText text; + private IRuntimeHost runtimeHost; private RuntimeInfo runtimeInfo; + private IText text; + private IUserInterfaceFactory uiFactory; private string[] commandLineArgs; public IProgressIndicator ProgressIndicator { private get; set; } @@ -33,16 +39,20 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations IConfigurationRepository repository, ILogger logger, IMessageBox messageBox, + IRuntimeHost runtimeHost, RuntimeInfo runtimeInfo, IText text, + IUserInterfaceFactory uiFactory, string[] commandLineArgs) { this.repository = repository; this.logger = logger; this.messageBox = messageBox; this.commandLineArgs = commandLineArgs; + this.runtimeHost = runtimeHost; this.runtimeInfo = runtimeInfo; this.text = text; + this.uiFactory = uiFactory; } public OperationResult Perform() @@ -54,22 +64,11 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations if (isValidUri) { - logger.Info($"Loading settings from '{uri.AbsolutePath}'..."); + logger.Info($"Attempting to load settings from '{uri.AbsolutePath}'..."); var result = LoadSettings(uri); - if (result == OperationResult.Success && repository.CurrentSettings.ConfigurationMode == ConfigurationMode.ConfigureClient) - { - var abort = IsConfigurationSufficient(); - - logger.Info($"The user chose to {(abort ? "abort" : "continue")} after successful client configuration."); - - if (abort) - { - return OperationResult.Aborted; - } - } - + HandleClientConfiguration(ref result); LogOperationResult(result); return result; @@ -90,7 +89,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations if (isValidUri) { - logger.Info($"Loading settings from '{uri.AbsolutePath}'..."); + logger.Info($"Attempting to load settings from '{uri.AbsolutePath}'..."); var result = LoadSettings(uri); @@ -117,19 +116,19 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations for (int adminAttempts = 0, settingsAttempts = 0; adminAttempts < 5 && settingsAttempts < 5;) { - status = repository.LoadSettings(uri, settingsPassword, adminPassword); + status = repository.LoadSettings(uri, adminPassword, settingsPassword); if (status == LoadStatus.AdminPasswordNeeded || status == LoadStatus.SettingsPasswordNeeded) { - var isAdmin = status == LoadStatus.AdminPasswordNeeded; - var success = isAdmin ? TryGetAdminPassword(out adminPassword) : TryGetSettingsPassword(out settingsPassword); + var purpose = status == LoadStatus.AdminPasswordNeeded ? PasswordRequestPurpose.Administrator : PasswordRequestPurpose.Settings; + var aborted = !TryGetPassword(purpose, out string password); - if (success) - { - adminAttempts += isAdmin ? 1 : 0; - settingsAttempts += isAdmin ? 0 : 1; - } - else + adminAttempts += purpose == PasswordRequestPurpose.Administrator ? 1 : 0; + adminPassword = purpose == PasswordRequestPurpose.Administrator ? password : adminPassword; + settingsAttempts += purpose == PasswordRequestPurpose.Settings ? 1 : 0; + settingsPassword = purpose == PasswordRequestPurpose.Settings ? password : settingsPassword; + + if (aborted) { return OperationResult.Aborted; } @@ -142,45 +141,101 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations if (status == LoadStatus.InvalidData) { - if (IsHtmlPage(uri)) - { - repository.LoadDefaultSettings(); - repository.CurrentSettings.Browser.StartUrl = uri.AbsoluteUri; - logger.Info($"The specified URI '{uri.AbsoluteUri}' appears to point to a HTML page, setting it as startup URL."); - - return OperationResult.Success; - } - - logger.Error($"The specified settings resource '{uri.AbsoluteUri}' is invalid!"); + HandleInvalidData(ref status, uri); } return status == LoadStatus.Success ? OperationResult.Success : OperationResult.Failed; } + private bool TryGetPassword(PasswordRequestPurpose purpose, out string password) + { + var isStartup = repository.CurrentSession == null; + var isRunningOnDefaultDesktop = repository.CurrentSettings?.KioskMode == KioskMode.DisableExplorerShell; + + if (isStartup || isRunningOnDefaultDesktop) + { + return TryGetPasswordViaDialog(purpose, out password); + } + else + { + return TryGetPasswordViaClient(purpose, out password); + } + } + + private bool TryGetPasswordViaDialog(PasswordRequestPurpose purpose, out string password) + { + var isAdmin = purpose == PasswordRequestPurpose.Administrator; + var message = isAdmin ? TextKey.PasswordDialog_AdminPasswordRequired : TextKey.PasswordDialog_SettingsPasswordRequired; + var title = isAdmin ? TextKey.PasswordDialog_AdminPasswordRequiredTitle : TextKey.PasswordDialog_SettingsPasswordRequiredTitle; + var dialog = uiFactory.CreatePasswordDialog(text.Get(message), text.Get(title)); + var result = dialog.Show(); + + if (result.Success) + { + password = result.Password; + } + else + { + password = default(string); + } + + return result.Success; + } + + private bool TryGetPasswordViaClient(PasswordRequestPurpose purpose, out string password) + { + var requestId = Guid.NewGuid(); + var response = default(PasswordEventArgs); + var responseEvent = new AutoResetEvent(false); + var responseEventHandler = new CommunicationEventHandler((args) => + { + if (args.RequestId == requestId) + { + response = args; + responseEvent.Set(); + } + }); + + runtimeHost.PasswordReceived += responseEventHandler; + repository.CurrentSession.ClientProxy.RequestPassword(purpose, requestId); + responseEvent.WaitOne(); + runtimeHost.PasswordReceived -= responseEventHandler; + + if (response.Success) + { + password = response.Password; + } + else + { + password = default(string); + } + + return response.Success; + } + + private void HandleInvalidData(ref LoadStatus status, Uri uri) + { + if (IsHtmlPage(uri)) + { + repository.LoadDefaultSettings(); + repository.CurrentSettings.Browser.StartUrl = uri.AbsoluteUri; + logger.Info($"The specified URI '{uri.AbsoluteUri}' appears to point to a HTML page, setting it as startup URL."); + + status = LoadStatus.Success; + } + else + { + logger.Error($"The specified settings resource '{uri.AbsoluteUri}' is invalid!"); + } + } + private bool IsHtmlPage(Uri uri) { // TODO + return false; } - private bool TryGetAdminPassword(out string password) - { - password = default(string); - - // TODO - - return true; - } - - private bool TryGetSettingsPassword(out string password) - { - password = default(string); - - // TODO - - return true; - } - private bool TryInitializeSettingsUri(out Uri uri) { var path = string.Empty; @@ -224,6 +279,21 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations return isValidUri; } + private void HandleClientConfiguration(ref OperationResult result) + { + if (result == OperationResult.Success && repository.CurrentSettings.ConfigurationMode == ConfigurationMode.ConfigureClient) + { + var abort = IsConfigurationSufficient(); + + logger.Info($"The user chose to {(abort ? "abort" : "continue")} after successful client configuration."); + + if (abort) + { + result = OperationResult.Aborted; + } + } + } + private bool IsConfigurationSufficient() { var message = text.Get(TextKey.MessageBox_ClientConfigurationQuestion); diff --git a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs index 63cf64c3..af14c254 100644 --- a/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs +++ b/SafeExamBrowser.Runtime/Communication/RuntimeHost.cs @@ -25,6 +25,7 @@ namespace SafeExamBrowser.Runtime.Communication public event CommunicationEventHandler ClientDisconnected; public event CommunicationEventHandler ClientReady; + public event CommunicationEventHandler PasswordReceived; public event CommunicationEventHandler ReconfigurationRequested; public event CommunicationEventHandler ShutdownRequested; @@ -62,6 +63,9 @@ namespace SafeExamBrowser.Runtime.Communication { switch (message) { + case PasswordReplyMessage r: + PasswordReceived?.InvokeAsync(new PasswordEventArgs { Password = r.Password, RequestId = r.RequestId, Success = r.Success }); + return new SimpleResponse(SimpleResponsePurport.Acknowledged); case ReconfigurationMessage r: ReconfigurationRequested?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = r.ConfigurationPath }); return new SimpleResponse(SimpleResponsePurport.Acknowledged); diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index fa7690c4..e4e4b9d6 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -63,7 +63,7 @@ namespace SafeExamBrowser.Runtime bootstrapOperations.Enqueue(new I18nOperation(logger, text)); bootstrapOperations.Enqueue(new CommunicationOperation(runtimeHost, logger)); - sessionOperations.Enqueue(new ConfigurationOperation(configuration, logger, messageBox, runtimeInfo, text, args)); + sessionOperations.Enqueue(new ConfigurationOperation(configuration, logger, messageBox, runtimeHost, runtimeInfo, text, uiFactory, args)); sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost)); sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy, text)); sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, TEN_SECONDS)); diff --git a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs index 5b057bbe..f450929c 100644 --- a/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs +++ b/SafeExamBrowser.UserInterface.Classic/UserInterfaceFactory.cs @@ -44,6 +44,11 @@ namespace SafeExamBrowser.UserInterface.Classic return new BrowserWindow(control, settings); } + public ISystemKeyboardLayoutControl CreateKeyboardLayoutControl() + { + return new KeyboardLayoutControl(); + } + public IWindow CreateLogWindow(ILogger logger) { LogWindow logWindow = null; @@ -74,9 +79,9 @@ namespace SafeExamBrowser.UserInterface.Classic return new NotificationButton(info); } - public ISystemKeyboardLayoutControl CreateKeyboardLayoutControl() + public IPasswordDialog CreatePasswordDialog(string message, string title) { - return new KeyboardLayoutControl(); + throw new System.NotImplementedException(); } public ISystemPowerSupplyControl CreatePowerSupplyControl() diff --git a/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs index 7db00aeb..5e0d5828 100644 --- a/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs +++ b/SafeExamBrowser.UserInterface.Windows10/UserInterfaceFactory.cs @@ -81,6 +81,12 @@ namespace SafeExamBrowser.UserInterface.Windows10 throw new System.NotImplementedException(); } + public IPasswordDialog CreatePasswordDialog(string message, string title) + { + // TODO + throw new System.NotImplementedException(); + } + public ISystemPowerSupplyControl CreatePowerSupplyControl() { return new PowerSupplyControl();