SEBWIN-220: Finished draft of (re-)configuration mechanism.

This commit is contained in:
dbuechel 2018-06-27 14:02:16 +02:00
parent 639bde7860
commit eb47cb362b
30 changed files with 529 additions and 101 deletions

View file

@ -11,7 +11,7 @@ using System;
namespace SafeExamBrowser.Contracts.Communication.Data namespace SafeExamBrowser.Contracts.Communication.Data
{ {
/// <summary> /// <summary>
/// The response to be used to reply to an authentication request (see <see cref="Messages.SimpleMessagePurport.Authenticate"/>). /// The response to be used to reply to an authentication request (see <see cref="SimpleMessagePurport.Authenticate"/>).
/// </summary> /// </summary>
[Serializable] [Serializable]
public class AuthenticationResponse : Response public class AuthenticationResponse : Response

View file

@ -12,7 +12,7 @@ using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Contracts.Communication.Data namespace SafeExamBrowser.Contracts.Communication.Data
{ {
/// <summary> /// <summary>
/// The response to be used to reply to a configuration request (see <see cref="Messages.SimpleMessagePurport.ConfigurationNeeded"/>). /// The response to be used to reply to a configuration request (see <see cref="SimpleMessagePurport.ConfigurationNeeded"/>).
/// </summary> /// </summary>
[Serializable] [Serializable]
public class ConfigurationResponse : Response public class ConfigurationResponse : Response

View file

@ -11,7 +11,7 @@ using System;
namespace SafeExamBrowser.Contracts.Communication.Data namespace SafeExamBrowser.Contracts.Communication.Data
{ {
/// <summary> /// <summary>
/// The response transmitted to a <see cref="Messages.DisconnectionMessage"/> /// The response transmitted to a <see cref="DisconnectionMessage"/>
/// </summary> /// </summary>
[Serializable] [Serializable]
public class DisconnectionResponse : Response public class DisconnectionResponse : Response

View file

@ -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
{
/// <summary>
/// The reply to a <see cref="PasswordRequestMessage"/>.
/// </summary>
[Serializable]
public class PasswordReplyMessage : Message
{
/// <summary>
/// The password entered by the user, or <c>null</c> if the user interaction was unsuccessful.
/// </summary>
public string Password { get; private set; }
/// <summary>
/// The unique identifier for the password request.
/// </summary>
public Guid RequestId { get; private set; }
/// <summary>
/// Determines whether the user interaction was successful or not.
/// </summary>
public bool Success { get; private set; }
public PasswordReplyMessage(string password, Guid requestId, bool success)
{
Password = password;
RequestId = requestId;
Success = success;
}
}
}

View file

@ -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
{
/// <summary>
/// This message is transmitted to the client to request a password input by the user.
/// </summary>
[Serializable]
public class PasswordRequestMessage : Message
{
/// <summary>
/// The purpose of the password request.
/// </summary>
public PasswordRequestPurpose Purpose { get; private set; }
/// <summary>
/// The unique identifier for the password request.
/// </summary>
public Guid RequestId { get; private set; }
public PasswordRequestMessage(PasswordRequestPurpose purpose, Guid requestId)
{
Purpose = purpose;
RequestId = requestId;
}
}
}

View file

@ -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
{
/// <summary>
/// Defines all possible reasons for a <see cref="PasswordRequestMessage"/>.
/// </summary>
public enum PasswordRequestPurpose
{
Undefined = 0,
/// <summary>
/// The password is to be used as administrator password for an application configuration.
/// </summary>
Administrator,
/// <summary>
/// The password is to be used as settings password for an application configuration.
/// </summary>
Settings
}
}

View file

@ -11,7 +11,7 @@ using System;
namespace SafeExamBrowser.Contracts.Communication.Data namespace SafeExamBrowser.Contracts.Communication.Data
{ {
/// <summary> /// <summary>
/// The response to a <see cref="Messages.ReconfigurationMessage"/>. /// The response to a <see cref="ReconfigurationMessage"/>.
/// </summary> /// </summary>
[Serializable] [Serializable]
public class ReconfigurationResponse : Response public class ReconfigurationResponse : Response

View file

@ -11,7 +11,7 @@ using System;
namespace SafeExamBrowser.Contracts.Communication.Data namespace SafeExamBrowser.Contracts.Communication.Data
{ {
/// <summary> /// <summary>
/// The base class for respones, from which a response must inherit in order to be sent to an interlocutor as reply to <see cref="ICommunication.Send(Messages.Message)"/>. /// The base class for respones, from which a response must inherit in order to be sent to an interlocutor as reply to <see cref="ICommunication.Send(Message)"/>.
/// </summary> /// </summary>
[Serializable] [Serializable]
public abstract class Response public abstract class Response

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
namespace SafeExamBrowser.Contracts.Communication.Events
{
/// <summary>
/// The event arguments used for the password input event fired by the <see cref="Hosts.IRuntimeHost"/>.
/// </summary>
public class PasswordEventArgs : CommunicationEventArgs
{
/// <summary>
/// The password entered by the user, or <c>null</c> if not available.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Identifies the password request.
/// </summary>
public Guid RequestId { get; set; }
/// <summary>
/// Indicates whether the password has been successfully entered by the user.
/// </summary>
public bool Success { get; set; }
}
}

View file

@ -31,6 +31,11 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// </summary> /// </summary>
event CommunicationEventHandler ClientReady; event CommunicationEventHandler ClientReady;
/// <summary>
/// Event fired when the client transmitted a password entered by the user.
/// </summary>
event CommunicationEventHandler<PasswordEventArgs> PasswordReceived;
/// <summary> /// <summary>
/// Event fired when the client requested a reconfiguration of the application. /// Event fired when the client requested a reconfiguration of the application.
/// </summary> /// </summary>

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
namespace SafeExamBrowser.Contracts.Communication.Proxies namespace SafeExamBrowser.Contracts.Communication.Proxies
@ -26,5 +27,11 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// </summary> /// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception> /// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
AuthenticationResponse RequestAuthentication(); AuthenticationResponse RequestAuthentication();
/// <summary>
/// Requests the client to render a password dialog and subsequently return the interaction result as separate message.
/// </summary>
/// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
void RequestPassword(PasswordRequestPurpose purpose, Guid requestId);
} }
} }

View file

@ -51,7 +51,7 @@ namespace SafeExamBrowser.Contracts.Configuration
/// Attempts to load settings from the specified resource, using the optional passwords. Returns a <see cref="LoadStatus"/> /// Attempts to load settings from the specified resource, using the optional passwords. Returns a <see cref="LoadStatus"/>
/// indicating the result of the operation. /// indicating the result of the operation.
/// </summary> /// </summary>
LoadStatus LoadSettings(Uri resource, string settingsPassword = null, string adminPassword = null); LoadStatus LoadSettings(Uri resource, string adminPassword = null, string settingsPassword = null);
/// <summary> /// <summary>
/// Loads the default settings. /// Loads the default settings.

View file

@ -44,6 +44,10 @@ namespace SafeExamBrowser.Contracts.I18n
MessageBox_StartupErrorTitle, MessageBox_StartupErrorTitle,
Notification_AboutTooltip, Notification_AboutTooltip,
Notification_LogTooltip, Notification_LogTooltip,
PasswordDialog_AdminPasswordRequired,
PasswordDialog_AdminPasswordRequiredTitle,
PasswordDialog_SettingsPasswordRequired,
PasswordDialog_SettingsPasswordRequiredTitle,
ProgressIndicator_CloseRuntimeConnection, ProgressIndicator_CloseRuntimeConnection,
ProgressIndicator_EmptyClipboard, ProgressIndicator_EmptyClipboard,
ProgressIndicator_FinalizeServiceSession, ProgressIndicator_FinalizeServiceSession,

View file

@ -60,8 +60,12 @@
<Compile Include="Browser\DownloadFinishedCallback.cs" /> <Compile Include="Browser\DownloadFinishedCallback.cs" />
<Compile Include="Browser\DownloadRequestedEventHandler.cs" /> <Compile Include="Browser\DownloadRequestedEventHandler.cs" />
<Compile Include="Browser\IBrowserApplicationController.cs" /> <Compile Include="Browser\IBrowserApplicationController.cs" />
<Compile Include="Communication\Data\PasswordRequestMessage.cs" />
<Compile Include="Communication\Data\PasswordRequestPurpose.cs" />
<Compile Include="Communication\Data\PasswordReplyMessage.cs" />
<Compile Include="Communication\Events\CommunicationEventArgs.cs" /> <Compile Include="Communication\Events\CommunicationEventArgs.cs" />
<Compile Include="Communication\Events\CommunicationEventHandler.cs" /> <Compile Include="Communication\Events\CommunicationEventHandler.cs" />
<Compile Include="Communication\Events\PasswordEventArgs.cs" />
<Compile Include="Communication\Events\ReconfigurationEventArgs.cs" /> <Compile Include="Communication\Events\ReconfigurationEventArgs.cs" />
<Compile Include="Communication\Hosts\IClientHost.cs" /> <Compile Include="Communication\Hosts\IClientHost.cs" />
<Compile Include="Communication\Hosts\IHostObject.cs" /> <Compile Include="Communication\Hosts\IHostObject.cs" />
@ -139,6 +143,8 @@
<Compile Include="UserInterface\MessageBox\IMessageBox.cs" /> <Compile Include="UserInterface\MessageBox\IMessageBox.cs" />
<Compile Include="UserInterface\IProgressIndicator.cs" /> <Compile Include="UserInterface\IProgressIndicator.cs" />
<Compile Include="UserInterface\Taskbar\QuitButtonClickedEventHandler.cs" /> <Compile Include="UserInterface\Taskbar\QuitButtonClickedEventHandler.cs" />
<Compile Include="UserInterface\Windows\IPasswordDialog.cs" />
<Compile Include="UserInterface\Windows\IPasswordDialogResult.cs" />
<Compile Include="UserInterface\Windows\IRuntimeWindow.cs" /> <Compile Include="UserInterface\Windows\IRuntimeWindow.cs" />
<Compile Include="UserInterface\MessageBox\MessageBoxResult.cs" /> <Compile Include="UserInterface\MessageBox\MessageBoxResult.cs" />
<Compile Include="UserInterface\Taskbar\INotificationButton.cs" /> <Compile Include="UserInterface\Taskbar\INotificationButton.cs" />

View file

@ -37,6 +37,11 @@ namespace SafeExamBrowser.Contracts.UserInterface
/// </summary> /// </summary>
IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings); IBrowserWindow CreateBrowserWindow(IBrowserControl control, BrowserSettings settings);
/// <summary>
/// Creates a system control which allows to change the keyboard layout of the computer.
/// </summary>
ISystemKeyboardLayoutControl CreateKeyboardLayoutControl();
/// <summary> /// <summary>
/// Creates a new log window which runs on its own thread. /// Creates a new log window which runs on its own thread.
/// </summary> /// </summary>
@ -48,9 +53,9 @@ namespace SafeExamBrowser.Contracts.UserInterface
INotificationButton CreateNotification(INotificationInfo info); INotificationButton CreateNotification(INotificationInfo info);
/// <summary> /// <summary>
/// Creates a system control which allows to change the keyboard layout of the computer. /// Creates a password dialog with the given message and title.
/// </summary> /// </summary>
ISystemKeyboardLayoutControl CreateKeyboardLayoutControl(); IPasswordDialog CreatePasswordDialog(string message, string title);
/// <summary> /// <summary>
/// Creates a system control displaying the power supply status of the computer. /// Creates a system control displaying the power supply status of the computer.

View file

@ -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
{
/// <summary>
/// Defines the functionality of a password dialog.
/// </summary>
public interface IPasswordDialog : IWindow
{
/// <summary>
/// Shows the dialog as topmost window. If a parent window is specified, the dialog is rendered modally for the given parent.
/// </summary>
IPasswordDialogResult Show(IWindow parent = null);
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.UserInterface.Windows
{
/// <summary>
/// Defines the user interaction result of an <see cref="IPasswordDialog"/>.
/// </summary>
public interface IPasswordDialogResult
{
/// <summary>
/// The password entered by the user, or <c>null</c> if the interaction was unsuccessful.
/// </summary>
string Password { get; }
/// <summary>
/// Indicates whether the user confirmed the dialog or not.
/// </summary>
bool Success { get; }
}
}

View file

@ -14,14 +14,14 @@ using SafeExamBrowser.Core.Communication.Hosts;
namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts
{ {
internal class BaseHostImpl : BaseHost internal class BaseHostStub : BaseHost
{ {
public Func<Guid?, bool> OnConnectStub { get; set; } public Func<Guid?, bool> OnConnectStub { get; set; }
public Action OnDisconnectStub { get; set; } public Action OnDisconnectStub { get; set; }
public Func<Message, Response> OnReceiveStub { get; set; } public Func<Message, Response> OnReceiveStub { get; set; }
public Func<SimpleMessagePurport, Response> OnReceiveSimpleMessageStub { get; set; } public Func<SimpleMessagePurport, Response> 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)
{ {
} }

View file

@ -24,7 +24,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts
private Mock<IHostObject> hostObject; private Mock<IHostObject> hostObject;
private Mock<IHostObjectFactory> hostObjectFactory; private Mock<IHostObjectFactory> hostObjectFactory;
private Mock<ILogger> logger; private Mock<ILogger> logger;
private BaseHostImpl sut; private BaseHostStub sut;
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
@ -35,7 +35,7 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts
hostObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>(), It.IsAny<ICommunication>())).Returns(hostObject.Object); hostObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>(), It.IsAny<ICommunication>())).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] [TestMethod]

View file

@ -83,7 +83,7 @@
<Compile Include="Behaviour\OperationModel\I18nOperationTests.cs" /> <Compile Include="Behaviour\OperationModel\I18nOperationTests.cs" />
<Compile Include="Behaviour\OperationModel\DelegateOperationTests.cs" /> <Compile Include="Behaviour\OperationModel\DelegateOperationTests.cs" />
<Compile Include="Behaviour\OperationModel\OperationSequenceTests.cs" /> <Compile Include="Behaviour\OperationModel\OperationSequenceTests.cs" />
<Compile Include="Communication\Hosts\BaseHostImpl.cs" /> <Compile Include="Communication\Hosts\BaseHostStub.cs" />
<Compile Include="Communication\Hosts\BaseHostTests.cs" /> <Compile Include="Communication\Hosts\BaseHostTests.cs" />
<Compile Include="Communication\Proxies\BaseProxyImpl.cs" /> <Compile Include="Communication\Proxies\BaseProxyImpl.cs" />
<Compile Include="Communication\Proxies\BaseProxyTests.cs" /> <Compile Include="Communication\Proxies\BaseProxyTests.cs" />

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.ServiceModel; using System.ServiceModel;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; 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)}."); throw new CommunicationException($"Did not receive authentication response! Received: {ToString(response)}.");
} }
public void RequestPassword(PasswordRequestPurpose purpose, Guid requestId)
{
// TODO
throw new NotImplementedException();
}
} }
} }

