SEBWIN-221: Implemented message box communication infrastructure and removed generic session start / stop error messages.

This commit is contained in:
dbuechel 2018-12-14 12:31:31 +01:00
parent 75f5994a3b
commit 4b634d8e99
19 changed files with 305 additions and 28 deletions

View file

@ -160,6 +160,7 @@ namespace SafeExamBrowser.Client
private void RegisterEvents() private void RegisterEvents()
{ {
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested; Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested; ClientHost.PasswordRequested += ClientHost_PasswordRequested;
ClientHost.ReconfigurationDenied += ClientHost_ReconfigurationDenied; ClientHost.ReconfigurationDenied += ClientHost_ReconfigurationDenied;
ClientHost.Shutdown += ClientHost_Shutdown; ClientHost.Shutdown += ClientHost_Shutdown;
@ -173,6 +174,7 @@ namespace SafeExamBrowser.Client
private void DeregisterEvents() private void DeregisterEvents()
{ {
Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested; Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested;
ClientHost.MessageBoxRequested -= ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested -= ClientHost_PasswordRequested; ClientHost.PasswordRequested -= ClientHost_PasswordRequested;
ClientHost.ReconfigurationDenied -= ClientHost_ReconfigurationDenied; ClientHost.ReconfigurationDenied -= ClientHost_ReconfigurationDenied;
ClientHost.Shutdown -= ClientHost_Shutdown; ClientHost.Shutdown -= ClientHost_Shutdown;
@ -259,6 +261,16 @@ namespace SafeExamBrowser.Client
} }
} }
private void ClientHost_MessageBoxRequested(MessageBoxRequestEventArgs args)
{
logger.Info($"Received message box request with id '{args.RequestId}'.");
var result = messageBox.Show(args.Message, args.Title, args.Action, args.Icon);
runtime.SubmitMessageBoxResult(args.RequestId, result);
logger.Info($"Message box request with id '{args.RequestId}' yielded result '{result}'.");
}
private void ClientHost_PasswordRequested(PasswordRequestEventArgs args) private void ClientHost_PasswordRequested(PasswordRequestEventArgs args)
{ {
var isAdmin = args.Purpose == PasswordRequestPurpose.Administrator; var isAdmin = args.Purpose == PasswordRequestPurpose.Administrator;

View file

@ -23,6 +23,7 @@ namespace SafeExamBrowser.Client.Communication
public Guid StartupToken { private get; set; } public Guid StartupToken { private get; set; }
public bool IsConnected { get; private set; } public bool IsConnected { get; private set; }
public event CommunicationEventHandler<MessageBoxRequestEventArgs> MessageBoxRequested;
public event CommunicationEventHandler<PasswordRequestEventArgs> PasswordRequested; public event CommunicationEventHandler<PasswordRequestEventArgs> PasswordRequested;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationDenied; public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationDenied;
public event CommunicationEventHandler RuntimeDisconnected; public event CommunicationEventHandler RuntimeDisconnected;
@ -62,11 +63,14 @@ namespace SafeExamBrowser.Client.Communication
{ {
switch (message) switch (message)
{ {
case PasswordRequestMessage p: case MessageBoxRequestMessage m:
PasswordRequested?.InvokeAsync(new PasswordRequestEventArgs { Purpose = p.Purpose, RequestId = p.RequestId }); MessageBoxRequested?.InvokeAsync(new MessageBoxRequestEventArgs { Action = m.Action, Icon = m.Icon, Message = m.Message, RequestId = m.RequestId, Title = m.Title });
return new SimpleResponse(SimpleResponsePurport.Acknowledged); return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case ReconfigurationDeniedMessage r: case PasswordRequestMessage m:
ReconfigurationDenied?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = r.FilePath }); PasswordRequested?.InvokeAsync(new PasswordRequestEventArgs { Purpose = m.Purpose, RequestId = m.RequestId });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case ReconfigurationDeniedMessage m:
ReconfigurationDenied?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = m.FilePath });
return new SimpleResponse(SimpleResponsePurport.Acknowledged); return new SimpleResponse(SimpleResponsePurport.Acknowledged);
} }

