SEBWIN-453: Implemented monitoring for kiosk mode Create New Desktop.

This commit is contained in:
Damian Büchel 2021-04-16 19:12:56 +02:00
parent 68f4349a4d
commit 5d05acb6d7
8 changed files with 143 additions and 5 deletions

View file

@ -24,6 +24,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
private SessionConfiguration currentSession;
private AppSettings currentSettings;
private Mock<IDesktopFactory> desktopFactory;
private Mock<IDesktopMonitor> desktopMonitor;
private Mock<IExplorerShell> explorerShell;
private Mock<ILogger> logger;
private SessionConfiguration nextSession;
@ -39,6 +40,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
currentSession = new SessionConfiguration();
currentSettings = new AppSettings();
desktopFactory = new Mock<IDesktopFactory>();
desktopMonitor = new Mock<IDesktopMonitor>();
explorerShell = new Mock<IExplorerShell>();
logger = new Mock<ILogger>();
nextSession = new SessionConfiguration();
@ -51,7 +53,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
sessionContext.Current = currentSession;
sessionContext.Next = nextSession;
sut = new KioskModeOperation(desktopFactory.Object, explorerShell.Object, logger.Object, processFactory.Object, sessionContext);
sut = new KioskModeOperation(desktopFactory.Object, desktopMonitor.Object, explorerShell.Object, logger.Object, processFactory.Object, sessionContext);
}
[TestMethod]
@ -64,6 +66,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var createNew = 0;
var activate = 0;
var setStartup = 0;
var startMonitor = 0;
nextSettings.Security.KioskMode = KioskMode.CreateNewDesktop;
@ -71,6 +74,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
desktopFactory.Setup(f => f.CreateNew(It.IsAny<string>())).Callback(() => createNew = ++order).Returns(newDesktop.Object);
newDesktop.Setup(d => d.Activate()).Callback(() => activate = ++order);
processFactory.SetupSet(f => f.StartupDesktop = It.IsAny<IDesktop>()).Callback(() => setStartup = ++order);
desktopMonitor.Setup(m => m.Start(It.IsAny<IDesktop>())).Callback(() => startMonitor = ++order);
var result = sut.Perform();
@ -86,6 +90,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
Assert.AreEqual(2, createNew);
Assert.AreEqual(3, activate);
Assert.AreEqual(4, setStartup);
Assert.AreEqual(5, startMonitor);
}
[TestMethod]
@ -366,6 +371,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
desktopFactory.Verify(f => f.GetCurrent(), Times.Once);
desktopFactory.Verify(f => f.CreateNew(It.IsAny<string>()), Times.Once);
desktopMonitor.Verify(m => m.Start(It.IsAny<IDesktop>()), Times.Once);
desktopMonitor.Verify(m => m.Stop(), Times.Never);
explorerShell.VerifyNoOtherCalls();
newDesktop.Verify(d => d.Activate(), Times.Once);
newDesktop.Verify(d => d.Close(), Times.Never);
@ -403,6 +410,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
var order = 0;
var activate = 0;
var setStartup = 0;
var stopMonitor = 0;
var close = 0;
currentSettings.Security.KioskMode = KioskMode.CreateNewDesktop;
@ -415,6 +423,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
Assert.AreEqual(OperationResult.Success, performResult);
desktopFactory.Reset();
desktopMonitor.Setup(m => m.Stop()).Callback(() => stopMonitor = ++order);
explorerShell.Reset();
originalDesktop.Reset();
originalDesktop.Setup(d => d.Activate()).Callback(() => activate = ++order);
@ -432,9 +441,10 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
Assert.AreEqual(OperationResult.Success, performResult);
Assert.AreEqual(OperationResult.Success, revertResult);
Assert.AreEqual(1, activate);
Assert.AreEqual(2, setStartup);
Assert.AreEqual(3, close);
Assert.AreEqual(1, stopMonitor);
Assert.AreEqual(2, activate);
Assert.AreEqual(3, setStartup);
Assert.AreEqual(4, close);
}
[TestMethod]

View file

@ -62,6 +62,7 @@ namespace SafeExamBrowser.Runtime
var nativeMethods = new NativeMethods();
var uiFactory = new UserInterfaceFactory(text);
var desktopFactory = new DesktopFactory(ModuleLogger(nameof(DesktopFactory)));
var desktopMonitor = new DesktopMonitor(ModuleLogger(nameof(DesktopMonitor)));
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var fileSystem = new FileSystem();
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
@ -91,7 +92,7 @@ namespace SafeExamBrowser.Runtime
sessionOperations.Enqueue(new ServiceOperation(logger, runtimeHost, serviceProxy, sessionContext, THIRTY_SECONDS, userInfo));
sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
sessionOperations.Enqueue(new ProctoringWorkaroundOperation(logger, sessionContext));
sessionOperations.Enqueue(new KioskModeOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new KioskModeOperation(desktopFactory, desktopMonitor, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new ClientOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext));

