SEBWIN-220: Implemented password input mechanism and dialog.

This commit is contained in:
dbuechel 2018-07-04 09:53:33 +02:00
parent 9a12bbdb7d
commit f8e5a4bedf
30 changed files with 316 additions and 71 deletions

View file

@ -12,6 +12,8 @@ using System.IO;
using SafeExamBrowser.Contracts.Behaviour; using SafeExamBrowser.Contracts.Behaviour;
using SafeExamBrowser.Contracts.Behaviour.OperationModel; using SafeExamBrowser.Contracts.Behaviour.OperationModel;
using SafeExamBrowser.Contracts.Browser; using SafeExamBrowser.Contracts.Browser;
using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Events;
using SafeExamBrowser.Contracts.Communication.Hosts; using SafeExamBrowser.Contracts.Communication.Hosts;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
@ -37,6 +39,7 @@ namespace SafeExamBrowser.Client.Behaviour
private Action shutdown; private Action shutdown;
private ISplashScreen splashScreen; private ISplashScreen splashScreen;
private ITaskbar taskbar; private ITaskbar taskbar;
private IText text;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
private IWindowMonitor windowMonitor; private IWindowMonitor windowMonitor;
private AppConfig appConfig; private AppConfig appConfig;
@ -68,6 +71,7 @@ namespace SafeExamBrowser.Client.Behaviour
IRuntimeProxy runtime, IRuntimeProxy runtime,
Action shutdown, Action shutdown,
ITaskbar taskbar, ITaskbar taskbar,
IText text,
IUserInterfaceFactory uiFactory, IUserInterfaceFactory uiFactory,
IWindowMonitor windowMonitor) IWindowMonitor windowMonitor)
{ {
@ -79,6 +83,7 @@ namespace SafeExamBrowser.Client.Behaviour
this.runtime = runtime; this.runtime = runtime;
this.shutdown = shutdown; this.shutdown = shutdown;
this.taskbar = taskbar; this.taskbar = taskbar;
this.text = text;
this.uiFactory = uiFactory; this.uiFactory = uiFactory;
this.windowMonitor = windowMonitor; this.windowMonitor = windowMonitor;
} }
@ -150,6 +155,8 @@ namespace SafeExamBrowser.Client.Behaviour
private void RegisterEvents() private void RegisterEvents()
{ {
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested; Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested;
ClientHost.ReconfigurationDenied += ClientHost_ReconfigurationDenied;
ClientHost.Shutdown += ClientHost_Shutdown; ClientHost.Shutdown += ClientHost_Shutdown;
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged; displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted; processMonitor.ExplorerStarted += ProcessMonitor_ExplorerStarted;
@ -161,6 +168,8 @@ namespace SafeExamBrowser.Client.Behaviour
private void DeregisterEvents() private void DeregisterEvents()
{ {
Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested; Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested;
ClientHost.PasswordRequested -= ClientHost_PasswordRequested;
ClientHost.ReconfigurationDenied -= ClientHost_ReconfigurationDenied;
ClientHost.Shutdown -= ClientHost_Shutdown; ClientHost.Shutdown -= ClientHost_Shutdown;
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged; displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted; processMonitor.ExplorerStarted -= ProcessMonitor_ExplorerStarted;
@ -236,6 +245,27 @@ namespace SafeExamBrowser.Client.Behaviour
} }
} }
private void ClientHost_PasswordRequested(PasswordRequestEventArgs args)
{
var isAdmin = args.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));
logger.Info($"Received input request with id '{args.RequestId}' for the {args.Purpose.ToString().ToLower()} password.");
var result = dialog.Show();
runtime.SubmitPassword(args.RequestId, result.Success, result.Password);
logger.Info($"Password request with id '{args.RequestId}' was {(result.Success ? "successful" : "aborted by the user")}.");
}
private void ClientHost_ReconfigurationDenied(ReconfigurationEventArgs args)
{
logger.Info($"The reconfiguration request for '{args.ConfigurationPath}' was denied by the runtime!");
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle);
}
private void ClientHost_Shutdown() private void ClientHost_Shutdown()
{ {
taskbar.Close(); taskbar.Close();

View file

@ -22,6 +22,8 @@ namespace SafeExamBrowser.Client.Communication
public Guid StartupToken { private get; set; } public Guid StartupToken { private get; set; }
public event CommunicationEventHandler<PasswordRequestEventArgs> PasswordRequested;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationDenied;
public event CommunicationEventHandler Shutdown; public event CommunicationEventHandler Shutdown;
public ClientHost(string address, IHostObjectFactory factory, ILogger logger, int processId) : base(address, factory, logger) public ClientHost(string address, IHostObjectFactory factory, ILogger logger, int processId) : base(address, factory, logger)
@ -49,6 +51,16 @@ namespace SafeExamBrowser.Client.Communication
protected override Response OnReceive(Message message) protected override Response OnReceive(Message message)
{ {
switch (message)
{
case PasswordRequestMessage p:
PasswordRequested?.InvokeAsync(new PasswordRequestEventArgs { Purpose = p.Purpose, RequestId = p.RequestId });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case ReconfigurationDeniedMessage r:
ReconfigurationDenied?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = r.FilePath });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage); return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
} }

