SEBWIN-310: Implemented quit URL.

This commit is contained in:
dbuechel 2019-12-19 15:02:40 +01:00
parent 4b415a5f45
commit 130dd45ff6
17 changed files with 213 additions and 88 deletions

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2019 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/.
*/
namespace SafeExamBrowser.Browser.Contracts.Events
{
/// <summary>
/// Event handler used to indicate that a termination request has been detected.
/// </summary>
public delegate void TerminationRequestedEventHandler();
}

View file

@ -20,5 +20,10 @@ namespace SafeExamBrowser.Browser.Contracts
/// Event fired when the browser application detects a download request for an application configuration file.
/// </summary>
event DownloadRequestedEventHandler ConfigurationDownloadRequested;
/// <summary>
/// Event fired when the browser application detects a request to terminate SEB.
/// </summary>
event TerminationRequestedEventHandler TerminationRequested;
}
}

View file

@ -57,6 +57,7 @@
<Compile Include="Events\DownloadEventArgs.cs" />
<Compile Include="Events\DownloadFinishedCallback.cs" />
<Compile Include="Events\DownloadRequestedEventHandler.cs" />
<Compile Include="Events\TerminationRequestedEventHandler.cs" />
<Compile Include="Filters\IRequestFilter.cs" />
<Compile Include="Filters\IRule.cs" />
<Compile Include="Filters\IRuleFactory.cs" />

View file

