213 lines
6.4 KiB
C#
213 lines
6.4 KiB
C#
/*
|
|
* Copyright (c) 2025 ETH Zürich, IT Services
|
|
*
|
|
* 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.IO;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using SafeExamBrowser.Browser.Contracts;
|
|
using SafeExamBrowser.Browser.Contracts.Events;
|
|
using SafeExamBrowser.Client.Contracts;
|
|
using SafeExamBrowser.Communication.Contracts.Proxies;
|
|
using SafeExamBrowser.I18n.Contracts;
|
|
using SafeExamBrowser.Logging.Contracts;
|
|
using SafeExamBrowser.Settings;
|
|
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
|
using SafeExamBrowser.UserInterface.Contracts.Shell;
|
|
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
|
|
|
namespace SafeExamBrowser.Client.Responsibilities
|
|
{
|
|
internal class BrowserResponsibility : ClientResponsibility
|
|
{
|
|
private readonly ICoordinator coordinator;
|
|
private readonly IMessageBox messageBox;
|
|
private readonly IRuntimeProxy runtime;
|
|
private readonly ISplashScreen splashScreen;
|
|
private readonly ITaskbar taskbar;
|
|
|
|
private IBrowserApplication Browser => Context.Browser;
|
|
|
|
public BrowserResponsibility(
|
|
ClientContext context,
|
|
ICoordinator coordinator,
|
|
ILogger logger,
|
|
IMessageBox messageBox,
|
|
IRuntimeProxy runtime,
|
|
ISplashScreen splashScreen,
|
|
ITaskbar taskbar) : base(context, logger)
|
|
{
|
|
this.coordinator = coordinator;
|
|
this.messageBox = messageBox;
|
|
this.runtime = runtime;
|
|
this.splashScreen = splashScreen;
|
|
this.taskbar = taskbar;
|
|
}
|
|
|
|
public override void Assume(ClientTask task)
|
|
{
|
|
switch (task)
|
|
{
|
|
case ClientTask.AutoStartApplications:
|
|
AutoStartBrowser();
|
|
break;
|
|
case ClientTask.DeregisterEvents:
|
|
DeregisterEvents();
|
|
break;
|
|
case ClientTask.RegisterEvents:
|
|
RegisterEvents();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void AutoStartBrowser()
|
|
{
|
|
if (Settings.Browser.EnableBrowser && Browser.AutoStart)
|
|
{
|
|
Logger.Info("Auto-starting browser...");
|
|
Browser.Start();
|
|
}
|
|
}
|
|
|
|
private void DeregisterEvents()
|
|
{
|
|
if (Browser != default)
|
|
{
|
|
Browser.ConfigurationDownloadRequested -= Browser_ConfigurationDownloadRequested;
|
|
Browser.LoseFocusRequested -= Browser_LoseFocusRequested;
|
|
Browser.TerminationRequested -= Browser_TerminationRequested;
|
|
Browser.UserIdentifierDetected -= Browser_UserIdentifierDetected;
|
|
}
|
|
}
|
|
|
|
private void RegisterEvents()
|
|
{
|
|
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
|
|
Browser.LoseFocusRequested += Browser_LoseFocusRequested;
|
|
Browser.TerminationRequested += Browser_TerminationRequested;
|
|
Browser.UserIdentifierDetected += Browser_UserIdentifierDetected;
|
|
}
|
|
|
|
private void Browser_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
|
|
{
|
|
args.AllowDownload = false;
|
|
|
|
if (IsAllowedToReconfigure(args.Url))
|
|
{
|
|
if (coordinator.RequestReconfigurationLock())
|
|
{
|
|
args.AllowDownload = true;
|
|
args.Callback = Browser_ConfigurationDownloadFinished;
|
|
args.DownloadPath = Path.Combine(Context.AppConfig.TemporaryDirectory, fileName);
|
|
|
|
splashScreen.Show();
|
|
splashScreen.BringToForeground();
|
|
splashScreen.SetIndeterminate();
|
|
splashScreen.UpdateStatus(TextKey.OperationStatus_InitializeSession, true);
|
|
|
|
Logger.Info($"Allowed download request for configuration file '{fileName}'.");
|
|
}
|
|
else
|
|
{
|
|
Logger.Warn($"A reconfiguration is already in progress, denied download request for configuration file '{fileName}'!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger.Info($"Reconfiguration is not allowed, denied download request for configuration file '{fileName}'.");
|
|
}
|
|
}
|
|
|
|
private bool IsAllowedToReconfigure(string url)
|
|
{
|
|
var allow = false;
|
|
var hasQuitPassword = !string.IsNullOrWhiteSpace(Settings.Security.QuitPasswordHash);
|
|
var hasUrl = !string.IsNullOrWhiteSpace(Settings.Security.ReconfigurationUrl);
|
|
|
|
if (hasQuitPassword)
|
|
{
|
|
if (hasUrl)
|
|
{
|
|
var expression = Regex.Escape(Settings.Security.ReconfigurationUrl).Replace(@"\*", ".*");
|
|
var regex = new Regex($"^{expression}$", RegexOptions.IgnoreCase);
|
|
var sebUrl = url.Replace(Uri.UriSchemeHttps, Context.AppConfig.SebUriSchemeSecure).Replace(Uri.UriSchemeHttp, Context.AppConfig.SebUriScheme);
|
|
|
|
allow = Settings.Security.AllowReconfiguration && (regex.IsMatch(url) || regex.IsMatch(sebUrl));
|
|
}
|
|
else
|
|
{
|
|
Logger.Warn("The active configuration does not contain a valid reconfiguration URL!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
allow = Settings.ConfigurationMode == ConfigurationMode.ConfigureClient || Settings.Security.AllowReconfiguration;
|
|
}
|
|
|
|
return allow;
|
|
}
|
|
|
|
private void Browser_ConfigurationDownloadFinished(bool success, string url, string filePath = null)
|
|
{
|
|
if (success)
|
|
{
|
|
PrepareShutdown();
|
|
|
|
var communication = runtime.RequestReconfiguration(filePath, url);
|
|
|
|
if (communication.Success)
|
|
{
|
|
Logger.Info($"Sent reconfiguration request for '{filePath}' to the runtime.");
|
|
}
|
|
else
|
|
{
|
|
Logger.Error($"Failed to communicate reconfiguration request for '{filePath}'!");
|
|
|
|
messageBox.Show(TextKey.MessageBox_ReconfigurationError, TextKey.MessageBox_ReconfigurationErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen);
|
|
splashScreen.Hide();
|
|
coordinator.ReleaseReconfigurationLock();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Logger.Error($"Failed to download configuration file '{filePath}'!");
|
|
|
|
messageBox.Show(TextKey.MessageBox_ConfigurationDownloadError, TextKey.MessageBox_ConfigurationDownloadErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen);
|
|
splashScreen.Hide();
|
|
coordinator.ReleaseReconfigurationLock();
|
|
}
|
|
}
|
|
|
|
private void Browser_LoseFocusRequested(bool forward)
|
|
{
|
|
taskbar.Focus(forward);
|
|
}
|
|
|
|
private void Browser_UserIdentifierDetected(string identifier)
|
|
{
|
|
if (Settings.SessionMode == SessionMode.Server)
|
|
{
|
|
var response = Context.Server.SendUserIdentifier(identifier);
|
|
|
|
while (!response.Success)
|
|
{
|
|
Logger.Error($"Failed to communicate user identifier with server! {response.Message}");
|
|
Thread.Sleep(Settings.Server.RequestAttemptInterval);
|
|
response = Context.Server.SendUserIdentifier(identifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Browser_TerminationRequested()
|
|
{
|
|
Logger.Info("Attempting to shutdown as requested by the browser...");
|
|
TryRequestShutdown();
|
|
}
|
|
}
|
|
}
|