View file

@ -103,7 +103,7 @@ namespace SafeExamBrowser.Client
var sequence = new OperationSequence(logger, operations); var sequence = new OperationSequence(logger, operations);
ClientController = new ClientController(displayMonitor, logger, messageBox, sequence, processMonitor, runtimeProxy, shutdown, Taskbar, uiFactory, windowMonitor); ClientController = new ClientController(displayMonitor, logger, messageBox, sequence, processMonitor, runtimeProxy, shutdown, Taskbar, text, uiFactory, windowMonitor);
} }
internal void LogStartupInformation() internal void LogStartupInformation()

View file

@ -56,6 +56,9 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit> <Prefer32Bit>true</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<ApplicationIcon>SafeExamBrowser.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@ -147,6 +150,9 @@
<Name>SafeExamBrowser.WindowsApi</Name> <Name>SafeExamBrowser.WindowsApi</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Resource Include="SafeExamBrowser.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>robocopy "$(SolutionDir)SafeExamBrowser.Browser\bin\$(PlatformName)\$(ConfigurationName)" "$(ProjectDir)bin\$(PlatformName)\$(ConfigurationName)" /e /np <PostBuildEvent>robocopy "$(SolutionDir)SafeExamBrowser.Browser\bin\$(PlatformName)\$(ConfigurationName)" "$(ProjectDir)bin\$(PlatformName)\$(ConfigurationName)" /e /np

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View file

