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.Collections.Generic;
using System.IO;
using System.Linq;
using SafeExamBrowser.Browser.Contracts;
@ -170,6 +171,7 @@ namespace SafeExamBrowser.Client
{
actionCenter.QuitButtonClicked += Shell_QuitButtonClicked;
applicationMonitor.ExplorerStarted += ApplicationMonitor_ExplorerStarted;
applicationMonitor.TerminationFailed += ApplicationMonitor_TerminationFailed;
Browser.ConfigurationDownloadRequested += Browser_ConfigurationDownloadRequested;
ClientHost.MessageBoxRequested += ClientHost_MessageBoxRequested;
ClientHost.PasswordRequested += ClientHost_PasswordRequested;
@ -185,6 +187,7 @@ namespace SafeExamBrowser.Client
{
actionCenter.QuitButtonClicked -= Shell_QuitButtonClicked;
applicationMonitor.ExplorerStarted -= ApplicationMonitor_ExplorerStarted;
applicationMonitor.TerminationFailed -= ApplicationMonitor_TerminationFailed;
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
runtime.ConnectionLost -= Runtime_ConnectionLost;
taskbar.QuitButtonClicked -= Shell_QuitButtonClicked;
@ -230,6 +233,13 @@ namespace SafeExamBrowser.Client
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)
{
if (Settings.ConfigurationMode == ConfigurationMode.ConfigureClient)

View file

@ -52,6 +52,7 @@ namespace SafeExamBrowser.Client
{
internal class CompositionRoot
{
private const int TWO_SECONDS = 2000;
private const int FIVE_SECONDS = 5000;
private Guid authenticationToken;
@ -94,7 +95,7 @@ namespace SafeExamBrowser.Client
taskbar = BuildTaskbar();
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 explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var hashAlgorithm = new HashAlgorithm();

View file

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

View file

@ -9,7 +9,7 @@
namespace SafeExamBrowser.Monitoring.Contracts.Applications.Events
{
/// <summary>
/// Indicates that the Windows explorer process has started.
/// Indicates that the Windows Explorer has been started.
/// </summary>
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>
event ExplorerStartedEventHandler ExplorerStarted;
/// <summary>
/// Event fired when the automatic termination of a blacklisted application failed.
/// </summary>
event TerminationFailedEventHandler TerminationFailed;
/// <summary>
/// Initializes the application monitor.
/// </summary>

View file

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

View file

@ -9,6 +9,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
@ -32,6 +33,7 @@ namespace SafeExamBrowser.Monitoring.Applications
private IList<WhitelistApplication> whitelist;
public event ExplorerStartedEventHandler ExplorerStarted;
public event TerminationFailedEventHandler TerminationFailed;
public ApplicationMonitor(int interval_ms, ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
{
@ -133,6 +135,74 @@ namespace SafeExamBrowser.Monitoring.Applications
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)
{
var application = result.FailedAutoTerminations.FirstOrDefault(a => a.Name == name);
@ -169,21 +239,6 @@ namespace SafeExamBrowser.Monitoring.Applications
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)
{
var title = nativeMethods.GetWindowTitle(window);
@ -192,6 +247,23 @@ namespace SafeExamBrowser.Monitoring.Applications
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)
{
var processId = nativeMethods.GetProcessIdFor(window);
@ -213,6 +285,21 @@ namespace SafeExamBrowser.Monitoring.Applications
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)
{
var title = nativeMethods.GetWindowTitle(window);
@ -265,51 +352,5 @@ namespace SafeExamBrowser.Monitoring.Applications
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.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.ClientProxy);
@ -181,12 +181,12 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod]
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();
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.ClientProxy);
@ -198,7 +198,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
PerformNormally();
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.ClientProxy);
@ -213,7 +213,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
proxy.Verify(p => p.InitiateShutdown(), 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.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}.");
if (ClientProcess.TryKill())
if (ClientProcess.TryKill(500))
{
break;
}