SEBWIN-220: Re-integrated system event hooks, now also running in separate threads. Decreased logging for EndpointNotFoundException in BaseProxy.

This commit is contained in:
dbuechel 2018-09-27 11:24:13 +02:00
parent b50c208f46
commit 67ba5fcce3
20 changed files with 308 additions and 121 deletions

View file

@ -9,6 +9,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Client.Operations; using SafeExamBrowser.Client.Operations;
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
@ -20,21 +21,58 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
private Mock<ILogger> loggerMock; private Mock<ILogger> loggerMock;
private Mock<IWindowMonitor> windowMonitorMock; private Mock<IWindowMonitor> windowMonitorMock;
private WindowMonitorOperation sut;
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
{ {
loggerMock = new Mock<ILogger>(); loggerMock = new Mock<ILogger>();
windowMonitorMock = new Mock<IWindowMonitor>(); windowMonitorMock = new Mock<IWindowMonitor>();
sut = new WindowMonitorOperation(loggerMock.Object, windowMonitorMock.Object);
} }
[TestMethod] [TestMethod]
public void MustPerformCorrectly() public void MustPerformCorrectlyForCreateNewDesktop()
{ {
var order = 0; var order = 0;
var hideAll = 0;
var startMonitoring = 0;
var sut = new WindowMonitorOperation(KioskMode.CreateNewDesktop, loggerMock.Object, windowMonitorMock.Object);
windowMonitorMock.Setup(w => w.HideAllWindows()).Callback(() => hideAll = ++order);
windowMonitorMock.Setup(w => w.StartMonitoringWindows()).Callback(() => startMonitoring = ++order);
sut.Perform();
windowMonitorMock.Verify(w => w.HideAllWindows(), Times.Never);
windowMonitorMock.Verify(w => w.StartMonitoringWindows(), Times.Once);
Assert.AreEqual(0, hideAll);
Assert.AreEqual(1, startMonitoring);
}
[TestMethod]
public void MustRevertCorrectlyForCreateNewDesktop()
{
var order = 0;
var stop = 0;
var restore = 0;
var sut = new WindowMonitorOperation(KioskMode.CreateNewDesktop, loggerMock.Object, windowMonitorMock.Object);
windowMonitorMock.Setup(w => w.StopMonitoringWindows()).Callback(() => stop = ++order);
windowMonitorMock.Setup(w => w.RestoreHiddenWindows()).Callback(() => restore = ++order);
sut.Revert();
windowMonitorMock.Verify(w => w.StopMonitoringWindows(), Times.Once);
windowMonitorMock.Verify(w => w.RestoreHiddenWindows(), Times.Never);
Assert.AreEqual(0, restore);
Assert.AreEqual(1, stop);
}
[TestMethod]
public void MustPerformCorrectlyForDisableExplorerShell()
{
var order = 0;
var sut = new WindowMonitorOperation(KioskMode.DisableExplorerShell, loggerMock.Object, windowMonitorMock.Object);
windowMonitorMock.Setup(w => w.HideAllWindows()).Callback(() => Assert.AreEqual(++order, 1)); windowMonitorMock.Setup(w => w.HideAllWindows()).Callback(() => Assert.AreEqual(++order, 1));
windowMonitorMock.Setup(w => w.StartMonitoringWindows()).Callback(() => Assert.AreEqual(++order, 2)); windowMonitorMock.Setup(w => w.StartMonitoringWindows()).Callback(() => Assert.AreEqual(++order, 2));
@ -46,9 +84,10 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
} }
[TestMethod] [TestMethod]
public void MustRevertCorrectly() public void MustRevertCorrectlyForDisableExplorerShell()
{ {
var order = 0; var order = 0;
var sut = new WindowMonitorOperation(KioskMode.DisableExplorerShell, loggerMock.Object, windowMonitorMock.Object);
windowMonitorMock.Setup(w => w.StopMonitoringWindows()).Callback(() => Assert.AreEqual(++order, 1)); windowMonitorMock.Setup(w => w.StopMonitoringWindows()).Callback(() => Assert.AreEqual(++order, 1));
windowMonitorMock.Setup(w => w.RestoreHiddenWindows()).Callback(() => Assert.AreEqual(++order, 2)); windowMonitorMock.Setup(w => w.RestoreHiddenWindows()).Callback(() => Assert.AreEqual(++order, 2));
@ -58,5 +97,13 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
windowMonitorMock.Verify(w => w.StopMonitoringWindows(), Times.Once); windowMonitorMock.Verify(w => w.StopMonitoringWindows(), Times.Once);
windowMonitorMock.Verify(w => w.RestoreHiddenWindows(), Times.Once); windowMonitorMock.Verify(w => w.RestoreHiddenWindows(), Times.Once);
} }
[TestMethod]
public void MustDoNothingWithoutKioskMode()
{
var sut = new WindowMonitorOperation(KioskMode.None, loggerMock.Object, windowMonitorMock.Object);
windowMonitorMock.VerifyNoOtherCalls();
}
} }
} }