@ -31,7 +31,7 @@ namespace SafeExamBrowser.Contracts.Communication.Data
/// </summary> /// </summary>
public bool Success { get; private set; } public bool Success { get; private set; }
public PasswordReplyMessage(string password, Guid requestId, bool success) public PasswordReplyMessage(Guid requestId, bool success, string password = null)
{ {
Password = password; Password = password;
RequestId = requestId; RequestId = requestId;

View file

@ -11,14 +11,19 @@ using System;
namespace SafeExamBrowser.Contracts.Communication.Data namespace SafeExamBrowser.Contracts.Communication.Data
{ {
/// <summary> /// <summary>
/// The response to a <see cref="ReconfigurationMessage"/>. /// This message is transmitted from the runtime to the client in order to inform the latter that a reconfiguration request was denied.
/// </summary> /// </summary>
[Serializable] [Serializable]
public class ReconfigurationResponse : Response public class ReconfigurationDeniedMessage : Message
{ {
/// <summary> /// <summary>
/// Indicates whether the reconfiguration request has been accepted. /// The full path to the configuration file for which a reconfiguration was denied.
/// </summary> /// </summary>
public bool Accepted { get; set; } public string FilePath { get; private set; }
public ReconfigurationDeniedMessage(string filePath)
{
FilePath = filePath;
}
} }
} }

View file

@ -13,7 +13,7 @@ namespace SafeExamBrowser.Contracts.Communication.Events
/// <summary> /// <summary>
/// The event arguments used for the password input event fired by the <see cref="Hosts.IRuntimeHost"/>. /// The event arguments used for the password input event fired by the <see cref="Hosts.IRuntimeHost"/>.
/// </summary> /// </summary>
public class PasswordEventArgs : CommunicationEventArgs public class PasswordReplyEventArgs : CommunicationEventArgs
{ {
/// <summary> /// <summary>
/// The password entered by the user, or <c>null</c> if not available. /// The password entered by the user, or <c>null</c> if not available.

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using SafeExamBrowser.Contracts.Communication.Data;
namespace SafeExamBrowser.Contracts.Communication.Events
{
/// <summary>
/// The event arguments used for the password request event fired by the <see cref="Hosts.IClientHost"/>.
/// </summary>
public class PasswordRequestEventArgs : CommunicationEventArgs
{
/// <summary>
/// The purpose for which a password is requested.
/// </summary>
public PasswordRequestPurpose Purpose { get; set; }
/// <summary>
/// Identifies the password request.
/// </summary>
public Guid RequestId { get; set; }
}
}

View file

@ -21,6 +21,16 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// </summary> /// </summary>
Guid StartupToken { set; } Guid StartupToken { set; }
/// <summary>
/// Event fired when the runtime requests a password input from the user.
/// </summary>
event CommunicationEventHandler<PasswordRequestEventArgs> PasswordRequested;
/// <summary>
/// Event fired when the runtime denied a reconfiguration request.
/// </summary>
event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationDenied;
/// <summary> /// <summary>
/// Event fired when the runtime commands the client to shutdown. /// Event fired when the runtime commands the client to shutdown.
/// </summary> /// </summary>

View file

@ -32,9 +32,9 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
event CommunicationEventHandler ClientReady; event CommunicationEventHandler ClientReady;
/// <summary> /// <summary>
/// Event fired when the client transmitted a password entered by the user. /// Event fired when the client submitted a password entered by the user.
/// </summary> /// </summary>
event CommunicationEventHandler<PasswordEventArgs> PasswordReceived; event CommunicationEventHandler<PasswordReplyEventArgs> 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.

View file

@ -20,8 +20,10 @@ namespace SafeExamBrowser.Contracts.Communication
[ServiceKnownType(typeof(AuthenticationResponse))] [ServiceKnownType(typeof(AuthenticationResponse))]
[ServiceKnownType(typeof(ClientConfiguration))] [ServiceKnownType(typeof(ClientConfiguration))]
[ServiceKnownType(typeof(ConfigurationResponse))] [ServiceKnownType(typeof(ConfigurationResponse))]
[ServiceKnownType(typeof(PasswordReplyMessage))]
[ServiceKnownType(typeof(PasswordRequestMessage))]
[ServiceKnownType(typeof(ReconfigurationMessage))] [ServiceKnownType(typeof(ReconfigurationMessage))]
[ServiceKnownType(typeof(ReconfigurationResponse))] [ServiceKnownType(typeof(ReconfigurationDeniedMessage))]
[ServiceKnownType(typeof(SimpleMessage))] [ServiceKnownType(typeof(SimpleMessage))]
[ServiceKnownType(typeof(SimpleResponse))] [ServiceKnownType(typeof(SimpleResponse))]
public interface ICommunication public interface ICommunication

View file

@ -16,6 +16,11 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// </summary> /// </summary>
public interface IClientProxy : ICommunicationProxy public interface IClientProxy : ICommunicationProxy
{ {
/// <summary>
/// Informs the client that the reconfiguration request for the specified file was denied.
/// </summary>
void InformReconfigurationDenied(string filePath);
/// <summary> /// <summary>
/// Instructs the client to initiate its shutdown procedure. /// Instructs the client to initiate its shutdown procedure.
/// </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.Configuration; using SafeExamBrowser.Contracts.Configuration;
namespace SafeExamBrowser.Contracts.Communication.Proxies namespace SafeExamBrowser.Contracts.Communication.Proxies
@ -38,5 +39,12 @@ 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>
void RequestReconfiguration(string filePath); void RequestReconfiguration(string filePath);
/// <summary>
/// Submits the result of a password input previously requested by the runtime. If the procedure was aborted by the user,
/// the password parameter will be <c>null</c>!
/// </summary>
/// /// <exception cref="System.ServiceModel.*">If the communication failed.</exception>
void SubmitPassword(Guid requestId, bool success, string password = null);
} }
} }

View file

@ -46,6 +46,8 @@ namespace SafeExamBrowser.Contracts.I18n
Notification_LogTooltip, Notification_LogTooltip,
PasswordDialog_AdminPasswordRequired, PasswordDialog_AdminPasswordRequired,
PasswordDialog_AdminPasswordRequiredTitle, PasswordDialog_AdminPasswordRequiredTitle,
PasswordDialog_Cancel,
PasswordDialog_Confirm,
PasswordDialog_SettingsPasswordRequired, PasswordDialog_SettingsPasswordRequired,
PasswordDialog_SettingsPasswordRequiredTitle, PasswordDialog_SettingsPasswordRequiredTitle,
ProgressIndicator_CloseRuntimeConnection, ProgressIndicator_CloseRuntimeConnection,

View file

@ -63,9 +63,11 @@
<Compile Include="Communication\Data\PasswordRequestMessage.cs" /> <Compile Include="Communication\Data\PasswordRequestMessage.cs" />
<Compile Include="Communication\Data\PasswordRequestPurpose.cs" /> <Compile Include="Communication\Data\PasswordRequestPurpose.cs" />
<Compile Include="Communication\Data\PasswordReplyMessage.cs" /> <Compile Include="Communication\Data\PasswordReplyMessage.cs" />
<Compile Include="Communication\Data\ReconfigurationDeniedMessage.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\PasswordReplyEventArgs.cs" />
<Compile Include="Communication\Events\PasswordRequestEventArgs.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" />
@ -89,7 +91,6 @@
<Compile Include="Communication\Data\ConfigurationResponse.cs" /> <Compile Include="Communication\Data\ConfigurationResponse.cs" />
<Compile Include="Communication\Data\ConnectionResponse.cs" /> <Compile Include="Communication\Data\ConnectionResponse.cs" />
<Compile Include="Communication\Data\DisconnectionResponse.cs" /> <Compile Include="Communication\Data\DisconnectionResponse.cs" />
<Compile Include="Communication\Data\ReconfigurationResponse.cs" />
<Compile Include="Communication\Data\Response.cs" /> <Compile Include="Communication\Data\Response.cs" />
<Compile Include="Communication\Data\SimpleResponsePurport.cs" /> <Compile Include="Communication\Data\SimpleResponsePurport.cs" />
<Compile Include="Communication\Data\SimpleResponse.cs" /> <Compile Include="Communication\Data\SimpleResponse.cs" />

View file

@ -257,9 +257,9 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts
var received = false; var received = false;
var simpleReceived = false; var simpleReceived = false;
var message = new ReconfigurationMessage(null); var message = new ReconfigurationMessage(null);
var reconfigurationResponse = new ReconfigurationResponse(); var configurationResponse = new ConfigurationResponse();
sut.OnReceiveStub = (m) => { received = true; return reconfigurationResponse; }; sut.OnReceiveStub = (m) => { received = true; return configurationResponse; };
sut.OnReceiveSimpleMessageStub = (m) => { simpleReceived = true; return null; }; sut.OnReceiveSimpleMessageStub = (m) => { simpleReceived = true; return null; };
sut.OnConnectStub = (t) => { return true; }; sut.OnConnectStub = (t) => { return true; };
sut.Connect(); sut.Connect();
@ -270,8 +270,8 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Hosts
Assert.IsTrue(received); Assert.IsTrue(received);
Assert.IsFalse(simpleReceived); Assert.IsFalse(simpleReceived);
Assert.IsInstanceOfType(response, typeof(ReconfigurationResponse)); Assert.IsInstanceOfType(response, typeof(ConfigurationResponse));
Assert.AreSame(reconfigurationResponse, response); Assert.AreSame(configurationResponse, response);
} }
} }
} }