View file

@ -84,6 +84,18 @@
<Entry key="Notification_LogTooltip"> <Entry key="Notification_LogTooltip">
Application Log Application Log
</Entry> </Entry>
<Entry key="PasswordDialog_AdminPasswordRequired">
Please enter the administrator password for the application configuration:
</Entry>
<Entry key="PasswordDialog_AdminPasswordRequiredTitle">
Administrator Password Required
</Entry>
<Entry key="PasswordDialog_SettingsPasswordRequired">
Please enter the settings password for the application configuration:
</Entry>
<Entry key="PasswordDialog_SettingsPasswordRequiredTitle">
Settings Password Required
</Entry>
<Entry key="ProgressIndicator_CloseRuntimeConnection"> <Entry key="ProgressIndicator_CloseRuntimeConnection">
Closing runtime connection Closing runtime connection
</Entry> </Entry>

View file

@ -11,11 +11,14 @@ using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Contracts.Behaviour.OperationModel; using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface;
using SafeExamBrowser.Contracts.UserInterface.MessageBox; using SafeExamBrowser.Contracts.UserInterface.MessageBox;
using SafeExamBrowser.Contracts.UserInterface.Windows;
using SafeExamBrowser.Runtime.Behaviour.Operations; using SafeExamBrowser.Runtime.Behaviour.Operations;
namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
@ -23,12 +26,15 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestClass] [TestClass]
public class ConfigurationOperationTests public class ConfigurationOperationTests
{ {
private RuntimeInfo info;
private Mock<ILogger> logger; private Mock<ILogger> logger;
private Mock<IMessageBox> messageBox; private Mock<IMessageBox> messageBox;
private RuntimeInfo info; private Mock<IPasswordDialog> passwordDialog;
private Mock<IConfigurationRepository> repository; private Mock<IConfigurationRepository> repository;
private Mock<IRuntimeHost> runtimeHost;
private Settings settings; private Settings settings;
private Mock<IText> text; private Mock<IText> text;
private Mock<IUserInterfaceFactory> uiFactory;
private ConfigurationOperation sut; private ConfigurationOperation sut;
[TestInitialize] [TestInitialize]
@ -37,42 +43,24 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
info = new RuntimeInfo(); info = new RuntimeInfo();
logger = new Mock<ILogger>(); logger = new Mock<ILogger>();
messageBox = new Mock<IMessageBox>(); messageBox = new Mock<IMessageBox>();
passwordDialog = new Mock<IPasswordDialog>();
repository = new Mock<IConfigurationRepository>(); repository = new Mock<IConfigurationRepository>();
runtimeHost = new Mock<IRuntimeHost>();
settings = new Settings(); settings = new Settings();
text = new Mock<IText>(); text = new Mock<IText>();
uiFactory = new Mock<IUserInterfaceFactory>();
info.AppDataFolder = @"C:\Not\Really\AppData"; info.AppDataFolder = @"C:\Not\Really\AppData";
info.DefaultSettingsFileName = "SettingsDummy.txt"; info.DefaultSettingsFileName = "SettingsDummy.txt";
info.ProgramDataFolder = @"C:\Not\Really\ProgramData"; info.ProgramDataFolder = @"C:\Not\Really\ProgramData";
}
[TestMethod] uiFactory.Setup(f => f.CreatePasswordDialog(It.IsAny<string>(), It.IsAny<string>())).Returns(passwordDialog.Object);
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();
} }
[TestMethod] [TestMethod]
public void MustUseCommandLineArgumentAs1stPrio() 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); var location = Path.GetDirectoryName(GetType().Assembly.Location);
info.ProgramDataFolder = location; info.ProgramDataFolder = location;
@ -81,10 +69,10 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
repository.SetupGet(r => r.CurrentSettings).Returns(settings); repository.SetupGet(r => r.CurrentSettings).Returns(settings);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.Success); repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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(); sut.Perform();
var resource = new Uri(path); var resource = new Uri(url);
repository.Verify(r => r.LoadSettings(It.Is<Uri>(u => u.Equals(resource)), null, null), Times.Once); repository.Verify(r => r.LoadSettings(It.Is<Uri>(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.SetupGet(r => r.CurrentSettings).Returns(settings);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.Success); repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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(); sut.Perform();
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); 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.SetupGet(r => r.CurrentSettings).Returns(settings);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.Success); repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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(); sut.Perform();
var resource = new Uri(Path.Combine(location, "SettingsDummy.txt")); var resource = new Uri(Path.Combine(location, "SettingsDummy.txt"));
@ -129,7 +117,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestMethod] [TestMethod]
public void MustFallbackToDefaultsAsLastPrio() 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(); sut.Perform();
repository.Verify(r => r.LoadDefaultSettings(), Times.Once); repository.Verify(r => r.LoadDefaultSettings(), Times.Once);
@ -139,11 +127,11 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
public void MustAbortIfWishedByUser() public void MustAbortIfWishedByUser()
{ {
info.ProgramDataFolder = Path.GetDirectoryName(GetType().Assembly.Location); info.ProgramDataFolder = Path.GetDirectoryName(GetType().Assembly.Location);
messageBox.Setup(u => u.Show(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>())).Returns(MessageBoxResult.Yes); messageBox.Setup(m => m.Show(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>())).Returns(MessageBoxResult.Yes);
repository.SetupGet(r => r.CurrentSettings).Returns(settings); repository.SetupGet(r => r.CurrentSettings).Returns(settings);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.Success); repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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(); var result = sut.Perform();
@ -153,15 +141,121 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
[TestMethod] [TestMethod]
public void MustNotAbortIfNotWishedByUser() public void MustNotAbortIfNotWishedByUser()
{ {
messageBox.Setup(u => u.Show(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>())).Returns(MessageBoxResult.No); messageBox.Setup(m => m.Show(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>())).Returns(MessageBoxResult.No);
repository.SetupGet(r => r.CurrentSettings).Returns(settings); repository.SetupGet(r => r.CurrentSettings).Returns(settings);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), null, null)).Returns(LoadStatus.Success); repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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(); var result = sut.Perform();
Assert.AreEqual(OperationResult.Success, result); 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<Uri>(), 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<string>(), It.IsAny<string>(), It.IsAny<MessageBoxAction>(), It.IsAny<MessageBoxIcon>()), 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<Uri>(), 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<Uri>(), 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<Uri>(), 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<Uri>(), 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<Uri>(), null, null)).Returns(LoadStatus.AdminPasswordNeeded);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>(), null, null), Times.Exactly(1));
repository.Verify(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>(), null, null)).Returns(LoadStatus.SettingsPasswordNeeded);
repository.Setup(r => r.LoadSettings(It.IsAny<Uri>(), 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<Uri>(), null, null), Times.Exactly(1));
repository.Verify(r => r.LoadSettings(It.IsAny<Uri>(), null, password), Times.Exactly(1));
}
} }
} }

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
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; }
}
}