View file

@ -307,17 +307,19 @@ namespace SafeExamBrowser.Client
private void WindowMonitor_WindowChanged(IntPtr window) private void WindowMonitor_WindowChanged(IntPtr window)
{ {
var allowed = processMonitor.BelongsToAllowedProcess(window); // TODO!
if (!allowed) //var allowed = processMonitor.BelongsToAllowedProcess(window);
{
var success = windowMonitor.Hide(window);
if (!success) //if (!allowed)
{ //{
windowMonitor.Close(window); // var success = windowMonitor.Hide(window);
}
} // if (!success)
// {
// windowMonitor.Close(window);
// }
//}
} }
} }
} }

View file

@ -63,6 +63,7 @@ namespace SafeExamBrowser.Client
private IText text; private IText text;
private ITextResource textResource; private ITextResource textResource;
private IUserInterfaceFactory uiFactory; private IUserInterfaceFactory uiFactory;
private IWindowMonitor windowMonitor;
internal IClientController ClientController { get; private set; } internal IClientController ClientController { get; private set; }
internal Taskbar Taskbar { get; private set; } internal Taskbar Taskbar { get; private set; }
@ -83,10 +84,10 @@ namespace SafeExamBrowser.Client
processMonitor = new ProcessMonitor(new ModuleLogger(logger, nameof(ProcessMonitor)), nativeMethods); processMonitor = new ProcessMonitor(new ModuleLogger(logger, nameof(ProcessMonitor)), nativeMethods);
uiFactory = new UserInterfaceFactory(text); uiFactory = new UserInterfaceFactory(text);
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(RuntimeProxy))); runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(RuntimeProxy)));
windowMonitor = new WindowMonitor(new ModuleLogger(logger, nameof(WindowMonitor)), nativeMethods);
var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, nameof(DisplayMonitor)), nativeMethods); var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, nameof(DisplayMonitor)), nativeMethods);
var explorerShell = new ExplorerShell(new ModuleLogger(logger, nameof(ExplorerShell)), nativeMethods); var explorerShell = new ExplorerShell(new ModuleLogger(logger, nameof(ExplorerShell)), nativeMethods);
var windowMonitor = new WindowMonitor(new ModuleLogger(logger, nameof(WindowMonitor)), nativeMethods);
Taskbar = new Taskbar(new ModuleLogger(logger, nameof(Taskbar))); Taskbar = new Taskbar(new ModuleLogger(logger, nameof(Taskbar)));
@ -98,8 +99,7 @@ namespace SafeExamBrowser.Client
operations.Enqueue(new DelegateOperation(UpdateAppConfig)); operations.Enqueue(new DelegateOperation(UpdateAppConfig));
operations.Enqueue(new LazyInitializationOperation(BuildCommunicationHostOperation)); operations.Enqueue(new LazyInitializationOperation(BuildCommunicationHostOperation));
operations.Enqueue(new LazyInitializationOperation(BuildKeyboardInterceptorOperation)); operations.Enqueue(new LazyInitializationOperation(BuildKeyboardInterceptorOperation));
// TODO operations.Enqueue(new LazyInitializationOperation(BuildWindowMonitorOperation));
//operations.Enqueue(new WindowMonitorOperation(logger, windowMonitor));
operations.Enqueue(new LazyInitializationOperation(BuildProcessMonitorOperation)); operations.Enqueue(new LazyInitializationOperation(BuildProcessMonitorOperation));
operations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, Taskbar)); operations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, Taskbar));
operations.Enqueue(new LazyInitializationOperation(BuildTaskbarOperation)); operations.Enqueue(new LazyInitializationOperation(BuildTaskbarOperation));
@ -223,6 +223,11 @@ namespace SafeExamBrowser.Client
return operation; return operation;
} }
private IOperation BuildWindowMonitorOperation()
{
return new WindowMonitorOperation(configuration.Settings.KioskMode, logger, windowMonitor);
}
private void UpdateAppConfig() private void UpdateAppConfig()
{ {
ClientController.AppConfig = configuration.AppConfig; ClientController.AppConfig = configuration.AppConfig;

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.I18n; using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -16,13 +17,15 @@ namespace SafeExamBrowser.Client.Operations
{ {
internal class WindowMonitorOperation : IOperation internal class WindowMonitorOperation : IOperation
{ {
private KioskMode kioskMode;
private ILogger logger; private ILogger logger;
private IWindowMonitor windowMonitor; private IWindowMonitor windowMonitor;
public IProgressIndicator ProgressIndicator { private get; set; } public IProgressIndicator ProgressIndicator { private get; set; }
public WindowMonitorOperation(ILogger logger, IWindowMonitor windowMonitor) public WindowMonitorOperation(KioskMode kioskMode, ILogger logger, IWindowMonitor windowMonitor)
{ {
this.kioskMode = kioskMode;
this.logger = logger; this.logger = logger;
this.windowMonitor = windowMonitor; this.windowMonitor = windowMonitor;
} }
@ -32,8 +35,15 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("Initializing window monitoring..."); logger.Info("Initializing window monitoring...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeWindowMonitoring); ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_InitializeWindowMonitoring);
windowMonitor.HideAllWindows(); if (kioskMode == KioskMode.DisableExplorerShell)
windowMonitor.StartMonitoringWindows(); {
windowMonitor.HideAllWindows();
}
if (kioskMode != KioskMode.None)
{
windowMonitor.StartMonitoringWindows();
}
return OperationResult.Success; return OperationResult.Success;
} }
@ -48,8 +58,15 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("Stopping window monitoring..."); logger.Info("Stopping window monitoring...");
ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopWindowMonitoring); ProgressIndicator?.UpdateText(TextKey.ProgressIndicator_StopWindowMonitoring);
windowMonitor.StopMonitoringWindows(); if (kioskMode != KioskMode.None)
windowMonitor.RestoreHiddenWindows(); {
windowMonitor.StopMonitoringWindows();
}
if (kioskMode == KioskMode.DisableExplorerShell)
{
windowMonitor.RestoreHiddenWindows();
}
} }
} }
} }