View file

@ -10,7 +10,6 @@ using System;
using System.ServiceModel; using System.ServiceModel;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Contracts.Communication;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
@ -96,56 +95,24 @@ namespace SafeExamBrowser.Core.UnitTests.Communication.Proxies
[TestMethod] [TestMethod]
public void MustCorrectlyRequestReconfiguration() public void MustCorrectlyRequestReconfiguration()
{ {
//var url = "sebs://some/url.seb"; var url = "file:///C:/Some/file/url.seb";
//var response = new ReconfigurationResponse
//{
// Accepted = true
//};
//proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url))).Returns(response); proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationPath == url))).Returns(new SimpleResponse(SimpleResponsePurport.Acknowledged));
//var accepted = sut.RequestReconfiguration(url); sut.RequestReconfiguration(url);
//proxy.Verify(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url)), Times.Once); proxy.Verify(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationPath == url)), Times.Once);
//Assert.IsTrue(accepted);
// TODO
Assert.Fail();
} }
[TestMethod] [TestMethod]
public void MustCorrectlyHandleDeniedReconfigurationRequest() [ExpectedException(typeof(CommunicationException))]
public void MustFailIfReconfigurationRequestNotAcknowledged()
{ {
//var url = "sebs://some/url.seb"; var url = "file:///C:/Some/file/url.seb";
//var response = new ReconfigurationResponse
//{
// Accepted = false
//};
//proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url))).Returns(response); proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationPath == url))).Returns<Response>(null);
//var accepted = sut.RequestReconfiguration(url); sut.RequestReconfiguration(url);
//Assert.IsFalse(accepted);
// TODO
Assert.Fail();
}
[TestMethod]
public void MustNotFailIfIncorrectResponseToReconfigurationRequest()
{
//var url = "sebs://some/url.seb";
//proxy.Setup(p => p.Send(It.Is<ReconfigurationMessage>(m => m.ConfigurationUrl == url))).Returns<Response>(null);
//var accepted = sut.RequestReconfiguration(url);
//Assert.IsFalse(accepted);
// TODO
Assert.Fail();
} }
[TestMethod] [TestMethod]

