SEBWIN-405: Implemented mechanism to retrieve exam selection via client.

This commit is contained in:
Damian Büchel 2020-07-31 19:57:08 +02:00
parent 7ac34b3473
commit 4d59ee399d
17 changed files with 298 additions and 20 deletions

View file

@ -28,6 +28,7 @@ using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Monitoring.Contracts.Display;
using SafeExamBrowser.Monitoring.Contracts.System;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
@ -186,6 +187,7 @@ namespace SafeExamBrowser.Client
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
Browser.SessionIdentifierDetected += Browser_SessionIdentifierDetected;
Browser.TerminationRequested += Browser_TerminationRequested;
ClientHost.ExamSelectionRequested += ClientHost_ExamSelectionRequested;
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested;
ClientHost.ReconfigurationAborted += ClientHost_ReconfigurationAborted;
@ -226,6 +228,7 @@ namespace SafeExamBrowser.Client
if (ClientHost != null)
{
ClientHost.ExamSelectionRequested -= ClientHost_ExamSelectionRequested;
ClientHost.MessageBoxRequested -= ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested -= ClientHost_PasswordRequested;
ClientHost.ReconfigurationAborted -= ClientHost_ReconfigurationAborted;
@ -393,6 +396,18 @@ namespace SafeExamBrowser.Client
}
}
private void ClientHost_ExamSelectionRequested(ExamSelectionRequestEventArgs args)
{
logger.Info($"Received exam selection request with id '{args.RequestId}'.");
var exams = args.Exams.Select(e => new Exam { Id = e.id, LmsName = e.lms, Name = e.name, Url = e.url });
var dialog = uiFactory.CreateExamSelectionDialog(exams);
var result = dialog.Show(splashScreen);
runtime.SubmitExamSelectionResult(args.RequestId, result.Success, result.SelectedExam?.Id);
logger.Info($"Exam selection request with id '{args.RequestId}' is complete.");
}
private void ClientHost_MessageBoxRequested(MessageBoxRequestEventArgs args)
{
logger.Info($"Received message box request with id '{args.RequestId}'.");

View file

@ -24,6 +24,7 @@ namespace SafeExamBrowser.Client.Communication
public Guid AuthenticationToken { private get; set; }
public bool IsConnected { get; private set; }
public event CommunicationEventHandler<ExamSelectionRequestEventArgs> ExamSelectionRequested;
public event CommunicationEventHandler<MessageBoxRequestEventArgs> MessageBoxRequested;
public event CommunicationEventHandler<PasswordRequestEventArgs> PasswordRequested;
public event CommunicationEventHandler ReconfigurationAborted;
@ -69,6 +70,9 @@ namespace SafeExamBrowser.Client.Communication
{
switch (message)
{
case ExamSelectionRequestMessage m:
ExamSelectionRequested?.InvokeAsync(new ExamSelectionRequestEventArgs { Exams = m.Exams, RequestId = m.RequestId });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case MessageBoxRequestMessage m:
MessageBoxRequested?.InvokeAsync(new MessageBoxRequestEventArgs { Action = m.Action, Icon = m.Icon, Message = m.Message, RequestId = m.RequestId, Title = m.Title });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 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.Communication.Contracts.Data
{
/// <summary>
/// The reply to a <see cref="ExamSelectionRequestMessage"/>.
/// </summary>
[Serializable]
public class ExamSelectionReplyMessage : Message
{
/// <summary>
/// The unique identifier for the exam selection request.
/// </summary>
public Guid RequestId { get; }
/// <summary>
/// The identifier of the exam selected by the user.
/// </summary>
public string SelectedExamId { get; }
/// <summary>
/// Determines whether the user interaction was successful or not.
/// </summary>
public bool Success { get; }
public ExamSelectionReplyMessage(Guid requestId, bool success, string selectedExamId)
{
RequestId = requestId;
Success = success;
SelectedExamId = selectedExamId;
}
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
namespace SafeExamBrowser.Communication.Contracts.Data
{
/// <summary>
/// This message is transmitted to the client to request a server exam selection by the user.
/// </summary>
[Serializable]
public class ExamSelectionRequestMessage : Message
{
/// <summary>
/// The exams from which the user needs to make a selection.
/// </summary>
public IEnumerable<(string id, string lms, string name, string url)> Exams { get; }
/// <summary>
/// The unique identifier for the server exam selection request.
/// </summary>
public Guid RequestId { get; }
public ExamSelectionRequestMessage(IEnumerable<(string id, string lms, string name, string url)> exams, Guid requestId)
{
Exams = exams;
RequestId = requestId;
}
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 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.Communication.Contracts.Events
{
/// <summary>
/// The event arguments used for the exam selection event fired by the <see cref="Hosts.IRuntimeHost"/>.
/// </summary>
public class ExamSelectionReplyEventArgs : CommunicationEventArgs
{
/// <summary>
/// Identifies the exam selection request.
/// </summary>
public Guid RequestId { get; set; }
/// <summary>
/// The identifier of the exam selected by the user.
/// </summary>
public string SelectedExamId { get; set; }
/// <summary>
/// Indicates whether an exam has been successfully selected by the user.
/// </summary>
public bool Success { get; set; }
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
namespace SafeExamBrowser.Communication.Contracts.Events
{
/// <summary>
/// The event arguments used for the server exam selection request event fired by the <see cref="Hosts.IClientHost"/>.
/// </summary>
public class ExamSelectionRequestEventArgs : CommunicationEventArgs
{
/// <summary>
/// The exams from which the user needs to make a selection.
/// </summary>
public IEnumerable<(string id, string lms, string name, string url)> Exams { get; set; }
/// <summary>
/// Identifies the server exam selection request.
/// </summary>
public Guid RequestId { get; set; }
}
}

View file

@ -26,6 +26,11 @@ namespace SafeExamBrowser.Communication.Contracts.Hosts
/// </summary>
bool IsConnected { get; }
/// <summary>
/// Event fired when the runtime requests a server exam selection from the user.
/// </summary>
event CommunicationEventHandler<ExamSelectionRequestEventArgs> ExamSelectionRequested;
/// <summary>
/// Event fired when the runtime requests a message box input from the user.
/// </summary>

View file

@ -41,6 +41,11 @@ namespace SafeExamBrowser.Communication.Contracts.Hosts
/// </summary>
event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded;
/// <summary>
/// Event fired when the client submitted a server exam selection made by the user.
/// </summary>
event CommunicationEventHandler<ExamSelectionReplyEventArgs> ExamSelectionReceived;
/// <summary>
/// Event fired when the client submitted a message box result chosen by the user.
/// </summary>

View file

@ -21,6 +21,8 @@ namespace SafeExamBrowser.Communication.Contracts
[ServiceKnownType(typeof(AuthenticationResponse))]
[ServiceKnownType(typeof(ClientConfiguration))]
[ServiceKnownType(typeof(ConfigurationResponse))]
[ServiceKnownType(typeof(ExamSelectionReplyMessage))]
[ServiceKnownType(typeof(ExamSelectionRequestMessage))]
[ServiceKnownType(typeof(MessageBoxReplyMessage))]
[ServiceKnownType(typeof(MessageBoxRequestMessage))]
[ServiceKnownType(typeof(PasswordReplyMessage))]

View file

@ -7,6 +7,7 @@
*/
using System;
using System.Collections.Generic;
using SafeExamBrowser.Communication.Contracts.Data;
namespace SafeExamBrowser.Communication.Contracts.Proxies
@ -36,6 +37,11 @@ namespace SafeExamBrowser.Communication.Contracts.Proxies
/// </summary>
CommunicationResult<AuthenticationResponse> RequestAuthentication();
/// <summary>
/// Requests the client to render a server exam selection dialog and subsequently return the interaction result as separate message.
/// </summary>
CommunicationResult RequestExamSelection(IEnumerable<(string id, string lms, string name, string url)> exams, Guid requestId);
/// <summary>
/// Requests the client to render a password dialog and subsequently return the interaction result as separate message.
/// </summary>

View file

@ -36,6 +36,12 @@ namespace SafeExamBrowser.Communication.Contracts.Proxies
/// </summary>
CommunicationResult RequestReconfiguration(string filePath);
/// <summary>
/// Submits the result of a server exam selection previously requested by the runtime. If the procedure was aborted by the user,
/// the selected exam identifier will be <see cref="default(string)"/>!
/// </summary>
CommunicationResult SubmitExamSelectionResult(Guid requestId, bool success, string selectedExamId = default(string));
/// <summary>
/// Submits the result of a message box input previously requested by the runtime.
/// </summary>
@ -43,8 +49,8 @@ namespace SafeExamBrowser.Communication.Contracts.Proxies
/// <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>!
/// the password parameter will be <see cref="default(string)"/>!
/// </summary>
CommunicationResult SubmitPassword(Guid requestId, bool success, string password = null);
CommunicationResult SubmitPassword(Guid requestId, bool success, string password = default(string));
}
}

View file

@ -60,6 +60,8 @@
<Compile Include="Data\ConnectionResponse.cs" />
<Compile Include="Data\DisconnectionMessage.cs" />
<Compile Include="Data\DisconnectionResponse.cs" />
<Compile Include="Data\ExamSelectionReplyMessage.cs" />
<Compile Include="Data\ExamSelectionRequestMessage.cs" />
<Compile Include="Data\Message.cs" />
<Compile Include="Data\MessageBoxReplyMessage.cs" />
<Compile Include="Data\MessageBoxRequestMessage.cs" />
@ -78,6 +80,8 @@
<Compile Include="Events\ClientConfigurationEventArgs.cs" />
<Compile Include="Events\CommunicationEventArgs.cs" />
<Compile Include="Events\CommunicationEventHandler.cs" />
<Compile Include="Events\ExamSelectionReplyEventArgs.cs" />
<Compile Include="Events\ExamSelectionRequestEventArgs.cs" />
<Compile Include="Events\MessageBoxReplyEventArgs.cs" />
<Compile Include="Events\MessageBoxRequestEventArgs.cs" />
<Compile Include="Events\PasswordReplyEventArgs.cs" />

View file

@ -7,6 +7,7 @@
*/
using System;
using System.Collections.Generic;
using SafeExamBrowser.Communication.Contracts;
using SafeExamBrowser.Communication.Contracts.Data;
using SafeExamBrowser.Communication.Contracts.Proxies;
@ -127,6 +128,32 @@ namespace SafeExamBrowser.Communication.Proxies
}
}
public CommunicationResult RequestExamSelection(IEnumerable<(string id, string lms, string name, string url)> exams, Guid requestId)
{
try
{
var response = Send(new ExamSelectionRequestMessage(exams, requestId));
var success = IsAcknowledged(response);
if (success)
{
Logger.Debug("Client acknowledged server exam selection request.");
}
else
{
Logger.Error($"Client did not acknowledge server exam selection request! Received: {ToString(response)}.");
}
return new CommunicationResult(success);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(RequestExamSelection)}'", e);
return new CommunicationResult(false);
}
}
public CommunicationResult RequestPassword(PasswordRequestPurpose purpose, Guid requestId)
{
try

View file

@ -127,6 +127,32 @@ namespace SafeExamBrowser.Communication.Proxies
}
}
public CommunicationResult SubmitExamSelectionResult(Guid requestId, bool success, string selectedExamId = null)
{
try
{
var response = Send(new ExamSelectionReplyMessage(requestId, success, selectedExamId));
var acknowledged = IsAcknowledged(response);
if (acknowledged)
{
Logger.Debug("Runtime acknowledged server exam selection transmission.");
}
else
{
Logger.Error($"Runtime did not acknowledge server exam selection transmission! Response: {ToString(response)}.");
}
return new CommunicationResult(acknowledged);
}
catch (Exception e)
{
Logger.Error($"Failed to perform '{nameof(SubmitExamSelectionResult)}'", e);
return new CommunicationResult(false);
}
}
public CommunicationResult SubmitMessageBoxResult(Guid requestId, int result)
{
try

View file

@ -24,6 +24,7 @@ namespace SafeExamBrowser.Runtime.Communication
public event CommunicationEventHandler ClientDisconnected;
public event CommunicationEventHandler ClientReady;
public event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded;
public event CommunicationEventHandler<ExamSelectionReplyEventArgs> ExamSelectionReceived;
public event CommunicationEventHandler<MessageBoxReplyEventArgs> MessageBoxReplyReceived;
public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
@ -58,6 +59,9 @@ namespace SafeExamBrowser.Runtime.Communication
{
switch (message)
{
case ExamSelectionReplyMessage m:
ExamSelectionReceived?.InvokeAsync(new ExamSelectionReplyEventArgs { RequestId = m.RequestId, SelectedExamId = m.SelectedExamId, Success = m.Success });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case MessageBoxReplyMessage m:
MessageBoxReplyReceived?.InvokeAsync(new MessageBoxReplyEventArgs { RequestId = m.RequestId, Result = m.Result });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);

View file

@ -7,6 +7,7 @@
*/
using System;
using System.Linq;
using System.Threading;
using SafeExamBrowser.Communication.Contracts.Data;
using SafeExamBrowser.Communication.Contracts.Events;
@ -18,6 +19,7 @@ using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Runtime.Operations.Events;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings.Security;
using SafeExamBrowser.Settings.Service;
using SafeExamBrowser.UserInterface.Contracts;
@ -410,8 +412,7 @@ namespace SafeExamBrowser.Runtime
}
else
{
// TODO: Also implement mechanism to retrieve selection via client!!
// TryAskForExamSelectionViaClient(args);
TryAskForExamSelectionViaClient(args);
}
}
@ -426,8 +427,7 @@ namespace SafeExamBrowser.Runtime
}
else
{
// TODO: Also implement mechanism to retrieve selection via client!!
// TryAskForServerFailureActionViaClient(args);
TryAskForServerFailureActionViaClient(args);
}
}
@ -521,6 +521,40 @@ namespace SafeExamBrowser.Runtime
args.Success = result.Success;
}
private void TryAskForExamSelectionViaClient(ExamSelectionEventArgs args)
{
var exams = args.Exams.Select(e => (e.Id, e.LmsName, e.Name, e.Url));
var requestId = Guid.NewGuid();
var response = default(ExamSelectionReplyEventArgs);
var responseEvent = new AutoResetEvent(false);
var responseEventHandler = new CommunicationEventHandler<ExamSelectionReplyEventArgs>((a) =>
{
if (a.RequestId == requestId)
{
response = a;
responseEvent.Set();
}
});
runtimeHost.ExamSelectionReceived += responseEventHandler;
var communication = sessionContext.ClientProxy.RequestExamSelection(exams, requestId);
if (communication.Success)
{
responseEvent.WaitOne();
args.SelectedExam = args.Exams.First(e => e.Id == response.SelectedExamId);
args.Success = response.Success;
}
else
{
args.SelectedExam = default(Exam);
args.Success = false;
}
runtimeHost.ExamSelectionReceived -= responseEventHandler;
}
private void TryAskForServerFailureActionViaDialog(ServerFailureEventArgs args)
{
var dialog = uiFactory.CreateServerFailureDialog(args.Message, args.ShowFallback);
@ -531,6 +565,11 @@ namespace SafeExamBrowser.Runtime
args.Retry = result.Retry;
}
private void TryAskForServerFailureActionViaClient(ServerFailureEventArgs args)
{
// TODO: Implement communication mechanism!
}
private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args)
{
var message = default(TextKey);

View file

@ -285,23 +285,19 @@ namespace SafeExamBrowser.Server
try
{
for (var count = 0; count < 5; count--)
if (logContent.TryDequeue(out var c) && c is ILogMessage message)
{
if (logContent.TryDequeue(out var c) && c is ILogMessage message)
var json = new JObject
{
var json = new JObject
{
["type"] = ToLogType(message.Severity),
["timestamp"] = message.DateTime.Ticks,
["text"] = message.Message
};
["type"] = ToLogType(message.Severity),
["timestamp"] = message.DateTime.Ticks,
["text"] = message.Message
};
var content = json.ToString();
var contentType = "application/json;charset=UTF-8";
// TODO: Logging these requests spams the application log!
// TODO: Why can't we send multiple log messages in one request?
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
}
var content = json.ToString();
var contentType = "application/json;charset=UTF-8";
// TODO: Why can't we send multiple log messages in one request?
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
}
}
catch (Exception e)