@ -20,7 +20,6 @@ using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Proxy;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.UserInterface.Contracts;
@ -49,6 +48,7 @@ namespace SafeExamBrowser.Browser
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
public event WindowsChangedEventHandler WindowsChanged;
public event TerminationRequestedEventHandler TerminationRequested;
public BrowserApplication(
AppConfig appConfig,
@ -127,6 +127,7 @@ namespace SafeExamBrowser.Browser
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.PopupRequested += Instance_PopupRequested;
instance.Terminated += Instance_Terminated;
instance.TerminationRequested += () => TerminationRequested?.Invoke();
instance.Initialize();
instances.Add(instance);

View file

@ -40,9 +40,9 @@ namespace SafeExamBrowser.Browser
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private string startUrl;
private IText text;
private IUserInterfaceFactory uiFactory;
private string url;
private double zoomLevel;
private WindowSettings WindowSettings
@ -59,6 +59,7 @@ namespace SafeExamBrowser.Browser
internal event DownloadRequestedEventHandler ConfigurationDownloadRequested;
internal event PopupRequestedEventHandler PopupRequested;
internal event InstanceTerminatedEventHandler Terminated;
internal event TerminationRequestedEventHandler TerminationRequested;
public event IconChangedEventHandler IconChanged;
public event TitleChangedEventHandler TitleChanged;
@ -72,7 +73,7 @@ namespace SafeExamBrowser.Browser
IModuleLogger logger,
IText text,
IUserInterfaceFactory uiFactory,
string url)
string startUrl)
{
this.appConfig = appConfig;
this.Id = id;
@ -83,7 +84,7 @@ namespace SafeExamBrowser.Browser
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
this.url = url;
this.startUrl = startUrl;
}
public void Activate()
@ -106,12 +107,12 @@ namespace SafeExamBrowser.Browser
{
var contextMenuHandler = new ContextMenuHandler();
var displayHandler = new DisplayHandler();
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} {Id}");
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} #{Id}");
var downloadHandler = new DownloadHandler(appConfig, settings, downloadLogger);
var keyboardHandler = new KeyboardHandler();
var lifeSpanHandler = new LifeSpanHandler();
var requestFilter = new RequestFilter();
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} {Id}");
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, settings, text);
Icon = new BrowserIconResource();
@ -124,11 +125,12 @@ namespace SafeExamBrowser.Browser
keyboardHandler.ZoomOutRequested += ZoomOutRequested;
keyboardHandler.ZoomResetRequested += ZoomResetRequested;
lifeSpanHandler.PopupRequested += LifeSpanHandler_PopupRequested;
requestHandler.QuitUrlVisited += RequestHandler_QuitUrlVisited;
requestHandler.RequestBlocked += RequestHandler_RequestBlocked;
InitializeRequestFilter(requestFilter);
control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, url);
control = new BrowserControl(contextMenuHandler, displayHandler, downloadHandler, keyboardHandler, lifeSpanHandler, requestHandler, startUrl);
control.AddressChanged += Control_AddressChanged;
control.LoadingStateChanged += Control_LoadingStateChanged;
control.TitleChanged += Control_TitleChanged;
@ -276,6 +278,35 @@ namespace SafeExamBrowser.Browser
}
}
private void RequestHandler_QuitUrlVisited(string url)
{
Task.Run(() =>
{
if (settings.ConfirmQuitUrl)
{
var message = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmation);
var title = text.Get(TextKey.MessageBox_BrowserQuitUrlConfirmationTitle);
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
var terminate = result == MessageBoxResult.Yes;
if (terminate)
{
logger.Info($"User confirmed termination via quit URL '{url}', forwarding request...");
TerminationRequested?.Invoke();
}
else
{
logger.Info($"User aborted termination via quit URL '{url}'.");
}
}
else
{
logger.Info($"Automatically requesting termination due to quit URL '{url}'...");
TerminationRequested?.Invoke();
}
});
}
private void RequestHandler_RequestBlocked(string url)
{
Task.Run(() =>
@ -285,7 +316,7 @@ namespace SafeExamBrowser.Browser
control.TitleChanged -= Control_TitleChanged;
if (url == this.url)
if (url.Equals(startUrl, StringComparison.OrdinalIgnoreCase))
{
window.UpdateTitle($"*** {title} ***");
TitleChanged?.Invoke($"*** {title} ***");

View file

@ -8,5 +8,5 @@
namespace SafeExamBrowser.Browser.Events
{
internal delegate void RequestBlockedEventHandler(string url);
internal delegate void UrlEventHandler(string url);
}

View file

@ -25,7 +25,8 @@ namespace SafeExamBrowser.Browser.Handlers
private ResourceHandler resourceHandler;
private BrowserSettings settings;
internal event RequestBlockedEventHandler RequestBlocked;
internal event UrlEventHandler QuitUrlVisited;
internal event UrlEventHandler RequestBlocked;
internal RequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, BrowserSettings settings, IText text)
{
@ -60,6 +61,13 @@ namespace SafeExamBrowser.Browser.Handlers
protected override bool OnBeforeBrowse(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
{
if (IsQuitUrl(request))
{
QuitUrlVisited?.Invoke(request.Url);
return true;
}
if (Block(request))
{
RequestBlocked?.Invoke(request.Url);
@ -70,6 +78,18 @@ namespace SafeExamBrowser.Browser.Handlers
return base.OnBeforeBrowse(webBrowser, browser, frame, request, userGesture, isRedirect);
}
private bool IsQuitUrl(IRequest request)
{
var isQuitUrl = settings.QuitUrl?.Equals(request.Url, StringComparison.OrdinalIgnoreCase) == true;
if (isQuitUrl)
{
logger.Debug($"Detected quit URL '{request.Url}'.");
}
return isQuitUrl;
}
private bool Block(IRequest request)
{
if (settings.Filter.ProcessMainRequests)

View file

@ -68,7 +68,7 @@
<Compile Include="Events\PopupRequestedEventArgs.cs" />
<Compile Include="Events\PopupRequestedEventHandler.cs" />
<Compile Include="Events\ProgressChangedEventHandler.cs" />
<Compile Include="Events\RequestBlockedEventHandler.cs" />
<Compile Include="Events\UrlEventHandler.cs" />
<Compile Include="Filters\RequestFilter.cs" />
<Compile Include="Filters\Rules\RegexRule.cs" />
<Compile Include="Filters\RuleFactory.cs" />

View file

@ -232,6 +232,17 @@ namespace SafeExamBrowser.Client.UnitTests
It.Is<IWindow>(w => w == lockScreen.Object)), Times.Exactly(attempt - 1));
}
[TestMethod]
public void Browser_MustTerminateIfRequested()
{
runtimeProxy.Setup(p => p.RequestShutdown()).Returns(new CommunicationResult(true));
sut.TryStart();
browser.Raise(b => b.TerminationRequested += null);
runtimeProxy.Verify(p => p.RequestShutdown(), Times.Once);
}
[TestMethod]
public void Communication_MustCorrectlyHandleMessageBoxRequest()
{

View file

@ -171,6 +171,7 @@ namespace SafeExamBrowser.Client
applicationMonitor.ExplorerStarted += ApplicationMonitor_ExplorerStarted;
applicationMonitor.TerminationFailed += ApplicationMonitor_TerminationFailed;
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
Browser.TerminationRequested += Browser_TerminationRequested;
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested;
ClientHost.ReconfigurationDenied += ClientHost_ReconfigurationDenied;
@ -321,8 +322,7 @@ namespace SafeExamBrowser.Client
}
else if (result.OptionId == terminateOption.Id)
{
logger.Info("Initiating shutdown request...");
logger.Info("Attempting to shutdown as requested by the user...");
TryRequestShutdown();
}
}
@ -343,6 +343,12 @@ namespace SafeExamBrowser.Client
}
}
private void Browser_TerminationRequested()
{
logger.Info("Attempting to shutdown as requested by the browser...");
TryRequestShutdown();
}
private void Browser_ConfigurationDownloadFinished(bool success, string filePath = null)
{
if (success)

View file

@ -94,8 +94,8 @@ namespace SafeExamBrowser.Configuration.UnitTests.DataFormats
Assert.AreEqual(true, result.RawData[Keys.Browser.AllowConfigurationDownloads]);
Assert.AreEqual(0, result.RawData[Keys.ConfigurationFile.ConfigurationPurpose]);
Assert.AreEqual("https://safeexambrowser.org/start", result.RawData[Keys.General.StartUrl]);
Assert.AreEqual(true, result.RawData[Keys.Input.Keyboard.EnableF5]);
Assert.AreEqual("https://safeexambrowser.org/start", result.RawData[Keys.Browser.StartUrl]);
Assert.AreEqual(true, result.RawData[Keys.Keyboard.EnableF5]);
Assert.IsInstanceOfType(result.RawData[Keys.Network.Certificates.EmbeddedCertificates], typeof(List<object>));
}