View file

@ -23,13 +23,23 @@ namespace SafeExamBrowser.Core.Communication.Proxies
{ {
} }
public void InformReconfigurationDenied(string filePath)
{
var response = Send(new ReconfigurationDeniedMessage(filePath));
if (!IsAcknowledged(response))
{
throw new CommunicationException($"Client did not acknowledge shutdown request! Received: {ToString(response)}.");
}
}
public void InitiateShutdown() public void InitiateShutdown()
{ {
var response = Send(SimpleMessagePurport.Shutdown); var response = Send(SimpleMessagePurport.Shutdown);
if (!IsAcknowledged(response)) if (!IsAcknowledged(response))
{ {
throw new CommunicationException($"Runtime did not acknowledge shutdown request! Received: {ToString(response)}."); throw new CommunicationException($"Client did not acknowledge shutdown request! Received: {ToString(response)}.");
} }
} }
@ -47,8 +57,12 @@ namespace SafeExamBrowser.Core.Communication.Proxies
public void RequestPassword(PasswordRequestPurpose purpose, Guid requestId) public void RequestPassword(PasswordRequestPurpose purpose, Guid requestId)
{ {
// TODO var response = Send(new PasswordRequestMessage(purpose, requestId));
throw new NotImplementedException();
if (!IsAcknowledged(response))
{
throw new CommunicationException($"Client did not acknowledge shutdown request! Received: {ToString(response)}.");
}
} }
} }
} }

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;
@ -64,5 +65,15 @@ namespace SafeExamBrowser.Core.Communication.Proxies
throw new CommunicationException($"Runtime did not acknowledge shutdown request! Response: {ToString(response)}."); throw new CommunicationException($"Runtime did not acknowledge shutdown request! Response: {ToString(response)}.");
} }
} }
public void SubmitPassword(Guid requestId, bool success, string password = null)
{
var response = Send(new PasswordReplyMessage(requestId, success, password));
if (!IsAcknowledged(response))
{
throw new CommunicationException($"Runtime did not acknowledge password submission! Response: {ToString(response)}.");
}
}
} }
} }

View file