View file

@ -20,6 +20,7 @@ namespace SafeExamBrowser.Runtime.Operations
private IDesktop newDesktop;
private IDesktop originalDesktop;
private IDesktopFactory desktopFactory;
private IDesktopMonitor desktopMonitor;
private IExplorerShell explorerShell;
private KioskMode? activeMode;
private ILogger logger;
@ -30,12 +31,14 @@ namespace SafeExamBrowser.Runtime.Operations
public KioskModeOperation(
IDesktopFactory desktopFactory,
IDesktopMonitor desktopMonitor,
IExplorerShell explorerShell,
ILogger logger,
IProcessFactory processFactory,
SessionContext sessionContext) : base(sessionContext)
{
this.desktopFactory = desktopFactory;
this.desktopMonitor = desktopMonitor;
this.explorerShell = explorerShell;
this.logger = logger;
this.processFactory = processFactory;
@ -129,10 +132,14 @@ namespace SafeExamBrowser.Runtime.Operations
newDesktop.Activate();
processFactory.StartupDesktop = newDesktop;
logger.Info("Successfully activated new desktop.");
desktopMonitor.Start(newDesktop);
}
private void CloseNewDesktop()
{
desktopMonitor.Stop();
if (originalDesktop != null)
{
originalDesktop.Activate();

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021 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.WindowsApi.Contracts
{
/// <summary>
/// Provides funcionality to monitor a desktop, i.e. ensure a given desktop remains active even when a desktop switch is performed.
/// </summary>
public interface IDesktopMonitor
{
/// <summary>
/// Starts to monitor the given desktop.
/// </summary>
void Start(IDesktop desktop);
/// <summary>
/// Stops the monitoring.
/// </summary>
void Stop();
}
}

View file

@ -67,6 +67,7 @@
<Compile Include="IBounds.cs" />
<Compile Include="IDesktop.cs" />
<Compile Include="IDesktopFactory.cs" />
<Compile Include="IDesktopMonitor.cs" />
<Compile Include="IExplorerShell.cs" />
<Compile Include="INativeMethods.cs" />
<Compile Include="IProcess.cs" />

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) 2021 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.Runtime.InteropServices;
using System.Timers;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.WindowsApi
{
public class DesktopMonitor : IDesktopMonitor
{
private readonly ILogger logger;
private readonly Timer timer;
private IDesktop desktop;
public DesktopMonitor(ILogger logger)
{
this.logger = logger;
this.timer = new Timer(1000);
}
public void Start(IDesktop desktop)
{
this.desktop = desktop;
timer.AutoReset = false;
timer.Elapsed += Timer_Elapsed;
timer.Start();
logger.Info($"Started monitoring desktop {desktop}.");
}
public void Stop()
{
timer.Stop();
timer.Elapsed -= Timer_Elapsed;
logger.Info($"Stopped monitoring desktop {desktop}.");
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
var handle = User32.OpenInputDesktop(0, false, (uint) AccessMask.DESKTOP_NONE);
var name = string.Empty;
var nameLength = 0;
if (handle != IntPtr.Zero)
{
User32.GetUserObjectInformation(handle, Constant.UOI_NAME, IntPtr.Zero, 0, ref nameLength);
var namePointer = Marshal.AllocHGlobal(nameLength);
var success = User32.GetUserObjectInformation(handle, Constant.UOI_NAME, namePointer, nameLength, ref nameLength);
if (success)
{
name = Marshal.PtrToStringAnsi(namePointer);
Marshal.FreeHGlobal(namePointer);
if (name?.Equals(desktop.Name, StringComparison.OrdinalIgnoreCase) != true)
{
logger.Warn($"Detected desktop switch to '{name}' [{handle}], trying to reactivate {desktop}...");
desktop.Activate();
}
}
else
{
logger.Warn("Failed to get name of currently active desktop!");
}
User32.CloseDesktop(handle);
}
else
{
logger.Warn("Failed to get currently active desktop!");
}
timer.Start();
}
}
}

View file

@ -65,6 +65,7 @@
<Compile Include="Delegates\HookDelegate.cs" />
<Compile Include="Desktop.cs" />
<Compile Include="DesktopFactory.cs" />
<Compile Include="DesktopMonitor.cs" />
<Compile Include="ExplorerShell.cs" />
<Compile Include="Hooks\MouseHook.cs" />
<Compile Include="Hooks\SystemHook.cs" />

View file

@ -77,6 +77,9 @@ namespace SafeExamBrowser.WindowsApi
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr OpenInputDesktop(uint dwFlags, bool fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", SetLastError = true)]
internal static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);