SEBWIN-313: Finished blacklist monitoring.

This commit is contained in:
dbuechel 2019-10-09 14:04:27 +02:00
parent 99aa595543
commit de6cb5e75c
10 changed files with 151 additions and 75 deletions

View file

@ -7,6 +7,7 @@
*/ */
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using SafeExamBrowser.Browser.Contracts; using SafeExamBrowser.Browser.Contracts;
@ -170,6 +171,7 @@ namespace SafeExamBrowser.Client
{ {
actionCenter.QuitButtonClicked += Shell_QuitButtonClicked; actionCenter.QuitButtonClicked += Shell_QuitButtonClicked;
applicationMonitor.ExplorerStarted += ApplicationMonitor_ExplorerStarted; applicationMonitor.ExplorerStarted += ApplicationMonitor_ExplorerStarted;
applicationMonitor.TerminationFailed += ApplicationMonitor_TerminationFailed;
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested; Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested; ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested; ClientHost.PasswordRequested += ClientHost_PasswordRequested;
@ -185,6 +187,7 @@ namespace SafeExamBrowser.Client
{ {
actionCenter.QuitButtonClicked -= Shell_QuitButtonClicked; actionCenter.QuitButtonClicked -= Shell_QuitButtonClicked;
applicationMonitor.ExplorerStarted -= ApplicationMonitor_ExplorerStarted; applicationMonitor.ExplorerStarted -= ApplicationMonitor_ExplorerStarted;
applicationMonitor.TerminationFailed -= ApplicationMonitor_TerminationFailed;
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged; displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
runtime.ConnectionLost -= Runtime_ConnectionLost; runtime.ConnectionLost -= Runtime_ConnectionLost;
taskbar.QuitButtonClicked -= Shell_QuitButtonClicked; taskbar.QuitButtonClicked -= Shell_QuitButtonClicked;
@ -230,6 +233,13 @@ namespace SafeExamBrowser.Client
logger.Info("Desktop successfully restored."); logger.Info("Desktop successfully restored.");
} }
private void ApplicationMonitor_TerminationFailed(IEnumerable<RunningApplication> applications)
{
// foreach actionCenterActivator -> Pause
// TODO: Show lock screen!
// foreach actionCenterActivator -> Resume
}
private void Browser_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args) private void Browser_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
{ {
if (Settings.ConfigurationMode == ConfigurationMode.ConfigureClient) if (Settings.ConfigurationMode == ConfigurationMode.ConfigureClient)

View file

@ -52,6 +52,7 @@ namespace SafeExamBrowser.Client
{ {
internal class CompositionRoot internal class CompositionRoot
{ {
private const int TWO_SECONDS = 2000;
private const int FIVE_SECONDS = 5000; private const int FIVE_SECONDS = 5000;
private Guid authenticationToken; private Guid authenticationToken;
@ -94,7 +95,7 @@ namespace SafeExamBrowser.Client
taskbar = BuildTaskbar(); taskbar = BuildTaskbar();
terminationActivator = new TerminationActivator(ModuleLogger(nameof(TerminationActivator))); terminationActivator = new TerminationActivator(ModuleLogger(nameof(TerminationActivator)));
var applicationMonitor = new ApplicationMonitor(FIVE_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, new ProcessFactory(ModuleLogger(nameof(ProcessFactory)))); var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, new ProcessFactory(ModuleLogger(nameof(ProcessFactory))));
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo); var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods); var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var hashAlgorithm = new HashAlgorithm(); var hashAlgorithm = new HashAlgorithm();

View file

@ -14,6 +14,7 @@ using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications; using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Applications; using SafeExamBrowser.Settings.Applications;
namespace SafeExamBrowser.Client.Operations namespace SafeExamBrowser.Client.Operations
@ -109,18 +110,18 @@ namespace SafeExamBrowser.Client.Operations
private void StartMonitor() private void StartMonitor()
{ {
//TODO: if (Context.Settings.KioskMode != KioskMode.None) if (Context.Settings.KioskMode != KioskMode.None)
//{ {
applicationMonitor.Start(); applicationMonitor.Start();
//} }
} }
private void StopMonitor() private void StopMonitor()
{ {
//TODO: if (Context.Settings.KioskMode != KioskMode.None) if (Context.Settings.KioskMode != KioskMode.None)
//{ {
applicationMonitor.Stop(); applicationMonitor.Stop();
//} }
} }
private OperationResult TryTerminate(IEnumerable<RunningApplication> runningApplications) private OperationResult TryTerminate(IEnumerable<RunningApplication> runningApplications)

View file

@ -9,7 +9,7 @@
namespace SafeExamBrowser.Monitoring.Contracts.Applications.Events namespace SafeExamBrowser.Monitoring.Contracts.Applications.Events
{ {
/// <summary> /// <summary>
/// Indicates that the Windows explorer process has started. /// Indicates that the Windows Explorer has been started.
/// </summary> /// </summary>
public delegate void ExplorerStartedEventHandler(); public delegate void ExplorerStartedEventHandler();
} }

View file

@ -0,0 +1,17 @@
/*
* 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/.
*/
using System.Collections.Generic;
namespace SafeExamBrowser.Monitoring.Contracts.Applications.Events
{
/// <summary>
/// Indicates that the given blacklisted applications could not be terminated.
/// </summary>
public delegate void TerminationFailedEventHandler(IEnumerable<RunningApplication> applications);
}

View file

@ -21,6 +21,11 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
/// </summary> /// </summary>
event ExplorerStartedEventHandler ExplorerStarted; event ExplorerStartedEventHandler ExplorerStarted;
/// <summary>
/// Event fired when the automatic termination of a blacklisted application failed.
/// </summary>
event TerminationFailedEventHandler TerminationFailed;
/// <summary> /// <summary>
/// Initializes the application monitor. /// Initializes the application monitor.
/// </summary> /// </summary>

View file

@ -53,6 +53,7 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Applications\Events\TerminationFailedEventHandler.cs" />
<Compile Include="Applications\RunningApplication.cs" /> <Compile Include="Applications\RunningApplication.cs" />
<Compile Include="Display\Events\DisplayChangedEventHandler.cs" /> <Compile Include="Display\Events\DisplayChangedEventHandler.cs" />
<Compile Include="Applications\Events\ExplorerStartedEventHandler.cs" /> <Compile Include="Applications\Events\ExplorerStartedEventHandler.cs" />

View file

@ -9,6 +9,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Timers; using System.Timers;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications; using SafeExamBrowser.Monitoring.Contracts.Applications;
@ -32,6 +33,7 @@ namespace SafeExamBrowser.Monitoring.Applications
private IList<WhitelistApplication> whitelist; private IList<WhitelistApplication> whitelist;
public event ExplorerStartedEventHandler ExplorerStarted; public event ExplorerStartedEventHandler ExplorerStarted;
public event TerminationFailedEventHandler TerminationFailed;
public ApplicationMonitor(int interval_ms, ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory) public ApplicationMonitor(int interval_ms, ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
{ {
@ -133,6 +135,74 @@ namespace SafeExamBrowser.Monitoring.Applications
return success; return success;
} }
private void SystemEvent_WindowChanged(IntPtr window)
{
if (window != IntPtr.Zero && activeWindow != window)
{
logger.Debug($"Window has changed from {activeWindow} to {window}.");
activeWindow = window;
Task.Run(() =>
{
if (!IsAllowed(window) && !TryHide(window))
{
Close(window);
}
});
}
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
var failed = new List<RunningApplication>();
var running = processFactory.GetAllRunning();
var started = running.Where(r => processes.All(p => p.Id != r.Id)).ToList();
var terminated = processes.Where(p => running.All(r => r.Id != p.Id)).ToList();
foreach (var process in started)
{
logger.Debug($"Process {process} has been started.");
processes.Add(process);
if (process.Name == "explorer")
{
HandleExplorerStart(process);
}
else if (!IsAllowed(process) && !TryTerminate(process))
{
AddFailed(process, failed);
}
}
foreach (var process in terminated)
{
logger.Debug($"Process {process} has been terminated.");
processes.Remove(process);
}
if (failed.Any())
{
logger.Warn($"Failed to terminate these blacklisted applications: {string.Join(", ", failed.Select(a => a.Name))}.");
TerminationFailed?.Invoke(failed);
}
timer.Start();
}
private void AddFailed(IProcess process, List<RunningApplication> failed)
{
var name = blacklist.First(a => BelongsToApplication(process, a)).ExecutableName;
var application = failed.FirstOrDefault(a => a.Name == name);
if (application == default(RunningApplication))
{
application = new RunningApplication(name);
failed.Add(application);
}
application.Processes.Add(process);
}
private void AddFailed(string name, IProcess process, InitializationResult result) private void AddFailed(string name, IProcess process, InitializationResult result)
{ {
var application = result.FailedAutoTerminations.FirstOrDefault(a => a.Name == name); var application = result.FailedAutoTerminations.FirstOrDefault(a => a.Name == name);
@ -169,21 +239,6 @@ namespace SafeExamBrowser.Monitoring.Applications
return sameName || sameOriginalName; return sameName || sameOriginalName;
} }
private void Check(IntPtr window)
{
var allowed = IsAllowed(window);
if (!allowed)
{
var success = TryHide(window);
if (!success)
{
Close(window);
}
}
}
private void Close(IntPtr window) private void Close(IntPtr window)
{ {
var title = nativeMethods.GetWindowTitle(window); var title = nativeMethods.GetWindowTitle(window);
@ -192,6 +247,23 @@ namespace SafeExamBrowser.Monitoring.Applications
logger.Info($"Sent close message to window '{title}' with handle = {window}."); logger.Info($"Sent close message to window '{title}' with handle = {window}.");
} }
private void HandleExplorerStart(IProcess process)
{
logger.Warn($"A new instance of Windows Explorer {process} has been started!");
if (!TryTerminate(process))
{
var application = new RunningApplication("Windows Explorer");
logger.Error("Failed to terminate new Windows Explorer instance!");
application.Processes.Add(process);
Task.Run(() => TerminationFailed?.Invoke(new[] { application }));
}
Task.Run(() => ExplorerStarted?.Invoke());
}
private bool IsAllowed(IntPtr window) private bool IsAllowed(IntPtr window)
{ {
var processId = nativeMethods.GetProcessIdFor(window); var processId = nativeMethods.GetProcessIdFor(window);
@ -213,6 +285,21 @@ namespace SafeExamBrowser.Monitoring.Applications
return true; return true;
} }
private bool IsAllowed(IProcess process)
{
foreach (var application in blacklist)
{
if (BelongsToApplication(process, application))
{
logger.Warn($"Process {process} belongs to blacklisted application '{application.ExecutableName}'!");
return false;
}
}
return true;
}
private bool TryHide(IntPtr window) private bool TryHide(IntPtr window)
{ {
var title = nativeMethods.GetWindowTitle(window); var title = nativeMethods.GetWindowTitle(window);
@ -265,51 +352,5 @@ namespace SafeExamBrowser.Monitoring.Applications
return process.HasTerminated; return process.HasTerminated;
} }
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
var running = processFactory.GetAllRunning();
var started = running.Where(r => processes.All(p => p.Id != r.Id)).ToList();
var terminated = processes.Where(p => running.All(r => r.Id != p.Id)).ToList();
foreach (var process in started)
{
logger.Debug($"Process {process} has been started.");
processes.Add(process);
foreach (var application in blacklist)
{
if (BelongsToApplication(process, application))
{
logger.Warn($"Process {process} belongs to blacklisted application '{application.ExecutableName}'! Attempting termination...");
var success = TryTerminate(process);
if (!success)
{
// TODO: Invoke event -> Show lock screen!
}
}
}
}
foreach (var process in terminated)
{
logger.Debug($"Process {process} has been terminated.");
processes.Remove(process);
}
timer.Start();
}
private void SystemEvent_WindowChanged(IntPtr window)
{
if (window != IntPtr.Zero && activeWindow != window)
{
logger.Debug($"Window has changed from {activeWindow} to {window}.");
activeWindow = window;
Check(window);
}
}
} }
} }