View file

@ -133,6 +133,22 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
}
}
private void MapQuitUrl(AppSettings settings, object value)
{
if (value is string url)
{
settings.Browser.QuitUrl = url;
}
}
private void MapQuitUrlConfirmation(AppSettings settings, object value)
{
if (value is bool confirm)
{
settings.Browser.ConfirmQuitUrl = confirm;
}
}
private void MapRequestFilter(IDictionary<string, object> rawData, AppSettings settings)
{
var processMainRequests = rawData.TryGetValue(Keys.Browser.Filter.EnableMainRequestFilter, out var value) && value as bool? == true;

View file

@ -22,7 +22,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
MapBrowserSettings(item.Key, item.Value, settings);
MapConfigurationFileSettings(item.Key, item.Value, settings);
MapGeneralSettings(item.Key, item.Value, settings);
MapInputSettings(item.Key, item.Value, settings);
MapKeyboardSettings(item.Key, item.Value, settings);
MapMouseSettings(item.Key, item.Value, settings);
MapSecuritySettings(item.Key, item.Value, settings);
MapUserInterfaceSettings(item.Key, item.Value, settings);
}
@ -133,6 +134,15 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
case Keys.Browser.Proxy.Settings:
MapProxySettings(settings, value);
break;
case Keys.Browser.QuitUrl:
MapQuitUrl(settings, value);
break;
case Keys.Browser.QuitUrlConfirmation:
MapQuitUrlConfirmation(settings, value);
break;
case Keys.Browser.StartUrl:
MapStartUrl(settings, value);
break;
}
}
@ -140,6 +150,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
switch (key)
{
case Keys.ConfigurationFile.AdminPasswordHash:
MapAdminPasswordHash(settings, value);
break;
case Keys.ConfigurationFile.ConfigurationPurpose:
MapConfigurationMode(settings, value);
break;
@ -150,83 +163,81 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
switch (key)
{
case Keys.General.AdminPasswordHash:
MapAdminPasswordHash(settings, value);
break;
case Keys.General.LogLevel:
MapLogLevel(settings, value);
break;
case Keys.General.QuitPasswordHash:
MapQuitPasswordHash(settings, value);
break;
case Keys.General.StartUrl:
MapStartUrl(settings, value);
break;
}
}
private void MapInputSettings(string key, object value, AppSettings settings)
private void MapKeyboardSettings(string key, object value, AppSettings settings)
{
switch (key)
{
case Keys.Input.Keyboard.EnableAltEsc:
case Keys.Keyboard.EnableAltEsc:
MapEnableAltEsc(settings, value);
break;
case Keys.Input.Keyboard.EnableAltF4:
case Keys.Keyboard.EnableAltF4:
MapEnableAltF4(settings, value);
break;
case Keys.Input.Keyboard.EnableAltTab:
case Keys.Keyboard.EnableAltTab:
MapEnableAltTab(settings, value);
break;
case Keys.Input.Keyboard.EnableCtrlEsc:
case Keys.Keyboard.EnableCtrlEsc:
MapEnableCtrlEsc(settings, value);
break;
case Keys.Input.Keyboard.EnableEsc:
case Keys.Keyboard.EnableEsc:
MapEnableEsc(settings, value);
break;
case Keys.Input.Keyboard.EnableF1:
case Keys.Keyboard.EnableF1:
MapEnableF1(settings, value);
break;
case Keys.Input.Keyboard.EnableF2:
case Keys.Keyboard.EnableF2:
MapEnableF2(settings, value);
break;
case Keys.Input.Keyboard.EnableF3:
case Keys.Keyboard.EnableF3:
MapEnableF3(settings, value);
break;
case Keys.Input.Keyboard.EnableF4:
case Keys.Keyboard.EnableF4:
MapEnableF4(settings, value);
break;
case Keys.Input.Keyboard.EnableF5:
case Keys.Keyboard.EnableF5:
MapEnableF5(settings, value);
break;
case Keys.Input.Keyboard.EnableF6:
case Keys.Keyboard.EnableF6:
MapEnableF6(settings, value);
break;
case Keys.Input.Keyboard.EnableF7:
case Keys.Keyboard.EnableF7:
MapEnableF7(settings, value);
break;
case Keys.Input.Keyboard.EnableF8:
case Keys.Keyboard.EnableF8:
MapEnableF8(settings, value);
break;
case Keys.Input.Keyboard.EnableF9:
case Keys.Keyboard.EnableF9:
MapEnableF9(settings, value);
break;
case Keys.Input.Keyboard.EnableF10:
case Keys.Keyboard.EnableF10:
MapEnableF10(settings, value);
break;
case Keys.Input.Keyboard.EnableF11:
case Keys.Keyboard.EnableF11:
MapEnableF11(settings, value);
break;
case Keys.Input.Keyboard.EnableF12:
case Keys.Keyboard.EnableF12:
MapEnableF12(settings, value);
break;
case Keys.Input.Keyboard.EnablePrintScreen:
case Keys.Keyboard.EnablePrintScreen:
MapEnablePrintScreen(settings, value);
break;
case Keys.Input.Keyboard.EnableSystemKey:
case Keys.Keyboard.EnableSystemKey:
MapEnableSystemKey(settings, value);
break;
case Keys.Input.Mouse.EnableRightMouse:
}
}
private void MapMouseSettings(string key, object value, AppSettings settings)
{
switch (key)
{
case Keys.Mouse.EnableRightMouse:
MapEnableRightMouse(settings, value);
break;
}
@ -236,6 +247,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
switch (key)
{
case Keys.Security.QuitPasswordHash:
MapQuitPasswordHash(settings, value);
break;
case Keys.Security.ServicePolicy:
MapServicePolicy(settings, value);
break;

View file

@ -12,10 +12,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
internal const int WINDOWS = 1;
internal static class AdditionalResources
{
}
internal static class Applications
{
internal const string Active = "active";
@ -53,6 +49,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string CustomUserAgentMobile = "browserUserAgentWinTouchModeCustom";
internal const string PopupPolicy = "newBrowserWindowByLinkPolicy";
internal const string PopupBlockForeignHost = "newBrowserWindowByLinkBlockForeign";
internal const string QuitUrl = "quitURL";
internal const string QuitUrlConfirmation = "quitURLConfirm";
internal const string StartUrl = "startURL";
internal const string UserAgentModeDesktop = "browserUserAgentWinDesktopMode";
internal const string UserAgentModeMobile = "browserUserAgentWinTouchMode";
@ -143,52 +142,43 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal static class ConfigurationFile
{
internal const string AdminPasswordHash = "hashedAdminPassword";
internal const string ConfigurationPurpose = "sebConfigPurpose";
internal const string KeepClientConfigEncryption = "clientConfigKeepEncryption";
}
internal static class Exam
{
}
internal static class General
{
internal const string AdminPasswordHash = "hashedAdminPassword";
internal const string AllowApplicationLog = "allowApplicationLog";
internal const string LogLevel = "logLevel";
internal const string QuitPasswordHash = "hashedQuitPassword";
internal const string StartUrl = "startURL";
}
internal static class Input
internal static class Keyboard
{
internal static class Keyboard
{
internal const string EnableAltEsc = "enableAltEsc";
internal const string EnableAltTab = "enableAltTab";
internal const string EnableAltF4 = "enableAltF4";
internal const string EnableCtrlEsc = "enableCtrlEsc";
internal const string EnableEsc = "enableEsc";
internal const string EnableF1 = "enableF1";
internal const string EnableF2 = "enableF2";
internal const string EnableF3 = "enableF3";
internal const string EnableF4 = "enableF4";
internal const string EnableF5 = "enableF5";
internal const string EnableF6 = "enableF6";
internal const string EnableF7 = "enableF7";
internal const string EnableF8 = "enableF8";
internal const string EnableF9 = "enableF9";
internal const string EnableF10 = "enableF10";
internal const string EnableF11 = "enableF11";
internal const string EnableF12 = "enableF12";
internal const string EnablePrintScreen = "enablePrintScreen";
internal const string EnableSystemKey = "enableStartMenu";
}
internal const string EnableAltEsc = "enableAltEsc";
internal const string EnableAltTab = "enableAltTab";
internal const string EnableAltF4 = "enableAltF4";
internal const string EnableCtrlEsc = "enableCtrlEsc";
internal const string EnableEsc = "enableEsc";
internal const string EnableF1 = "enableF1";
internal const string EnableF2 = "enableF2";
internal const string EnableF3 = "enableF3";
internal const string EnableF4 = "enableF4";
internal const string EnableF5 = "enableF5";
internal const string EnableF6 = "enableF6";
internal const string EnableF7 = "enableF7";
internal const string EnableF8 = "enableF8";
internal const string EnableF9 = "enableF9";
internal const string EnableF10 = "enableF10";
internal const string EnableF11 = "enableF11";
internal const string EnableF12 = "enableF12";
internal const string EnablePrintScreen = "enablePrintScreen";
internal const string EnableSystemKey = "enableStartMenu";
}
internal static class Mouse
{
internal const string EnableRightMouse = "enableRightMouse";
}
internal static class Mouse
{
internal const string EnableRightMouse = "enableRightMouse";
}
internal static class Network
@ -201,14 +191,11 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
}
}
internal static class Registry
{
}
internal static class Security
{
internal const string KioskModeCreateNewDesktop = "createNewDesktop";
internal const string KioskModeDisableExplorerShell = "killExplorerShell";
internal const string QuitPasswordHash = "hashedQuitPassword";
internal const string ServicePolicy = "sebServicePolicy";
}

View file

@ -45,6 +45,8 @@ namespace SafeExamBrowser.I18n.Contracts
MessageBox_ApplicationTerminationFailureTitle,
MessageBox_BrowserNavigationBlocked,
MessageBox_BrowserNavigationBlockedTitle,
MessageBox_BrowserQuitUrlConfirmation,
MessageBox_BrowserQuitUrlConfirmationTitle,
MessageBox_CancelButton,
MessageBox_ClientConfigurationError,
MessageBox_ClientConfigurationErrorTitle,

View file

@ -93,6 +93,12 @@
<Entry key="MessageBox_BrowserNavigationBlockedTitle">
Page Blocked
</Entry>
<Entry key="MessageBox_BrowserQuitUrlConfirmation">
The browser application has detected a quit URL! Would you like to terminate SEB now?
</Entry>
<Entry key="MessageBox_BrowserQuitUrlConfirmationTitle">
Quit URL Detected
</Entry>
<Entry key="MessageBox_CancelButton">
Cancel
</Entry>

View file

@ -36,6 +36,11 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary>
public bool AllowPageZoom { get; set; }
/// <summary>
/// Determines whether the user needs to confirm the termination of SEB by <see cref="QuitUrl"/>.
/// </summary>
public bool ConfirmQuitUrl { get; set; }
/// <summary>
/// The custom user agent to optionally be used for all requests.
/// </summary>
@ -61,6 +66,11 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary>
public ProxySettings Proxy { get; set; }
/// <summary>
/// An URL which will initiate the termination of SEB if visited by the user.
/// </summary>
public string QuitUrl { get; set; }
/// <summary>
/// The URL with which the main browser window will be loaded.
/// </summary>