@ -90,6 +90,12 @@
<Entry key="PasswordDialog_AdminPasswordRequiredTitle"> <Entry key="PasswordDialog_AdminPasswordRequiredTitle">
Administrator Password Required Administrator Password Required
</Entry> </Entry>
<Entry key="PasswordDialog_Cancel">
Cancel
</Entry>
<Entry key="PasswordDialog_Confirm">
Confirm
</Entry>
<Entry key="PasswordDialog_SettingsPasswordRequired"> <Entry key="PasswordDialog_SettingsPasswordRequired">
Please enter the settings password for the application configuration: Please enter the settings password for the application configuration:
</Entry> </Entry>

View file

@ -75,9 +75,9 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
} }
[TestMethod] [TestMethod]
public void MustStartClientOnRepeat() public void TODO()
{ {
// TODO: Extract static fields from operation -> allows unit testing of this requirement etc.! // TODO: MustStartClientOnRepeat -> Extract static fields from operation -> allows unit testing of this requirement etc.!
Assert.Fail(); Assert.Fail();
} }
} }

View file

@ -337,7 +337,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
var clientProxy = new Mock<IClientProxy>(); var clientProxy = new Mock<IClientProxy>();
var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) => var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
{ {
runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordEventArgs { RequestId = id, Success = true }); runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = true });
}); });
var session = new Mock<ISessionData>(); var session = new Mock<ISessionData>();
var url = @"http://www.safeexambrowser.org/whatever.seb"; var url = @"http://www.safeexambrowser.org/whatever.seb";
@ -363,7 +363,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Behaviour.Operations
var clientProxy = new Mock<IClientProxy>(); var clientProxy = new Mock<IClientProxy>();
var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) => var passwordReceived = new Action<PasswordRequestPurpose, Guid>((p, id) =>
{ {
runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordEventArgs { RequestId = id, Success = false }); runtimeHost.Raise(r => r.PasswordReceived += null, new PasswordReplyEventArgs { RequestId = id, Success = false });
}); });
var session = new Mock<ISessionData>(); var session = new Mock<ISessionData>();
var url = @"http://www.safeexambrowser.org/whatever.seb"; var url = @"http://www.safeexambrowser.org/whatever.seb";

View file