View file

@ -10,6 +10,7 @@ using System;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
namespace SafeExamBrowser.Communication.Proxies namespace SafeExamBrowser.Communication.Proxies
{ {
@ -125,5 +126,31 @@ namespace SafeExamBrowser.Communication.Proxies
return new CommunicationResult(false); return new CommunicationResult(false);
} }
} }
public CommunicationResult ShowMessage(string message, string title, MessageBoxAction action, MessageBoxIcon icon, Guid requestId)
{
try
{
var response = Send(new MessageBoxRequestMessage(action, icon, message, requestId, title));
var success = IsAcknowledged(response);
if (success)
{
Logger.Debug("Client acknowledged message box request.");
}
else
{
Logger.Error($"Client did not acknowledge message box request! Received: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(ShowMessage)}'", e);
return new CommunicationResult(false);
}
}
} }
} }

View file

@ -10,6 +10,7 @@ using System;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Communication.Proxies; using SafeExamBrowser.Contracts.Communication.Proxies;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
namespace SafeExamBrowser.Communication.Proxies namespace SafeExamBrowser.Communication.Proxies
{ {
@ -126,6 +127,32 @@ namespace SafeExamBrowser.Communication.Proxies
} }
} }
public CommunicationResult SubmitMessageBoxResult(Guid requestId, MessageBoxResult result)
{
try
{
var response = Send(new MessageBoxReplyMessage(requestId, result));
var acknowledged = IsAcknowledged(response);
if (acknowledged)
{
Logger.Debug("Runtime acknowledged message box result transmission.");
}
else
{
Logger.Error($"Runtime did not acknowledge message box result transmission! Response: {ToString(response)}.");
}
return new CommunicationResult(acknowledged);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(SubmitMessageBoxResult)}'", e);
return new CommunicationResult(false);
}
}
public CommunicationResult SubmitPassword(Guid requestId, bool success, string password = null) public CommunicationResult SubmitPassword(Guid requestId, bool success, string password = null)
{ {
try try

View file

@ -0,0 +1,36 @@
/*
* 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.UserInterface.MessageBox;
namespace SafeExamBrowser.Contracts.Communication.Data
{
/// <summary>
/// The reply to a <see cref="MessageBoxRequestMessage"/>.
/// </summary>
[Serializable]
public class MessageBoxReplyMessage : Message
{
/// <summary>
/// Identifies the message box request.
/// </summary>
public Guid RequestId { get; private set; }
/// <summary>
/// The result of the interaction.
/// </summary>
public MessageBoxResult Result { get; private set; }
public MessageBoxReplyMessage(Guid requestId, MessageBoxResult result)
{
RequestId = requestId;
Result = result;
}
}
}

View file

@ -0,0 +1,54 @@
/*
* 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.UserInterface.MessageBox;
namespace SafeExamBrowser.Contracts.Communication.Data
{
/// <summary>
/// This message is transmitted to the client to request a message box input by the user.
/// </summary>
[Serializable]
public class MessageBoxRequestMessage : Message
{
/// <summary>
/// The action to be displayed.
/// </summary>
public MessageBoxAction Action { get; private set; }
/// <summary>
/// The icon to be displayed.
/// </summary>
public MessageBoxIcon Icon { get; private set; }
/// <summary>
/// The message to be displayed.
/// </summary>
public string Message { get; private set; }
/// <summary>
/// Identifies the message box request.
/// </summary>
public Guid RequestId { get; private set; }
/// <summary>
/// The title to be displayed.
/// </summary>
public string Title { get; private set; }
public MessageBoxRequestMessage(MessageBoxAction action, MessageBoxIcon icon, string message, Guid requestId, string title)
{
Action = action;
Icon = icon;
Message = message;
RequestId = requestId;
Title = title;
}
}
}

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.UserInterface.MessageBox;
namespace SafeExamBrowser.Contracts.Communication.Events
{
/// <summary>
/// The event arguments used for the message box reply event fired by the <see cref="Hosts.IRuntimeHost"/>.
/// </summary>
public class MessageBoxReplyEventArgs : CommunicationEventArgs
{
/// <summary>
/// Identifies the message box request.
/// </summary>
public Guid RequestId { get; set; }
/// <summary>
/// The result of the interaction.
/// </summary>
public MessageBoxResult Result { get; set; }
}
}

View file

@ -0,0 +1,44 @@
/*
* 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.UserInterface.MessageBox;
namespace SafeExamBrowser.Contracts.Communication.Events
{
/// <summary>
/// The event arguments used for the message box request event fired by the <see cref="Hosts.IClientHost"/>.
/// </summary>
public class MessageBoxRequestEventArgs : CommunicationEventArgs
{
/// <summary>
/// The action to be displayed.
/// </summary>
public MessageBoxAction Action { get; set; }
/// <summary>
/// The icon to be displayed.
/// </summary>
public MessageBoxIcon Icon { get; set; }
/// <summary>
/// The message to be displayed.
/// </summary>
public string Message { get; set; }
/// <summary>
/// Identifies the message box request.
/// </summary>
public Guid RequestId { get; set; }
/// <summary>
/// The title to be displayed.
/// </summary>
public string Title { get; set; }
}
}

View file

@ -26,6 +26,11 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// </summary> /// </summary>
Guid StartupToken { set; } Guid StartupToken { set; }
/// <summary>
/// Event fired when the runtime requests a message box input from the user.
/// </summary>
event CommunicationEventHandler<MessageBoxRequestEventArgs> MessageBoxRequested;
/// <summary> /// <summary>
/// Event fired when the runtime requests a password input from the user. /// Event fired when the runtime requests a password input from the user.
/// </summary> /// </summary>

View file

@ -41,6 +41,11 @@ namespace SafeExamBrowser.Contracts.Communication.Hosts
/// </summary> /// </summary>
event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded; event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded;
/// <summary>
/// Event fired when the client submitted a message box result chosen by the user.
/// </summary>
event CommunicationEventHandler<MessageBoxReplyEventArgs> MessageBoxReplyReceived;
/// <summary> /// <summary>
/// Event fired when the client submitted a password entered by the user. /// Event fired when the client submitted a password entered by the user.
/// </summary> /// </summary>

View file

@ -20,6 +20,8 @@ namespace SafeExamBrowser.Contracts.Communication
[ServiceKnownType(typeof(AuthenticationResponse))] [ServiceKnownType(typeof(AuthenticationResponse))]
[ServiceKnownType(typeof(ClientConfiguration))] [ServiceKnownType(typeof(ClientConfiguration))]
[ServiceKnownType(typeof(ConfigurationResponse))] [ServiceKnownType(typeof(ConfigurationResponse))]
[ServiceKnownType(typeof(MessageBoxReplyMessage))]
[ServiceKnownType(typeof(MessageBoxRequestMessage))]
[ServiceKnownType(typeof(PasswordReplyMessage))] [ServiceKnownType(typeof(PasswordReplyMessage))]
[ServiceKnownType(typeof(PasswordRequestMessage))] [ServiceKnownType(typeof(PasswordRequestMessage))]
[ServiceKnownType(typeof(ReconfigurationMessage))] [ServiceKnownType(typeof(ReconfigurationMessage))]

View file

@ -8,6 +8,7 @@
using System; using System;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
namespace SafeExamBrowser.Contracts.Communication.Proxies namespace SafeExamBrowser.Contracts.Communication.Proxies
{ {
@ -35,5 +36,10 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// Requests the client to render a password dialog and subsequently return the interaction result as separate message. /// Requests the client to render a password dialog and subsequently return the interaction result as separate message.
/// </summary> /// </summary>
CommunicationResult RequestPassword(PasswordRequestPurpose purpose, Guid requestId); CommunicationResult RequestPassword(PasswordRequestPurpose purpose, Guid requestId);
/// <summary>
/// Requests the client to render a message box and subsequently return the interaction result as separate message.
/// </summary>
CommunicationResult ShowMessage(string message, string title, MessageBoxAction action, MessageBoxIcon icon, Guid requestId);
} }
} }

View file

@ -8,6 +8,7 @@
using System; using System;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.UserInterface.MessageBox;
namespace SafeExamBrowser.Contracts.Communication.Proxies namespace SafeExamBrowser.Contracts.Communication.Proxies
{ {
@ -36,6 +37,11 @@ namespace SafeExamBrowser.Contracts.Communication.Proxies
/// </summary> /// </summary>
CommunicationResult RequestReconfiguration(string filePath); CommunicationResult RequestReconfiguration(string filePath);
/// <summary>
/// Submits the result of a message box input previously requested by the runtime.
/// </summary>
CommunicationResult SubmitMessageBoxResult(Guid requestId, MessageBoxResult result);
/// <summary> /// <summary>
/// Submits the result of a password input previously requested by the runtime. If the procedure was aborted by the user, /// 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>! /// the password parameter will be <c>null</c>!

View file

@ -38,10 +38,6 @@ namespace SafeExamBrowser.Contracts.I18n
MessageBox_ReconfigurationErrorTitle, MessageBox_ReconfigurationErrorTitle,
MessageBox_ReconfigurationQuestion, MessageBox_ReconfigurationQuestion,
MessageBox_ReconfigurationQuestionTitle, MessageBox_ReconfigurationQuestionTitle,
MessageBox_SessionStartError,
MessageBox_SessionStartErrorTitle,
MessageBox_SessionStopError,
MessageBox_SessionStopErrorTitle,
MessageBox_ShutdownError, MessageBox_ShutdownError,
MessageBox_ShutdownErrorTitle, MessageBox_ShutdownErrorTitle,
MessageBox_SingleInstance, MessageBox_SingleInstance,

View file

@ -52,7 +52,11 @@
<Reference Include="System.ServiceModel" /> <Reference Include="System.ServiceModel" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Communication\Data\MessageBoxReplyMessage.cs" />
<Compile Include="Communication\Data\MessageBoxRequestMessage.cs" />
<Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" /> <Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" />
<Compile Include="Communication\Events\MessageBoxReplyEventArgs.cs" />
<Compile Include="Communication\Events\MessageBoxRequestEventArgs.cs" />
<Compile Include="Configuration\Cryptography\EncryptionParameters.cs" /> <Compile Include="Configuration\Cryptography\EncryptionParameters.cs" />
<Compile Include="Configuration\Cryptography\PasswordParameters.cs" /> <Compile Include="Configuration\Cryptography\PasswordParameters.cs" />
<Compile Include="Configuration\Cryptography\PublicKeyHashParameters.cs" /> <Compile Include="Configuration\Cryptography\PublicKeyHashParameters.cs" />

View file

@ -72,18 +72,6 @@
<Entry key="MessageBox_ReconfigurationQuestionTitle"> <Entry key="MessageBox_ReconfigurationQuestionTitle">
Configuration Detected Configuration Detected
</Entry> </Entry>
<Entry key="MessageBox_SessionStartError">
The application failed to start a new session. Please consult the application log for more information...
</Entry>
<Entry key="MessageBox_SessionStartErrorTitle">
Session Start Error
</Entry>
<Entry key="MessageBox_SessionStopError">
The application failed to properly stop the running session. Please consult the application log for more information...
</Entry>
<Entry key="MessageBox_SessionStopErrorTitle">
Session Stop Error
</Entry>
<Entry key="MessageBox_ShutdownError"> <Entry key="MessageBox_ShutdownError">
An unexpected error occurred during the shutdown procedure! Please consult the application log for more information... An unexpected error occurred during the shutdown procedure! Please consult the application log for more information...
</Entry> </Entry>

View file

@ -23,6 +23,7 @@ namespace SafeExamBrowser.Runtime.Communication
public event CommunicationEventHandler ClientDisconnected; public event CommunicationEventHandler ClientDisconnected;
public event CommunicationEventHandler ClientReady; public event CommunicationEventHandler ClientReady;
public event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded; public event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded;
public event CommunicationEventHandler<MessageBoxReplyEventArgs> MessageBoxReplyReceived;
public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived; public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested; public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
public event CommunicationEventHandler ShutdownRequested; public event CommunicationEventHandler ShutdownRequested;
@ -53,11 +54,14 @@ namespace SafeExamBrowser.Runtime.Communication
{ {
switch (message) switch (message)
{ {
case PasswordReplyMessage r: case MessageBoxReplyMessage m:
PasswordReceived?.InvokeAsync(new PasswordReplyEventArgs { Password = r.Password, RequestId = r.RequestId, Success = r.Success }); MessageBoxReplyReceived?.InvokeAsync(new MessageBoxReplyEventArgs { RequestId = m.RequestId, Result = m.Result });
return new SimpleResponse(SimpleResponsePurport.Acknowledged); return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case ReconfigurationMessage r: case PasswordReplyMessage m:
ReconfigurationRequested?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = r.ConfigurationPath }); PasswordReceived?.InvokeAsync(new PasswordReplyEventArgs { Password = m.Password, RequestId = m.RequestId, Success = m.Success });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case ReconfigurationMessage m:
ReconfigurationRequested?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = m.ConfigurationPath });
return new SimpleResponse(SimpleResponsePurport.Acknowledged); return new SimpleResponse(SimpleResponsePurport.Acknowledged);
} }

View file

@ -273,6 +273,7 @@ namespace SafeExamBrowser.Runtime.Operations
else else
{ {
logger.Info("Authentication has failed!"); logger.Info("Authentication has failed!");
ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
return OperationResult.Failed; return OperationResult.Failed;
} }

View file

@ -207,9 +207,7 @@ namespace SafeExamBrowser.Runtime
{ {
StopSession(); StopSession();
messageBox.Show(TextKey.MessageBox_SessionStartError, TextKey.MessageBox_SessionStartErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow);
logger.Info("Terminating application..."); logger.Info("Terminating application...");
shutdown.Invoke(); shutdown.Invoke();
} }
} }
@ -247,7 +245,6 @@ namespace SafeExamBrowser.Runtime
else else
{ {
logger.Info("### --- Session Stop Failed --- ###"); logger.Info("### --- Session Stop Failed --- ###");
messageBox.Show(TextKey.MessageBox_SessionStopError, TextKey.MessageBox_SessionStopErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow);
} }
} }
@ -419,10 +416,40 @@ namespace SafeExamBrowser.Runtime
} }
else else
{ {
// TODO ShowMessageBoxViaClient(message, title, MessageBoxAction.Confirm, args.Icon);
} }
} }
private MessageBoxResult ShowMessageBoxViaClient(string message, string title, MessageBoxAction confirm, MessageBoxIcon icon)
{
var requestId = Guid.NewGuid();
var result = MessageBoxResult.None;
var response = default(MessageBoxReplyEventArgs);
var responseEvent = new AutoResetEvent(false);
var responseEventHandler = new CommunicationEventHandler<MessageBoxReplyEventArgs>((args) =>
{
if (args.RequestId == requestId)
{
response = args;
responseEvent.Set();
}
});
runtimeHost.MessageBoxReplyReceived += responseEventHandler;
var communication = sessionContext.ClientProxy.ShowMessage(message, title, MessageBoxAction.Confirm, icon, requestId);
if (communication.Success)
{
responseEvent.WaitOne();
result = response.Result;
}
runtimeHost.MessageBoxReplyReceived -= responseEventHandler;
return result;
}
private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args) private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args)
{ {
var isAdmin = args.Purpose == PasswordRequestPurpose.Administrator; var isAdmin = args.Purpose == PasswordRequestPurpose.Administrator;