View file

@ -172,7 +172,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
proxy.Verify(p => p.InitiateShutdown(), Times.Once); proxy.Verify(p => p.InitiateShutdown(), Times.Once);
proxy.Verify(p => p.Disconnect(), Times.Once); proxy.Verify(p => p.Disconnect(), Times.Once);
process.Verify(p => p.TryKill(default(int)), Times.Never); process.Verify(p => p.TryKill(It.IsAny<int>()), Times.Never);
Assert.IsNull(sessionContext.ClientProcess); Assert.IsNull(sessionContext.ClientProcess);
Assert.IsNull(sessionContext.ClientProxy); Assert.IsNull(sessionContext.ClientProxy);
@ -181,12 +181,12 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod] [TestMethod]
public void Revert_MustKillClientIfStoppingFailed() public void Revert_MustKillClientIfStoppingFailed()
{ {
process.Setup(p => p.TryKill(default(int))).Callback(() => process.SetupGet(p => p.HasTerminated).Returns(true)); process.Setup(p => p.TryKill(It.IsAny<int>())).Callback(() => process.SetupGet(p => p.HasTerminated).Returns(true));
PerformNormally(); PerformNormally();
sut.Revert(); sut.Revert();
process.Verify(p => p.TryKill(default(int)), Times.AtLeastOnce); process.Verify(p => p.TryKill(It.IsAny<int>()), Times.AtLeastOnce);
Assert.IsNull(sessionContext.ClientProcess); Assert.IsNull(sessionContext.ClientProcess);
Assert.IsNull(sessionContext.ClientProxy); Assert.IsNull(sessionContext.ClientProxy);
@ -198,7 +198,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
PerformNormally(); PerformNormally();
sut.Revert(); sut.Revert();
process.Verify(p => p.TryKill(default(int)), Times.Exactly(5)); process.Verify(p => p.TryKill(It.IsAny<int>()), Times.Exactly(5));
Assert.IsNotNull(sessionContext.ClientProcess); Assert.IsNotNull(sessionContext.ClientProcess);
Assert.IsNotNull(sessionContext.ClientProxy); Assert.IsNotNull(sessionContext.ClientProxy);
@ -213,7 +213,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
proxy.Verify(p => p.InitiateShutdown(), Times.Never); proxy.Verify(p => p.InitiateShutdown(), Times.Never);
proxy.Verify(p => p.Disconnect(), Times.Never); proxy.Verify(p => p.Disconnect(), Times.Never);
process.Verify(p => p.TryKill(default(int)), Times.Never); process.Verify(p => p.TryKill(It.IsAny<int>()), Times.Never);
Assert.IsNull(sessionContext.ClientProcess); Assert.IsNull(sessionContext.ClientProcess);
Assert.IsNull(sessionContext.ClientProxy); Assert.IsNull(sessionContext.ClientProxy);

View file

@ -248,7 +248,7 @@ namespace SafeExamBrowser.Runtime.Operations
{ {
logger.Info($"Attempt {attempt}/{MAX_ATTEMPTS} to kill client process with ID = {ClientProcess.Id}."); logger.Info($"Attempt {attempt}/{MAX_ATTEMPTS} to kill client process with ID = {ClientProcess.Id}.");
if (ClientProcess.TryKill()) if (ClientProcess.TryKill(500))
{ {
break; break;
} }