View file

@ -120,6 +120,24 @@ namespace SafeExamBrowser.Communication.UnitTests.Proxies
Assert.IsFalse(connected); Assert.IsFalse(connected);
} }
[TestMethod]
public void MustHandleMissingEndpointCorrectly()
{
var proxy = new Mock<IProxyObject>();
proxyObjectFactory.Setup(f => f.CreateObject(It.IsAny<string>())).Throws<EndpointNotFoundException>();
var token = Guid.NewGuid();
var connected = sut.Connect(token);
logger.Verify(l => l.Warn(It.IsAny<string>()), Times.AtLeastOnce());
logger.Verify(l => l.Error(It.IsAny<string>()), Times.Never());
logger.Verify(l => l.Error(It.IsAny<string>(), It.IsAny<Exception>()), Times.Never());
proxyObjectFactory.Verify(f => f.CreateObject(It.IsAny<string>()), Times.Once);
Assert.IsFalse(connected);
}
[TestMethod] [TestMethod]
public void MustFailToDisconnectIfNotConnected() public void MustFailToDisconnectIfNotConnected()
{ {

View file

@ -22,7 +22,7 @@ namespace SafeExamBrowser.Communication.Hosts
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)] [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
public abstract class BaseHost : ICommunication, ICommunicationHost public abstract class BaseHost : ICommunication, ICommunicationHost
{ {
private const int TWO_SECONDS = 2000; private const int FIVE_SECONDS = 5000;
private readonly object @lock = new object(); private readonly object @lock = new object();
private string address; private string address;
@ -137,11 +137,11 @@ namespace SafeExamBrowser.Communication.Hosts
hostThread.IsBackground = true; hostThread.IsBackground = true;
hostThread.Start(); hostThread.Start();
var success = startedEvent.WaitOne(TWO_SECONDS); var success = startedEvent.WaitOne(FIVE_SECONDS);
if (!success) if (!success)
{ {
throw new CommunicationException($"Failed to start communication host for endpoint '{address}' within {TWO_SECONDS / 1000} seconds!", exception); throw new CommunicationException($"Failed to start communication host for endpoint '{address}' within {FIVE_SECONDS / 1000} seconds!", exception);
} }
} }
} }
@ -202,7 +202,7 @@ namespace SafeExamBrowser.Communication.Hosts
try try
{ {
host?.Close(); host?.Close();
success = hostThread?.Join(TWO_SECONDS) == true; success = hostThread?.Join(FIVE_SECONDS) == true;
} }
catch (Exception e) catch (Exception e)
{ {

View file

@ -64,12 +64,16 @@ namespace SafeExamBrowser.Communication.Proxies
return success; return success;
} }
catch (EndpointNotFoundException)
{
Logger.Warn($"Endpoint '{address}' could not be found!");
}
catch (Exception e) catch (Exception e)
{ {
Logger.Error($"Failed to connect to endpoint '{address}'!", e); Logger.Error($"Failed to connect to endpoint '{address}'!", e);
return false;
} }
return false;
} }
public virtual bool Disconnect() public virtual bool Disconnect()
@ -209,7 +213,7 @@ namespace SafeExamBrowser.Communication.Proxies
private void BaseProxy_Faulted(object sender, EventArgs e) private void BaseProxy_Faulted(object sender, EventArgs e)
{ {
Logger.Error("Communication channel has faulted!"); Logger.Warn("Communication channel has faulted!");
} }
private void BaseProxy_Opened(object sender, EventArgs e) private void BaseProxy_Opened(object sender, EventArgs e)

View file