View file

@ -81,6 +81,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Behaviour\Operations\ConfigurationOperationTests.cs" /> <Compile Include="Behaviour\Operations\ConfigurationOperationTests.cs" />
<Compile Include="Behaviour\Operations\KioskModeOperationTests.cs" /> <Compile Include="Behaviour\Operations\KioskModeOperationTests.cs" />
<Compile Include="Behaviour\Operations\PasswordDialogResultStub.cs" />
<Compile Include="Behaviour\Operations\ServiceOperationTests.cs" /> <Compile Include="Behaviour\Operations\ServiceOperationTests.cs" />
<Compile Include="Behaviour\Operations\ClientOperationTests.cs" /> <Compile Include="Behaviour\Operations\ClientOperationTests.cs" />
<Compile Include="Behaviour\Operations\ClientTerminationOperationTests.cs" /> <Compile Include="Behaviour\Operations\ClientTerminationOperationTests.cs" />

View file

@ -8,7 +8,11 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading;
using SafeExamBrowser.Contracts.Behaviour.OperationModel; 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;
using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
@ -23,8 +27,10 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private IConfigurationRepository repository; private IConfigurationRepository repository;
private ILogger logger; private ILogger logger;
private IMessageBox messageBox; private IMessageBox messageBox;
private IText text; private IRuntimeHost runtimeHost;
private RuntimeInfo runtimeInfo; private RuntimeInfo runtimeInfo;
private IText text;
private IUserInterfaceFactory uiFactory;
private string[] commandLineArgs; private string[] commandLineArgs;
public IProgressIndicator ProgressIndicator { private get; set; } public IProgressIndicator ProgressIndicator { private get; set; }
@ -33,16 +39,20 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
IConfigurationRepository repository, IConfigurationRepository repository,
ILogger logger, ILogger logger,
IMessageBox messageBox, IMessageBox messageBox,
IRuntimeHost runtimeHost,
RuntimeInfo runtimeInfo, RuntimeInfo runtimeInfo,
IText text, IText text,
IUserInterfaceFactory uiFactory,
string[] commandLineArgs) string[] commandLineArgs)
{ {
this.repository = repository; this.repository = repository;
this.logger = logger; this.logger = logger;
this.messageBox = messageBox; this.messageBox = messageBox;
this.commandLineArgs = commandLineArgs; this.commandLineArgs = commandLineArgs;
this.runtimeHost = runtimeHost;
this.runtimeInfo = runtimeInfo; this.runtimeInfo = runtimeInfo;
this.text = text; this.text = text;
this.uiFactory = uiFactory;
} }
public OperationResult Perform() public OperationResult Perform()
@ -54,22 +64,11 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
if (isValidUri) if (isValidUri)
{ {
logger.Info($"Loading settings from '{uri.AbsolutePath}'..."); logger.Info($"Attempting to load settings from '{uri.AbsolutePath}'...");
var result = LoadSettings(uri); var result = LoadSettings(uri);
if (result == OperationResult.Success && repository.CurrentSettings.ConfigurationMode == ConfigurationMode.ConfigureClient) HandleClientConfiguration(ref result);
{
var abort = IsConfigurationSufficient();
logger.Info($"The user chose to {(abort ? "abort" : "continue")} after successful client configuration.");
if (abort)
{
return OperationResult.Aborted;
}
}
LogOperationResult(result); LogOperationResult(result);
return result; return result;
@ -90,7 +89,7 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
if (isValidUri) if (isValidUri)
{ {
logger.Info($"Loading settings from '{uri.AbsolutePath}'..."); logger.Info($"Attempting to load settings from '{uri.AbsolutePath}'...");
var result = LoadSettings(uri); var result = LoadSettings(uri);
@ -117,19 +116,19 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
for (int adminAttempts = 0, settingsAttempts = 0; adminAttempts < 5 && settingsAttempts < 5;) 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) if (status == LoadStatus.AdminPasswordNeeded || status == LoadStatus.SettingsPasswordNeeded)
{ {
var isAdmin = status == LoadStatus.AdminPasswordNeeded; var purpose = status == LoadStatus.AdminPasswordNeeded ? PasswordRequestPurpose.Administrator : PasswordRequestPurpose.Settings;
var success = isAdmin ? TryGetAdminPassword(out adminPassword) : TryGetSettingsPassword(out settingsPassword); var aborted = !TryGetPassword(purpose, out string password);
if (success) adminAttempts += purpose == PasswordRequestPurpose.Administrator ? 1 : 0;
{ adminPassword = purpose == PasswordRequestPurpose.Administrator ? password : adminPassword;
adminAttempts += isAdmin ? 1 : 0; settingsAttempts += purpose == PasswordRequestPurpose.Settings ? 1 : 0;
settingsAttempts += isAdmin ? 0 : 1; settingsPassword = purpose == PasswordRequestPurpose.Settings ? password : settingsPassword;
}
else if (aborted)
{ {
return OperationResult.Aborted; return OperationResult.Aborted;
} }
@ -142,45 +141,101 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
if (status == LoadStatus.InvalidData) if (status == LoadStatus.InvalidData)
{ {
if (IsHtmlPage(uri)) HandleInvalidData(ref status, 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!");
} }
return status == LoadStatus.Success ? OperationResult.Success : OperationResult.Failed; 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<PasswordEventArgs>((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) private bool IsHtmlPage(Uri uri)
{ {
// TODO // TODO
return false; 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) private bool TryInitializeSettingsUri(out Uri uri)
{ {
var path = string.Empty; var path = string.Empty;
@ -224,6 +279,21 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
return isValidUri; 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() private bool IsConfigurationSufficient()
{ {
var message = text.Get(TextKey.MessageBox_ClientConfigurationQuestion); var message = text.Get(TextKey.MessageBox_ClientConfigurationQuestion);

View file

@ -25,6 +25,7 @@ namespace SafeExamBrowser.Runtime.Communication
public event CommunicationEventHandler ClientDisconnected; public event CommunicationEventHandler ClientDisconnected;
public event CommunicationEventHandler ClientReady; public event CommunicationEventHandler ClientReady;
public event CommunicationEventHandler<PasswordEventArgs> PasswordReceived;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested; public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
public event CommunicationEventHandler ShutdownRequested; public event CommunicationEventHandler ShutdownRequested;
@ -62,6 +63,9 @@ namespace SafeExamBrowser.Runtime.Communication
{ {
switch (message) 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: case ReconfigurationMessage r:
ReconfigurationRequested?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = r.ConfigurationPath }); ReconfigurationRequested?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = r.ConfigurationPath });
return new SimpleResponse(SimpleResponsePurport.Acknowledged); return new SimpleResponse(SimpleResponsePurport.Acknowledged);

View file

@ -63,7 +63,7 @@ namespace SafeExamBrowser.Runtime
bootstrapOperations.Enqueue(new I18nOperation(logger, text)); bootstrapOperations.Enqueue(new I18nOperation(logger, text));
bootstrapOperations.Enqueue(new CommunicationOperation(runtimeHost, logger)); 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 SessionInitializationOperation(configuration, logger, runtimeHost));
sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy, text)); sessionOperations.Enqueue(new ServiceOperation(configuration, logger, serviceProxy, text));
sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, TEN_SECONDS)); sessionOperations.Enqueue(new ClientTerminationOperation(configuration, logger, processFactory, proxyFactory, runtimeHost, TEN_SECONDS));

View file

@ -44,6 +44,11 @@ namespace SafeExamBrowser.UserInterface.Classic
return new BrowserWindow(control, settings); return new BrowserWindow(control, settings);
} }
public ISystemKeyboardLayoutControl CreateKeyboardLayoutControl()
{
return new KeyboardLayoutControl();
}
public IWindow CreateLogWindow(ILogger logger) public IWindow CreateLogWindow(ILogger logger)
{ {
LogWindow logWindow = null; LogWindow logWindow = null;
@ -74,9 +79,9 @@ namespace SafeExamBrowser.UserInterface.Classic
return new NotificationButton(info); return new NotificationButton(info);
} }
public ISystemKeyboardLayoutControl CreateKeyboardLayoutControl() public IPasswordDialog CreatePasswordDialog(string message, string title)
{ {
return new KeyboardLayoutControl(); throw new System.NotImplementedException();
} }
public ISystemPowerSupplyControl CreatePowerSupplyControl() public ISystemPowerSupplyControl CreatePowerSupplyControl()

View file

@ -81,6 +81,12 @@ namespace SafeExamBrowser.UserInterface.Windows10
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public IPasswordDialog CreatePasswordDialog(string message, string title)
{
// TODO
throw new System.NotImplementedException();
}
public ISystemPowerSupplyControl CreatePowerSupplyControl() public ISystemPowerSupplyControl CreatePowerSupplyControl()
{ {
return new PowerSupplyControl(); return new PowerSupplyControl();