SEBWIN-477 implementation

This commit is contained in:
anhefti 2022-08-31 14:11:19 +02:00
parent 4bb46c0d7a
commit c13b2d2ac7
12 changed files with 190 additions and 8 deletions

View file

@ -68,6 +68,7 @@ namespace SafeExamBrowser.Client
private IServerProxy Server => context.Server; private IServerProxy Server => context.Server;
private AppSettings Settings => context.Settings; private AppSettings Settings => context.Settings;
private ILockScreen lockScreen;
private bool sessionLocked; private bool sessionLocked;
internal ClientController( internal ClientController(
@ -215,10 +216,25 @@ namespace SafeExamBrowser.Client
if (Server != null) if (Server != null)
{ {
Server.LockScreenConfirmed += Server_LockScreenConfirmed;
Server.TerminationRequested += Server_TerminationRequested; Server.TerminationRequested += Server_TerminationRequested;
Server.LockScreenRequested += Server_LockScreenRequested;
} }
} }
private void Server_LockScreenRequested(string message)
{
logger.Info("Received lock screen event from SEB Server.");
var title = text.Get(TextKey.LockScreen_Title);
ShowLockScreen(message, title, Enumerable.Empty<LockScreenOption>());
}
private void Server_LockScreenConfirmed()
{
logger.Info("Closing lock screen as requested by the server...");
lockScreen?.Cancel();
}
private void Taskbar_LoseFocusRequested(bool forward) private void Taskbar_LoseFocusRequested(bool forward)
{ {
Browser.Focus(forward); Browser.Focus(forward);
@ -260,7 +276,9 @@ namespace SafeExamBrowser.Client
if (Server != null) if (Server != null)
{ {
Server.LockScreenConfirmed -= Server_LockScreenConfirmed;
Server.TerminationRequested -= Server_TerminationRequested; Server.TerminationRequested -= Server_TerminationRequested;
Server.LockScreenRequested -= Server_LockScreenRequested;
} }
foreach (var activator in context.Activators.OfType<ITerminationActivator>()) foreach (var activator in context.Activators.OfType<ITerminationActivator>())
@ -769,18 +787,33 @@ namespace SafeExamBrowser.Client
private LockScreenResult ShowLockScreen(string message, string title, IEnumerable<LockScreenOption> options) private LockScreenResult ShowLockScreen(string message, string title, IEnumerable<LockScreenOption> options)
{ {
var hasQuitPassword = !string.IsNullOrEmpty(Settings.Security.QuitPasswordHash); var hasQuitPassword = !string.IsNullOrEmpty(Settings.Security.QuitPasswordHash);
var lockScreen = uiFactory.CreateLockScreen(message, title, options); lockScreen = uiFactory.CreateLockScreen(message, title, options);
var result = default(LockScreenResult); var result = default(LockScreenResult);
logger.Info("Showing lock screen..."); logger.Info("Showing lock screen...");
PauseActivators(); PauseActivators();
lockScreen.Show(); lockScreen.Show();
if (Settings.SessionMode == SessionMode.Server)
{
var response = Server.LockScreen(message);
if (!response.Success)
{
logger.Error($"Failed to send lock screen notification to server! Message: {response.Message}.");
}
}
for (var unlocked = false; !unlocked;) for (var unlocked = false; !unlocked;)
{ {
result = lockScreen.WaitForResult(); result = lockScreen.WaitForResult();
if (hasQuitPassword) if (result.Canceled)
{
logger.Info("The lock screen has been automaticaly canceled.");
unlocked = true;
}
else if (hasQuitPassword)
{ {
var passwordHash = hashAlgorithm.GenerateHashFor(result.Password); var passwordHash = hashAlgorithm.GenerateHashFor(result.Password);
var isCorrect = Settings.Security.QuitPasswordHash.Equals(passwordHash, StringComparison.OrdinalIgnoreCase); var isCorrect = Settings.Security.QuitPasswordHash.Equals(passwordHash, StringComparison.OrdinalIgnoreCase);
@ -807,6 +840,16 @@ namespace SafeExamBrowser.Client
ResumeActivators(); ResumeActivators();
logger.Info("Closed lock screen."); logger.Info("Closed lock screen.");
if (Settings.SessionMode == SessionMode.Server)
{
var response = Server.ConfirmLockScreen();
if (!response.Success)
{
logger.Error($"Failed to send lock screen confirm notification to server! Message: {response.Message}.");
}
}
return result; return result;
} }

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SafeExamBrowser.Server.Contracts.Events
{
/// <summary>
/// Event handler used to handle a requested lock screen from SEB Server
/// </summary>
public delegate void LockScreenRequestedEventHandler(String message);
}

View file

@ -24,6 +24,16 @@ namespace SafeExamBrowser.Server.Contracts
/// </summary> /// </summary>
event ServerEventHandler HandConfirmed; event ServerEventHandler HandConfirmed;
/// <summary>
/// Event fired when the proxy receives a confirmation for a lock screen notification.
/// </summary>
event ServerEventHandler LockScreenConfirmed;
/// <summary>
/// Event fired when proxy receives a lock screen instruction.
/// </summary>
event LockScreenRequestedEventHandler LockScreenRequested;
/// <summary> /// <summary>
/// Event fired when the proxy receives new proctoring configuration values. /// Event fired when the proxy receives new proctoring configuration values.
/// </summary> /// </summary>
@ -79,6 +89,11 @@ namespace SafeExamBrowser.Server.Contracts
/// </summary> /// </summary>
ServerResponse LowerHand(); ServerResponse LowerHand();
/// <summary>
/// Sends a lock screen confirm notification to the server.
/// </summary>
ServerResponse ConfirmLockScreen();
/// <summary> /// <summary>
/// Sends the given user session identifier of a LMS and thus establishes a connection with the server. /// Sends the given user session identifier of a LMS and thus establishes a connection with the server.
/// </summary> /// </summary>
@ -98,5 +113,10 @@ namespace SafeExamBrowser.Server.Contracts
/// Sends a raise hand notification to the server. /// Sends a raise hand notification to the server.
/// </summary> /// </summary>
ServerResponse RaiseHand(string message = default(string)); ServerResponse RaiseHand(string message = default(string));
/// <summary>
/// Sends a lock screen notification to the server.
/// </summary>
ServerResponse LockScreen(string message = default(string));
} }
} }

View file

@ -56,6 +56,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="Data\ConnectionInfo.cs" /> <Compile Include="Data\ConnectionInfo.cs" />
<Compile Include="Data\Exam.cs" /> <Compile Include="Data\Exam.cs" />
<Compile Include="Events\LockScreenRequestedEventHandler.cs" />
<Compile Include="Events\ProctoringConfigurationReceivedEventHandler.cs" /> <Compile Include="Events\ProctoringConfigurationReceivedEventHandler.cs" />
<Compile Include="Events\ProctoringInstructionEventArgs.cs" /> <Compile Include="Events\ProctoringInstructionEventArgs.cs" />
<Compile Include="Events\ProctoringInstructionReceivedEventHandler.cs" /> <Compile Include="Events\ProctoringInstructionReceivedEventHandler.cs" />

View file

@ -19,6 +19,8 @@ namespace SafeExamBrowser.Server.Data
internal bool ReceiveVideo { get; set; } internal bool ReceiveVideo { get; set; }
internal string Type { get; set; } internal string Type { get; set; }
internal string Message { get; set; }
internal Attributes() internal Attributes()
{ {
Instruction = new ProctoringInstructionEventArgs(); Instruction = new ProctoringInstructionEventArgs();

View file

@ -14,5 +14,6 @@ namespace SafeExamBrowser.Server.Data
internal const string PROCTORING = "SEB_PROCTORING"; internal const string PROCTORING = "SEB_PROCTORING";
internal const string PROCTORING_RECONFIGURATION = "SEB_RECONFIGURE_SETTINGS"; internal const string PROCTORING_RECONFIGURATION = "SEB_RECONFIGURE_SETTINGS";
internal const string QUIT = "SEB_QUIT"; internal const string QUIT = "SEB_QUIT";
internal const string LOCK_SCREEN = "SEB_FORCE_LOCK_SCREEN";
} }
} }