@ -181,6 +181,7 @@
<Compile Include="UserInterface\MessageBox\MessageBoxAction.cs" /> <Compile Include="UserInterface\MessageBox\MessageBoxAction.cs" />
<Compile Include="UserInterface\MessageBox\MessageBoxIcon.cs" /> <Compile Include="UserInterface\MessageBox\MessageBoxIcon.cs" />
<Compile Include="WindowsApi\Events\ProcessTerminatedEventHandler.cs" /> <Compile Include="WindowsApi\Events\ProcessTerminatedEventHandler.cs" />
<Compile Include="WindowsApi\Events\SystemEventCallback.cs" />
<Compile Include="WindowsApi\IBounds.cs" /> <Compile Include="WindowsApi\IBounds.cs" />
<Compile Include="WindowsApi\IDesktop.cs" /> <Compile Include="WindowsApi\IDesktop.cs" />
<Compile Include="WindowsApi\IDesktopFactory.cs" /> <Compile Include="WindowsApi\IDesktopFactory.cs" />

View file

@ -0,0 +1,17 @@
/*
* 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;
namespace SafeExamBrowser.Contracts.WindowsApi.Events
{
/// <summary>
/// Defines the callback for system event hooks.
/// </summary>
public delegate void SystemEventCallback(IntPtr window);
}

View file

@ -9,6 +9,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.WindowsApi.Events;
namespace SafeExamBrowser.Contracts.WindowsApi namespace SafeExamBrowser.Contracts.WindowsApi
{ {
@ -34,12 +35,12 @@ namespace SafeExamBrowser.Contracts.WindowsApi
void DeregisterMouseHook(IMouseInterceptor interceptor); void DeregisterMouseHook(IMouseInterceptor interceptor);
/// <summary> /// <summary>
/// Deregisters a previously registered system event. /// Deregisters a previously registered system event hook.
/// </summary> /// </summary>
/// <exception cref="System.ComponentModel.Win32Exception"> /// <exception cref="System.ComponentModel.Win32Exception">
/// If the event hook could not be successfully removed. /// If the event hook could not be successfully removed.
/// </exception> /// </exception>
void DeregisterSystemEvent(IntPtr handle); void DeregisterSystemEventHook(Guid hookId);
/// <summary> /// <summary>
/// Empties the clipboard. /// Empties the clipboard.
@ -128,17 +129,17 @@ namespace SafeExamBrowser.Contracts.WindowsApi
/// </summary> /// </summary>
void RegisterMouseHook(IMouseInterceptor interceptor); void RegisterMouseHook(IMouseInterceptor interceptor);
/// <summary>
/// Registers a system event which will invoke the specified callback when a window has received mouse capture.
/// Returns the ID of the newly registered Windows event hook.
/// </summary>
Guid RegisterSystemCaptureStartEvent(SystemEventCallback callback);
/// <summary> /// <summary>
/// Registers a system event which will invoke the specified callback when the foreground window has changed. /// Registers a system event which will invoke the specified callback when the foreground window has changed.
/// Returns a handle to the newly registered Windows event hook. /// Returns a handle to the newly registered Windows event hook.
/// </summary> /// </summary>
IntPtr RegisterSystemForegroundEvent(Action<IntPtr> callback); Guid RegisterSystemForegroundEvent(SystemEventCallback callback);
/// <summary>
/// Registers a system event which will invoke the specified callback when a window has received mouse capture.
/// Returns a handle to the newly registered Windows event hook.
/// </summary>
IntPtr RegisterSystemCaptureStartEvent(Action<IntPtr> callback);
/// <summary> /// <summary>
/// Removes the currently configured desktop wallpaper. /// Removes the currently configured desktop wallpaper.

View file

@ -35,9 +35,11 @@ namespace SafeExamBrowser.Logging
{ {
var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"); var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
var severity = message.Severity.ToString().ToUpper(); var severity = message.Severity.ToString().ToUpper();
var threadInfo = $"{message.ThreadInfo.Id}{(message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty)}"; var threadId = message.ThreadInfo.Id < 10 ? $"0{message.ThreadInfo.Id}" : message.ThreadInfo.Id.ToString();
var threadName = message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty;
var threadInfo = $"[{threadId}{threadName}]";
return $"{date} [{threadInfo}] - {severity}: {message.Message}"; return $"{date} {threadInfo} - {severity}: {message.Message}";
} }
} }
} }

View file

@ -17,8 +17,9 @@ namespace SafeExamBrowser.Monitoring.Windows
{ {
public class WindowMonitor : IWindowMonitor public class WindowMonitor : IWindowMonitor
{ {
private IntPtr captureStartHookHandle; private IntPtr activeWindow;
private IntPtr foregroundHookHandle; private Guid? captureHookId;
private Guid? foregroundHookId;
private ILogger logger; private ILogger logger;
private IList<Window> minimizedWindows = new List<Window>(); private IList<Window> minimizedWindows = new List<Window>();
private INativeMethods nativeMethods; private INativeMethods nativeMethods;
@ -92,31 +93,36 @@ namespace SafeExamBrowser.Monitoring.Windows
public void StartMonitoringWindows() public void StartMonitoringWindows()
{ {
captureStartHookHandle = nativeMethods.RegisterSystemCaptureStartEvent(OnWindowChanged); captureHookId = nativeMethods.RegisterSystemCaptureStartEvent(OnWindowChanged);
logger.Info($"Registered system capture start event with handle = {captureStartHookHandle}."); logger.Info($"Registered system capture start event with ID = {captureHookId}.");
foregroundHookHandle = nativeMethods.RegisterSystemForegroundEvent(OnWindowChanged); foregroundHookId = nativeMethods.RegisterSystemForegroundEvent(OnWindowChanged);
logger.Info($"Registered system foreground event with handle = {foregroundHookHandle}."); logger.Info($"Registered system foreground event with ID = {foregroundHookId}.");
} }
public void StopMonitoringWindows() public void StopMonitoringWindows()
{ {
if (captureStartHookHandle != IntPtr.Zero) if (captureHookId.HasValue)
{ {
nativeMethods.DeregisterSystemEvent(captureStartHookHandle); nativeMethods.DeregisterSystemEventHook(captureHookId.Value);
logger.Info($"Unregistered system capture start event with handle = {captureStartHookHandle}."); logger.Info($"Unregistered system capture start event with ID = {captureHookId}.");
} }
if (foregroundHookHandle != IntPtr.Zero) if (foregroundHookId.HasValue)
{ {
nativeMethods.DeregisterSystemEvent(foregroundHookHandle); nativeMethods.DeregisterSystemEventHook(foregroundHookId.Value);
logger.Info($"Unregistered system foreground event with handle = {foregroundHookHandle}."); logger.Info($"Unregistered system foreground event with ID = {foregroundHookId}.");
} }
} }
private void OnWindowChanged(IntPtr window) private void OnWindowChanged(IntPtr window)
{ {
WindowChanged?.Invoke(window); if (activeWindow != window)
{
logger.Debug($"Window has changed from {activeWindow} to {window}.");
activeWindow = window;
WindowChanged?.Invoke(window);
}
} }
} }
} }

View file

@ -41,7 +41,7 @@ namespace SafeExamBrowser.Runtime
internal void BuildObjectGraph(Action shutdown) internal void BuildObjectGraph(Action shutdown)
{ {
const int STARTUP_TIMEOUT_MS = 30000; const int STARTUP_TIMEOUT_MS = 15000;
var args = Environment.GetCommandLineArgs(); var args = Environment.GetCommandLineArgs();
var configuration = BuildConfigurationRepository(); var configuration = BuildConfigurationRepository();

View file

@ -139,7 +139,7 @@ namespace SafeExamBrowser.Runtime
runtimeWindow.Show(); runtimeWindow.Show();
runtimeWindow.BringToForeground(); runtimeWindow.BringToForeground();
runtimeWindow.ShowProgressBar(); runtimeWindow.ShowProgressBar();
logger.Info("Initiating session procedure..."); logger.Info("### --- Session Start Procedure --- ###");
if (sessionRunning) if (sessionRunning)
{ {
@ -152,7 +152,7 @@ namespace SafeExamBrowser.Runtime
{ {
RegisterSessionEvents(); RegisterSessionEvents();
logger.Info("Session is running."); logger.Info("### --- Session Running --- ###");
runtimeWindow.HideProgressBar(); runtimeWindow.HideProgressBar();
runtimeWindow.UpdateText(TextKey.RuntimeWindow_ApplicationRunning); runtimeWindow.UpdateText(TextKey.RuntimeWindow_ApplicationRunning);
runtimeWindow.TopMost = configuration.CurrentSettings.KioskMode != KioskMode.None; runtimeWindow.TopMost = configuration.CurrentSettings.KioskMode != KioskMode.None;
@ -166,7 +166,7 @@ namespace SafeExamBrowser.Runtime
} }
else else
{ {
logger.Info($"Session procedure {(result == OperationResult.Aborted ? "was aborted." : "has failed!")}"); logger.Info($"### --- Session Start {(result == OperationResult.Aborted ? "Aborted" : "Failed")} --- ###");
if (result == OperationResult.Failed) if (result == OperationResult.Failed)
{ {
@ -187,7 +187,7 @@ namespace SafeExamBrowser.Runtime
runtimeWindow.Show(); runtimeWindow.Show();
runtimeWindow.BringToForeground(); runtimeWindow.BringToForeground();
runtimeWindow.ShowProgressBar(); runtimeWindow.ShowProgressBar();
logger.Info("Reverting session operations..."); logger.Info("### --- Session Stop Procedure --- ###");
DeregisterSessionEvents(); DeregisterSessionEvents();
@ -195,12 +195,12 @@ namespace SafeExamBrowser.Runtime
if (success) if (success)
{ {
logger.Info("Session is terminated."); logger.Info("### --- Session Terminated --- ###");
sessionRunning = false; sessionRunning = false;
} }
else else
{ {
logger.Info("Session reversion was erroneous!"); logger.Info("### --- Session Stop Failed --- ###");
messageBox.Show(TextKey.MessageBox_SessionStopError, TextKey.MessageBox_SessionStopErrorTitle, icon: MessageBoxIcon.Error); messageBox.Show(TextKey.MessageBox_SessionStopError, TextKey.MessageBox_SessionStopErrorTitle, icon: MessageBoxIcon.Error);
} }
} }

View file

@ -70,9 +70,11 @@ namespace SafeExamBrowser.UserInterface.Classic.ViewModels
{ {
var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"); var date = message.DateTime.ToString("yyyy-MM-dd HH:mm:ss.fff");
var severity = message.Severity.ToString().ToUpper(); var severity = message.Severity.ToString().ToUpper();
var threadInfo = $"{message.ThreadInfo.Id}{(message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty)}"; var threadId = message.ThreadInfo.Id < 10 ? $"0{message.ThreadInfo.Id}" : message.ThreadInfo.Id.ToString();
var threadName = message.ThreadInfo.HasName ? ": " + message.ThreadInfo.Name : string.Empty;
var threadInfo = $"[{threadId}{threadName}]";
var infoRun = new Run($"{date} [{threadInfo}] - ") { Foreground = Brushes.Gray }; var infoRun = new Run($"{date} {threadInfo} - ") { Foreground = Brushes.Gray };
var messageRun = new Run($"{severity}: {message.Message}{Environment.NewLine}") { Foreground = GetBrushFor(message.Severity) }; var messageRun = new Run($"{severity}: {message.Message}{Environment.NewLine}") { Foreground = GetBrushFor(message.Severity) };
textBlock.Inlines.Add(infoRun); textBlock.Inlines.Add(infoRun);

View file

@ -8,7 +8,6 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Delegates; using SafeExamBrowser.WindowsApi.Delegates;
@ -25,15 +24,13 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
private const int DELETE = 46; private const int DELETE = 46;
private bool altPressed, ctrlPressed; private bool altPressed, ctrlPressed;
private HookDelegate hookProc; private HookDelegate hookDelegate;
internal IntPtr Handle { get; private set; } internal IntPtr Handle { get; private set; }
internal AutoResetEvent InputEvent { get; private set; }
internal IKeyboardInterceptor Interceptor { get; private set; } internal IKeyboardInterceptor Interceptor { get; private set; }
internal KeyboardHook(IKeyboardInterceptor interceptor) internal KeyboardHook(IKeyboardInterceptor interceptor)
{ {
InputEvent = new AutoResetEvent(false);
Interceptor = interceptor; Interceptor = interceptor;
} }
@ -46,9 +43,9 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
// IMORTANT: // IMORTANT:
// Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash! // Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
hookProc = new HookDelegate(LowLevelKeyboardProc); hookDelegate = new HookDelegate(LowLevelKeyboardProc);
Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookProc, moduleHandle, 0); Handle = User32.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, hookDelegate, moduleHandle, 0);
} }
internal bool Detach() internal bool Detach()
@ -58,8 +55,6 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam) private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
{ {
InputEvent.Set();
if (nCode >= 0) if (nCode >= 0)
{ {
var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT)); var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));

View file

@ -8,7 +8,6 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Delegates; using SafeExamBrowser.WindowsApi.Delegates;
@ -18,15 +17,13 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
{ {
internal class MouseHook internal class MouseHook
{ {
private HookDelegate hookProc; private HookDelegate hookDelegate;
internal IntPtr Handle { get; private set; } internal IntPtr Handle { get; private set; }
internal AutoResetEvent InputEvent { get; private set; }
internal IMouseInterceptor Interceptor { get; private set; } internal IMouseInterceptor Interceptor { get; private set; }
internal MouseHook(IMouseInterceptor interceptor) internal MouseHook(IMouseInterceptor interceptor)
{ {
InputEvent = new AutoResetEvent(false);
Interceptor = interceptor; Interceptor = interceptor;
} }
@ -39,9 +36,9 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
// IMORTANT: // IMORTANT:
// Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. // Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash! // Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
hookProc = new HookDelegate(LowLevelMouseProc); hookDelegate = new HookDelegate(LowLevelMouseProc);
Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookProc, moduleHandle, 0); Handle = User32.SetWindowsHookEx(HookType.WH_MOUSE_LL, hookDelegate, moduleHandle, 0);
} }
internal bool Detach() internal bool Detach()
@ -51,8 +48,6 @@ namespace SafeExamBrowser.WindowsApi.Monitoring
private IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam) private IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{ {
InputEvent.Set();
if (nCode >= 0 && !Ignore(wParam.ToInt32())) if (nCode >= 0 && !Ignore(wParam.ToInt32()))
{ {
var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));

View file

@ -0,0 +1,67 @@
/*
* 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 System.Threading;
using SafeExamBrowser.Contracts.WindowsApi.Events;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Delegates;
namespace SafeExamBrowser.WindowsApi.Monitoring
{
internal class SystemHook
{
private SystemEventCallback callback;
private AutoResetEvent detachEvent, detachResultAvailableEvent;
private bool detachSuccess;
private EventDelegate eventDelegate;
private uint eventId;
internal IntPtr Handle { get; private set; }
internal Guid Id { get; private set; }
public SystemHook(SystemEventCallback callback, uint eventId)
{
this.callback = callback;
this.detachEvent = new AutoResetEvent(false);
this.detachResultAvailableEvent = new AutoResetEvent(false);
this.eventId = eventId;
this.Id = Guid.NewGuid();
}
internal void Attach()
{
// IMORTANT:
// Ensures that the hook delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
eventDelegate = new EventDelegate(LowLevelSystemProc);
Handle = User32.SetWinEventHook(eventId, eventId, IntPtr.Zero, eventDelegate, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);
}
internal void AwaitDetach()
{
detachEvent.WaitOne();
detachSuccess = User32.UnhookWinEvent(Handle);
detachResultAvailableEvent.Set();
}
internal bool Detach()
{
detachEvent.Set();
detachResultAvailableEvent.WaitOne();
return detachSuccess;
}
private void LowLevelSystemProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
callback(hwnd);
}
}
}

View file

@ -16,8 +16,8 @@ using System.Text;
using System.Threading; using System.Threading;
using SafeExamBrowser.Contracts.Monitoring; using SafeExamBrowser.Contracts.Monitoring;
using SafeExamBrowser.Contracts.WindowsApi; using SafeExamBrowser.Contracts.WindowsApi;
using SafeExamBrowser.Contracts.WindowsApi.Events;
using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Delegates;
using SafeExamBrowser.WindowsApi.Monitoring; using SafeExamBrowser.WindowsApi.Monitoring;
using SafeExamBrowser.WindowsApi.Types; using SafeExamBrowser.WindowsApi.Types;
@ -25,28 +25,28 @@ namespace SafeExamBrowser.WindowsApi
{ {
public class NativeMethods : INativeMethods public class NativeMethods : INativeMethods
{ {
private ConcurrentDictionary<IntPtr, EventDelegate> EventDelegates = new ConcurrentDictionary<IntPtr, EventDelegate>();
private ConcurrentDictionary<IntPtr, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<IntPtr, KeyboardHook>(); private ConcurrentDictionary<IntPtr, KeyboardHook> KeyboardHooks = new ConcurrentDictionary<IntPtr, KeyboardHook>();
private ConcurrentDictionary<IntPtr, MouseHook> MouseHooks = new ConcurrentDictionary<IntPtr, MouseHook>(); private ConcurrentDictionary<IntPtr, MouseHook> MouseHooks = new ConcurrentDictionary<IntPtr, MouseHook>();
private ConcurrentDictionary<IntPtr, SystemHook> SystemHooks = new ConcurrentDictionary<IntPtr, SystemHook>();
/// <summary> /// <summary>
/// Upon finalization, unregister all active system events and hooks... /// Upon finalization, unregister all active system events and hooks...
/// </summary> /// </summary>
~NativeMethods() ~NativeMethods()
{ {
foreach (var handle in EventDelegates.Keys) foreach (var hook in SystemHooks.Values)
{ {
User32.UnhookWinEvent(handle); hook.Detach();
} }
foreach (var handle in KeyboardHooks.Keys) foreach (var hook in KeyboardHooks.Values)
{ {
User32.UnhookWindowsHookEx(handle); hook.Detach();
} }
foreach (var handle in MouseHooks.Keys) foreach (var hook in MouseHooks.Values)
{ {
User32.UnhookWindowsHookEx(handle); hook.Detach();
} }
} }
@ -63,7 +63,7 @@ namespace SafeExamBrowser.WindowsApi
throw new Win32Exception(Marshal.GetLastWin32Error()); throw new Win32Exception(Marshal.GetLastWin32Error());
} }
KeyboardHooks.TryRemove(hook.Handle, out KeyboardHook h); KeyboardHooks.TryRemove(hook.Handle, out _);
} }
} }
@ -80,20 +80,25 @@ namespace SafeExamBrowser.WindowsApi
throw new Win32Exception(Marshal.GetLastWin32Error()); throw new Win32Exception(Marshal.GetLastWin32Error());
} }
MouseHooks.TryRemove(hook.Handle, out MouseHook h); MouseHooks.TryRemove(hook.Handle, out _);
} }
} }
public void DeregisterSystemEvent(IntPtr handle) public void DeregisterSystemEventHook(Guid hookId)
{ {
var success = User32.UnhookWinEvent(handle); var hook = SystemHooks.Values.FirstOrDefault(h => h.Id == hookId);
if (!success) if (hook != null)
{ {
throw new Win32Exception(Marshal.GetLastWin32Error()); var success = hook.Detach();
}
EventDelegates.TryRemove(handle, out EventDelegate d); if (!success)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
SystemHooks.TryRemove(hook.Handle, out _);
}
} }
public void EmptyClipboard() public void EmptyClipboard()
@ -236,6 +241,7 @@ namespace SafeExamBrowser.WindowsApi
var hookThread = new Thread(() => var hookThread = new Thread(() =>
{ {
var hook = new KeyboardHook(interceptor); var hook = new KeyboardHook(interceptor);
var sleepEvent = new AutoResetEvent(false);
hook.Attach(); hook.Attach();
KeyboardHooks[hook.Handle] = hook; KeyboardHooks[hook.Handle] = hook;
@ -243,7 +249,7 @@ namespace SafeExamBrowser.WindowsApi
while (true) while (true)
{ {
hook.InputEvent.WaitOne(); sleepEvent.WaitOne();
} }
}); });
@ -260,6 +266,7 @@ namespace SafeExamBrowser.WindowsApi
var hookThread = new Thread(() => var hookThread = new Thread(() =>
{ {
var hook = new MouseHook(interceptor); var hook = new MouseHook(interceptor);
var sleepEvent = new AutoResetEvent(false);
hook.Attach(); hook.Attach();
MouseHooks[hook.Handle] = hook; MouseHooks[hook.Handle] = hook;
@ -267,7 +274,7 @@ namespace SafeExamBrowser.WindowsApi
while (true) while (true)
{ {
hook.InputEvent.WaitOne(); sleepEvent.WaitOne();
} }
}); });
@ -278,38 +285,38 @@ namespace SafeExamBrowser.WindowsApi
hookReadyEvent.WaitOne(); hookReadyEvent.WaitOne();
} }
public IntPtr RegisterSystemForegroundEvent(Action<IntPtr> callback) public Guid RegisterSystemCaptureStartEvent(SystemEventCallback callback)
{ {
void evenDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) return RegisterSystemEvent(callback, Constant.EVENT_SYSTEM_CAPTURESTART);
{
callback(hwnd);
}
var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_FOREGROUND, Constant.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, evenDelegate, 0, 0, Constant.WINEVENT_OUTOFCONTEXT);
// IMORTANT:
// Ensures that the event delegate does not get garbage collected prematurely, as it will be passed to unmanaged code.
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash!
EventDelegates[handle] = evenDelegate;
return handle;
} }
public IntPtr RegisterSystemCaptureStartEvent(Action<IntPtr> callback) public Guid RegisterSystemForegroundEvent(SystemEventCallback callback)
{ {
void eventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) return RegisterSystemEvent(callback, Constant.EVENT_SYSTEM_FOREGROUND);
}
internal Guid RegisterSystemEvent(SystemEventCallback callback, uint eventId)
{
var hookId = default(Guid);
var hookReadyEvent = new AutoResetEvent(false);
var hookThread = new Thread(() =>
{ {
callback(hwnd); var hook = new SystemHook(callback, eventId);
}
var handle = User32.SetWinEventHook(Constant.EVENT_SYSTEM_CAPTURESTART, Constant.EVENT_SYSTEM_CAPTURESTART, IntPtr.Zero, eventDelegate, 0, 0, Constant.WINEVENT_OUTOFCONTEXT); hook.Attach();
hookId = hook.Id;
SystemHooks[hook.Handle] = hook;
hookReadyEvent.Set();
hook.AwaitDetach();
});
// IMORTANT: hookThread.SetApartmentState(ApartmentState.STA);
// Ensures that the event delegate does not get garbage collected prematurely, as it will be passed to unmanaged code. hookThread.IsBackground = true;
// Not doing so will result in a <c>CallbackOnCollectedDelegate</c> error and subsequent application crash! hookThread.Start();
EventDelegates[handle] = eventDelegate;
return handle; hookReadyEvent.WaitOne();
return hookId;
} }
public void RemoveWallpaper() public void RemoveWallpaper()

View file

@ -62,6 +62,7 @@
<Compile Include="DesktopFactory.cs" /> <Compile Include="DesktopFactory.cs" />
<Compile Include="ExplorerShell.cs" /> <Compile Include="ExplorerShell.cs" />
<Compile Include="Monitoring\MouseHook.cs" /> <Compile Include="Monitoring\MouseHook.cs" />
<Compile Include="Monitoring\SystemHook.cs" />
<Compile Include="Process.cs" /> <Compile Include="Process.cs" />
<Compile Include="ProcessFactory.cs" /> <Compile Include="ProcessFactory.cs" />
<Compile Include="Constants\ACCESS_MASK.cs" /> <Compile Include="Constants\ACCESS_MASK.cs" />