@ -188,9 +188,9 @@ namespace SafeExamBrowser.Runtime.Behaviour.Operations
private bool TryGetPasswordViaClient(PasswordRequestPurpose purpose, out string password) private bool TryGetPasswordViaClient(PasswordRequestPurpose purpose, out string password)
{ {
var requestId = Guid.NewGuid(); var requestId = Guid.NewGuid();
var response = default(PasswordEventArgs); var response = default(PasswordReplyEventArgs);
var responseEvent = new AutoResetEvent(false); var responseEvent = new AutoResetEvent(false);
var responseEventHandler = new CommunicationEventHandler<PasswordEventArgs>((args) => var responseEventHandler = new CommunicationEventHandler<PasswordReplyEventArgs>((args) =>
{ {
if (args.RequestId == requestId) if (args.RequestId == requestId)
{ {

View file

@ -261,7 +261,7 @@ namespace SafeExamBrowser.Runtime.Behaviour
else else
{ {
logger.Info($"Denied request for reconfiguration with '{args.ConfigurationPath}' due to '{mode}' mode!"); logger.Info($"Denied request for reconfiguration with '{args.ConfigurationPath}' due to '{mode}' mode!");
// TODO: configuration.CurrentSession.ClientProxy.InformReconfigurationDenied(); configuration.CurrentSession.ClientProxy.InformReconfigurationDenied(args.ConfigurationPath);
} }
} }

View file

@ -25,7 +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<PasswordReplyEventArgs> PasswordReceived;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested; public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
public event CommunicationEventHandler ShutdownRequested; public event CommunicationEventHandler ShutdownRequested;
@ -64,7 +64,7 @@ namespace SafeExamBrowser.Runtime.Communication
switch (message) switch (message)
{ {
case PasswordReplyMessage r: case PasswordReplyMessage r:
PasswordReceived?.InvokeAsync(new PasswordEventArgs { Password = r.Password, RequestId = r.RequestId, Success = r.Success }); PasswordReceived?.InvokeAsync(new PasswordReplyEventArgs { Password = r.Password, RequestId = r.RequestId, Success = r.Success });
return new SimpleResponse(SimpleResponsePurport.Acknowledged); 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 });

View file

@ -0,0 +1,35 @@
<Window x:Class="SafeExamBrowser.UserInterface.Classic.PasswordDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Classic"
mc:Ignorable="d" Height="200" Width="450" ResizeMode="NoResize" Topmost="True">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="./Templates/Colors.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="4*" />
<RowDefinition Height="2*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="25,0,25,25">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="Message" Margin="0,0,0,10" TextWrapping="WrapWithOverflow" VerticalAlignment="Bottom" />
<PasswordBox Grid.Row="1" x:Name="Password" Height="25" VerticalContentAlignment="Center" />
</Grid>
<Grid Grid.Row="1" Background="{StaticResource BackgroundBrush}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<WrapPanel Orientation="Horizontal" Margin="25,0" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button x:Name="ConfirmButton" Cursor="Hand" Margin="10,0" Padding="10,5" MinWidth="75" />
<Button x:Name="CancelButton" Cursor="Hand" Padding="10,5" MinWidth="75" />
</WrapPanel>
</Grid>
</Grid>
</Window>

View file

@ -0,0 +1,94 @@
/*
* 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.Windows;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.UserInterface.Windows;
namespace SafeExamBrowser.UserInterface.Classic
{
public partial class PasswordDialog : Window, IPasswordDialog
{
private IText text;
private WindowClosingEventHandler closing;
event WindowClosingEventHandler IWindow.Closing
{
add { closing += value; }
remove { closing -= value; }
}
public PasswordDialog(string message, string title, IText text)
{
this.text = text;
InitializeComponent();
InitializePasswordDialog(message, title);
}
public void BringToForeground()
{
Dispatcher.Invoke(Activate);
}
public IPasswordDialogResult Show(IWindow parent = null)
{
return Dispatcher.Invoke(() =>
{
var result = new PasswordDialogResult { Success = false };
if (parent is Window)
{
Owner = parent as Window;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
}
if (ShowDialog() is true)
{
result.Password = Password.Password;
result.Success = true;
}
return result;
});
}
private void InitializePasswordDialog(string message, string title)
{
Message.Text = message;
Title = title;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
CancelButton.Content = text.Get(TextKey.PasswordDialog_Cancel);
CancelButton.Click += CancelButton_Click;
ConfirmButton.Content = text.Get(TextKey.PasswordDialog_Confirm);
ConfirmButton.Click += ConfirmButton_Click;
Closing += (o, args) => closing?.Invoke();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private class PasswordDialogResult : IPasswordDialogResult
{
public string Password { get; set; }
public bool Success { get; set; }
}
}
}

View file

@ -105,6 +105,9 @@
<DependentUpon>LogWindow.xaml</DependentUpon> <DependentUpon>LogWindow.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="MessageBox.cs" /> <Compile Include="MessageBox.cs" />
<Compile Include="PasswordDialog.xaml.cs">
<DependentUpon>PasswordDialog.xaml</DependentUpon>
</Compile>
<Compile Include="RuntimeWindow.xaml.cs"> <Compile Include="RuntimeWindow.xaml.cs">
<DependentUpon>RuntimeWindow.xaml</DependentUpon> <DependentUpon>RuntimeWindow.xaml</DependentUpon>
</Compile> </Compile>
@ -224,6 +227,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="PasswordDialog.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="RuntimeWindow.xaml"> <Page Include="RuntimeWindow.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View file

@ -7,6 +7,7 @@
*/ */
using System.Threading; using System.Threading;
using System.Windows;
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;
@ -81,7 +82,7 @@ namespace SafeExamBrowser.UserInterface.Classic
public IPasswordDialog CreatePasswordDialog(string message, string title) public IPasswordDialog CreatePasswordDialog(string message, string title)
{ {
throw new System.NotImplementedException(); return Application.Current.Dispatcher.Invoke(() => new PasswordDialog(message, title, text));
} }
public ISystemPowerSupplyControl CreatePowerSupplyControl() public ISystemPowerSupplyControl CreatePowerSupplyControl()