View file

@ -180,7 +180,7 @@ namespace SafeExamBrowser.Server
instructionConfirmation = attributesJson["instruction-confirm"].Value<string>(); instructionConfirmation = attributesJson["instruction-confirm"].Value<string>();
} }
attributes = ParseProctoringAttributes(attributesJson, instruction); attributes = ParseAttributes(attributesJson, instruction);
} }
} }
} }
@ -210,7 +210,7 @@ namespace SafeExamBrowser.Server
return oauth2Token != default; return oauth2Token != default;
} }
private Attributes ParseProctoringAttributes(JObject attributesJson, string instruction) private Attributes ParseAttributes(JObject attributesJson, string instruction)
{ {
var attributes = new Attributes(); var attributes = new Attributes();
@ -225,11 +225,22 @@ namespace SafeExamBrowser.Server
case Instructions.PROCTORING_RECONFIGURATION: case Instructions.PROCTORING_RECONFIGURATION:
ParseReconfigurationInstruction(attributes, attributesJson); ParseReconfigurationInstruction(attributes, attributesJson);
break; break;
case Instructions.LOCK_SCREEN:
ParseLockScreenInstruction(attributes, attributesJson);
break;
} }
return attributes; return attributes;
} }
private void ParseLockScreenInstruction(Attributes attributes, JObject attributesJson)
{
if (attributesJson.ContainsKey("message"))
{
attributes.Message = attributesJson["message"].Value<string>();
}
}
private void ParseNotificationConfirmation(Attributes attributes, JObject attributesJson) private void ParseNotificationConfirmation(Attributes attributes, JObject attributesJson)
{ {
if (attributesJson.ContainsKey("id")) if (attributesJson.ContainsKey("id"))

View file

@ -54,16 +54,21 @@ namespace SafeExamBrowser.Server
private bool connectedToPowergrid; private bool connectedToPowergrid;
private int currentWlanValue; private int currentWlanValue;
private string examId; private string examId;
private int handNotificationId; private int notificationId;
private int currentRaisHandId;
private int currentLockScreenId;
private HttpClient httpClient; private HttpClient httpClient;
private string oauth2Token; private string oauth2Token;
private int pingNumber; private int pingNumber;
private ServerSettings settings; private ServerSettings settings;
public event ServerEventHandler HandConfirmed; public event ServerEventHandler HandConfirmed;
public event ServerEventHandler LockScreenConfirmed;
public event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived; public event ProctoringConfigurationReceivedEventHandler ProctoringConfigurationReceived;
public event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived; public event ProctoringInstructionReceivedEventHandler ProctoringInstructionReceived;
public event TerminationRequestedEventHandler TerminationRequested; public event TerminationRequestedEventHandler TerminationRequested;
public event LockScreenRequestedEventHandler LockScreenRequested;
public ServerProxy( public ServerProxy(
AppConfig appConfig, AppConfig appConfig,
@ -242,7 +247,7 @@ namespace SafeExamBrowser.Server
{ {
["type"] = "NOTIFICATION_CONFIRMED", ["type"] = "NOTIFICATION_CONFIRMED",
["timestamp"] = DateTime.Now.ToUnixTimestamp(), ["timestamp"] = DateTime.Now.ToUnixTimestamp(),
["numericValue"] = handNotificationId, ["numericValue"] = currentRaisHandId,
}; };
var content = json.ToString(); var content = json.ToString();
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token); var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
@ -259,6 +264,32 @@ namespace SafeExamBrowser.Server
return new ServerResponse(success, response.ToLogString()); return new ServerResponse(success, response.ToLogString());
} }
public ServerResponse ConfirmLockScreen()
{
var authorization = ("Authorization", $"Bearer {oauth2Token}");
var contentType = "application/json;charset=UTF-8";
var token = ("SEBConnectionToken", connectionToken);
var json = new JObject
{
["type"] = "NOTIFICATION_CONFIRMED",
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
["numericValue"] = currentLockScreenId,
};
var content = json.ToString();
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
if (success)
{
logger.Info("Successfully sent lock screen confirm notification.");
}
else
{
logger.Error("Failed to send lock screen confirm notification!");
}
return new ServerResponse(success, response.ToLogString());
}
public void Notify(ILogContent content) public void Notify(ILogContent content)
{ {
logContent.Enqueue(content); logContent.Enqueue(content);
@ -273,7 +304,7 @@ namespace SafeExamBrowser.Server
{ {
["type"] = "NOTIFICATION", ["type"] = "NOTIFICATION",
["timestamp"] = DateTime.Now.ToUnixTimestamp(), ["timestamp"] = DateTime.Now.ToUnixTimestamp(),
["numericValue"] = ++handNotificationId, ["numericValue"] = currentRaisHandId = ++notificationId,
["text"] = $"<raisehand> {message}" ["text"] = $"<raisehand> {message}"
}; };
var content = json.ToString(); var content = json.ToString();
@ -291,6 +322,33 @@ namespace SafeExamBrowser.Server
return new ServerResponse(success, response.ToLogString()); return new ServerResponse(success, response.ToLogString());
} }
public ServerResponse LockScreen(string message = null)
{
var authorization = ("Authorization", $"Bearer {oauth2Token}");
var contentType = "application/json;charset=UTF-8";
var token = ("SEBConnectionToken", connectionToken);
var json = new JObject
{
["type"] = "NOTIFICATION",
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
["numericValue"] = currentLockScreenId = ++notificationId,
["text"] = $"<lockscreen> {message}"
};
var content = json.ToString();
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, content, contentType, authorization, token);
if (success)
{
logger.Info("Successfully sent lock screen notification.");
}
else
{
logger.Error("Failed to send lock screen notification!");
}
return new ServerResponse(success, response.ToLogString());
}
public ServerResponse SendSessionIdentifier(string identifier) public ServerResponse SendSessionIdentifier(string identifier)
{ {
var authorization = ("Authorization", $"Bearer {oauth2Token}"); var authorization = ("Authorization", $"Bearer {oauth2Token}");
@ -414,9 +472,12 @@ namespace SafeExamBrowser.Server
{ {
switch (instruction) switch (instruction)
{ {
case Instructions.NOTIFICATION_CONFIRM when attributes.Type == "raisehand" && attributes.Id == handNotificationId: case Instructions.NOTIFICATION_CONFIRM when attributes.Type == "raisehand":
Task.Run(() => HandConfirmed?.Invoke()); Task.Run(() => HandConfirmed?.Invoke());
break; break;
case Instructions.NOTIFICATION_CONFIRM when attributes.Type == "lockscreen":
Task.Run(() => LockScreenConfirmed?.Invoke());
break;
case Instructions.PROCTORING: case Instructions.PROCTORING:
Task.Run(() => ProctoringInstructionReceived?.Invoke(attributes.Instruction)); Task.Run(() => ProctoringInstructionReceived?.Invoke(attributes.Instruction));
break; break;
@ -426,6 +487,9 @@ namespace SafeExamBrowser.Server
case Instructions.QUIT: case Instructions.QUIT:
Task.Run(() => TerminationRequested?.Invoke()); Task.Run(() => TerminationRequested?.Invoke());
break; break;
case Instructions.LOCK_SCREEN:
Task.Run(() => LockScreenRequested?.Invoke(attributes.Message));
break;
} }
if (instructionConfirmation != default) if (instructionConfirmation != default)

View file

@ -15,6 +15,12 @@ namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data
/// </summary> /// </summary>
public class LockScreenResult public class LockScreenResult
{ {
/// <summary>
/// This is been set if the lock screen was canceled from another process (E.g.: from SEB Server instruction)
/// </summary>
public bool Canceled { get; set; }
/// <summary> /// <summary>
/// The identifier of the option selected by the user, if available. /// The identifier of the option selected by the user, if available.
/// </summary> /// </summary>

View file

@ -19,5 +19,9 @@ namespace SafeExamBrowser.UserInterface.Contracts.Windows
/// Waits for the user to provide the required input to unlock the application. /// Waits for the user to provide the required input to unlock the application.
/// </summary> /// </summary>
LockScreenResult WaitForResult(); LockScreenResult WaitForResult();
/// <summary>
/// This cancel a waiting thread for LockScreenResult and force the lock screen to close
/// </summary>
void Cancel();
} }
} }

View file

@ -27,6 +27,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
{ {
private readonly AutoResetEvent autoResetEvent; private readonly AutoResetEvent autoResetEvent;
private readonly IText text; private readonly IText text;
private bool canceled = false;
private IList<Window> windows; private IList<Window> windows;
@ -85,6 +86,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
} }
}); });
result.Canceled = canceled;
return result; return result;
} }
@ -172,5 +174,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
autoResetEvent.Set(); autoResetEvent.Set();
} }
} }
public void Cancel()
{
canceled = true;
autoResetEvent.Set();
}
} }
} }

View file

@ -27,6 +27,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
{ {
private readonly AutoResetEvent autoResetEvent; private readonly AutoResetEvent autoResetEvent;
private readonly IText text; private readonly IText text;
private bool canceled = false;
private IList<Window> windows; private IList<Window> windows;
@ -85,6 +86,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
} }
}); });
result.Canceled = canceled;
return result; return result;
} }
@ -172,5 +174,11 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
autoResetEvent.Set(); autoResetEvent.Set();
} }
} }
public void Cancel()
{
canceled = true;
autoResetEvent.Set();
}
} }
} }