SEBWIN-312: Implemented mechanism to detect start of whitelisted application instances.
This commit is contained in:
parent
f19f284d95
commit
df13e96dcd
24 changed files with 248 additions and 135 deletions
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* 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 SafeExamBrowser.Core.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Applications.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// The information about an application which can be accessed via the shell.
|
||||
/// </summary>
|
||||
public class ApplicationInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates whether the application should be automatically started.
|
||||
/// </summary>
|
||||
public bool AutoStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the application.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The tooltip for the application.
|
||||
/// </summary>
|
||||
public string Tooltip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The resource providing the application icon.
|
||||
/// </summary>
|
||||
public IconResource Icon { get; set; }
|
||||
}
|
||||
}
|
|
@ -6,8 +6,10 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Core.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Applications.Contracts
|
||||
{
|
||||
|
@ -17,9 +19,29 @@ namespace SafeExamBrowser.Applications.Contracts
|
|||
public interface IApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about the application.
|
||||
/// Indicates whether the application should be automatically started.
|
||||
/// </summary>
|
||||
ApplicationInfo Info { get; }
|
||||
bool AutoStart { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The resource providing the application icon.
|
||||
/// </summary>
|
||||
IconResource Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The unique identifier of the application.
|
||||
/// </summary>
|
||||
Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the application.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The tooltip for the application.
|
||||
/// </summary>
|
||||
string Tooltip { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the windows of the application have changed.
|
||||
|
|
|
@ -60,7 +60,6 @@
|
|||
<Compile Include="FactoryResult.cs" />
|
||||
<Compile Include="IApplication.cs" />
|
||||
<Compile Include="IApplicationFactory.cs" />
|
||||
<Compile Include="ApplicationInfo.cs" />
|
||||
<Compile Include="IApplicationWindow.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -11,8 +11,8 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using Microsoft.Win32;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Core.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
|
@ -20,12 +20,18 @@ namespace SafeExamBrowser.Applications
|
|||
{
|
||||
public class ApplicationFactory : IApplicationFactory
|
||||
{
|
||||
private IApplicationMonitor applicationMonitor;
|
||||
private IModuleLogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
private IProcessFactory processFactory;
|
||||
|
||||
public ApplicationFactory(IModuleLogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
|
||||
public ApplicationFactory(
|
||||
IApplicationMonitor applicationMonitor,
|
||||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
IProcessFactory processFactory)
|
||||
{
|
||||
this.applicationMonitor = applicationMonitor;
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.processFactory = processFactory;
|
||||
|
@ -65,9 +71,8 @@ namespace SafeExamBrowser.Applications
|
|||
|
||||
private IApplication BuildApplication(string executablePath, WhitelistApplication settings)
|
||||
{
|
||||
var icon = new IconResource { Type = IconResourceType.Embedded, Uri = new Uri(executablePath) };
|
||||
var info = new ApplicationInfo { AutoStart = settings.AutoStart, Icon = icon, Name = settings.DisplayName, Tooltip = settings.Description ?? settings.DisplayName };
|
||||
var application = new ExternalApplication(executablePath, info, logger.CloneFor(settings.DisplayName), nativeMethods, processFactory);
|
||||
var applicationLogger = logger.CloneFor(settings.DisplayName);
|
||||
var application = new ExternalApplication(applicationMonitor, executablePath, applicationLogger, nativeMethods, processFactory, settings);
|
||||
|
||||
return application;
|
||||
}
|
||||
|
|
|
@ -11,48 +11,68 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using SafeExamBrowser.Applications.Contracts;
|
||||
using SafeExamBrowser.Applications.Contracts.Events;
|
||||
using SafeExamBrowser.Core.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Applications
|
||||
{
|
||||
internal class ExternalApplication : IApplication
|
||||
{
|
||||
private int instanceIdCounter = default(int);
|
||||
private readonly object @lock = new object();
|
||||
|
||||
private IApplicationMonitor applicationMonitor;
|
||||
private string executablePath;
|
||||
private IModuleLogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
private IList<ExternalApplicationInstance> instances;
|
||||
private IProcessFactory processFactory;
|
||||
private WhitelistApplication settings;
|
||||
|
||||
public bool AutoStart { get; private set; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public Guid Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Tooltip { get; private set; }
|
||||
|
||||
public event WindowsChangedEventHandler WindowsChanged;
|
||||
|
||||
public ApplicationInfo Info { get; }
|
||||
|
||||
internal ExternalApplication(
|
||||
IApplicationMonitor applicationMonitor,
|
||||
string executablePath,
|
||||
ApplicationInfo info,
|
||||
IModuleLogger logger,
|
||||
INativeMethods nativeMethods,
|
||||
IProcessFactory processFactory)
|
||||
IProcessFactory processFactory,
|
||||
WhitelistApplication settings)
|
||||
{
|
||||
this.applicationMonitor = applicationMonitor;
|
||||
this.executablePath = executablePath;
|
||||
this.Info = info;
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.instances = new List<ExternalApplicationInstance>();
|
||||
this.processFactory = processFactory;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public IEnumerable<IApplicationWindow> GetWindows()
|
||||
{
|
||||
return instances.SelectMany(i => i.GetWindows());
|
||||
lock (@lock)
|
||||
{
|
||||
return instances.SelectMany(i => i.GetWindows());
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// Nothing to do here for now.
|
||||
AutoStart = settings.AutoStart;
|
||||
Icon = new IconResource { Type = IconResourceType.Embedded, Uri = new Uri(executablePath) };
|
||||
Id = settings.Id;
|
||||
Name = settings.DisplayName;
|
||||
Tooltip = settings.Description ?? settings.DisplayName;
|
||||
|
||||
applicationMonitor.InstanceStarted += ApplicationMonitor_InstanceStarted;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
|
@ -60,16 +80,7 @@ namespace SafeExamBrowser.Applications
|
|||
try
|
||||
{
|
||||
logger.Info("Starting application...");
|
||||
|
||||
var process = processFactory.StartNew(executablePath);
|
||||
var id = ++instanceIdCounter;
|
||||
var instanceLogger = logger.CloneFor($"{Info.Name} Instance #{id}");
|
||||
var instance = new ExternalApplicationInstance(Info.Icon, id, instanceLogger, nativeMethods, process);
|
||||
|
||||
instance.Initialize();
|
||||
instance.Terminated += Instance_Terminated;
|
||||
instance.WindowsChanged += () => WindowsChanged?.Invoke();
|
||||
instances.Add(instance);
|
||||
InitializeInstance(processFactory.StartNew(executablePath));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -77,22 +88,55 @@ namespace SafeExamBrowser.Applications
|
|||
}
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
applicationMonitor.InstanceStarted -= ApplicationMonitor_InstanceStarted;
|
||||
|
||||
lock (@lock)
|
||||
{
|
||||
if (instances.Any())
|
||||
{
|
||||
logger.Info("Terminating application...");
|
||||
|
||||
foreach (var instance in instances)
|
||||
{
|
||||
instance.Terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplicationMonitor_InstanceStarted(Guid applicationId, IProcess process)
|
||||
{
|
||||
if (applicationId == Id)
|
||||
{
|
||||
logger.Info("New application instance was started.");
|
||||
InitializeInstance(process);
|
||||
}
|
||||
}
|
||||
|
||||
private void Instance_Terminated(int id)
|
||||
{
|
||||
instances.Remove(instances.First(i => i.Id == id));
|
||||
lock (@lock)
|
||||
{
|
||||
instances.Remove(instances.First(i => i.Id == id));
|
||||
}
|
||||
|
||||
WindowsChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
private void InitializeInstance(IProcess process)
|
||||
{
|
||||
if (instances.Any())
|
||||
{
|
||||
logger.Info("Terminating application...");
|
||||
var instanceLogger = logger.CloneFor($"{Name} ({process.Id})");
|
||||
var instance = new ExternalApplicationInstance(Icon, instanceLogger, nativeMethods, process);
|
||||
|
||||
foreach (var instance in instances)
|
||||
{
|
||||
instance.Terminate();
|
||||
}
|
||||
instance.Terminated += Instance_Terminated;
|
||||
instance.WindowsChanged += () => WindowsChanged?.Invoke();
|
||||
instance.Initialize();
|
||||
|
||||
lock (@lock)
|
||||
{
|
||||
instances.Add(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,15 +30,14 @@ namespace SafeExamBrowser.Applications
|
|||
private Timer timer;
|
||||
private IList<ExternalApplicationWindow> windows;
|
||||
|
||||
internal int Id { get; }
|
||||
internal int Id { get; private set; }
|
||||
|
||||
internal event InstanceTerminatedEventHandler Terminated;
|
||||
internal event WindowsChangedEventHandler WindowsChanged;
|
||||
|
||||
internal ExternalApplicationInstance(IconResource icon, int id, ILogger logger, INativeMethods nativeMethods, IProcess process)
|
||||
internal ExternalApplicationInstance(IconResource icon, ILogger logger, INativeMethods nativeMethods, IProcess process)
|
||||
{
|
||||
this.icon = icon;
|
||||
this.Id = id;
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.process = process;
|
||||
|
@ -55,6 +54,7 @@ namespace SafeExamBrowser.Applications
|
|||
|
||||
internal void Initialize()
|
||||
{
|
||||
Id = process.Id;
|
||||
InitializeEvents();
|
||||
logger.Info("Initialized application instance.");
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ namespace SafeExamBrowser.Applications
|
|||
lock (@lock)
|
||||
{
|
||||
var closedWindows = windows.Where(w => openWindows.All(ow => ow != w.Handle)).ToList();
|
||||
var openedWindows = openWindows.Where(ow => windows.All(w => w.Handle != ow) && BelongsToInstance(ow));
|
||||
var openedWindows = openWindows.Where(ow => windows.All(w => w.Handle != ow) && BelongsToInstance(ow)).ToList();
|
||||
|
||||
foreach (var window in closedWindows)
|
||||
{
|
||||
|
@ -128,7 +128,6 @@ namespace SafeExamBrowser.Applications
|
|||
|
||||
if (changed)
|
||||
{
|
||||
logger.Error("WINDOWS CHANGED!");
|
||||
WindowsChanged?.Invoke();
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,10 @@
|
|||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Monitoring.Contracts\SafeExamBrowser.Monitoring.Contracts.csproj">
|
||||
<Project>{6d563a30-366d-4c35-815b-2c9e6872278b}</Project>
|
||||
<Name>SafeExamBrowser.Monitoring.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
|
|
|
@ -17,6 +17,7 @@ using SafeExamBrowser.Browser.Contracts;
|
|||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Browser.Events;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Core.Contracts;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Logging;
|
||||
|
@ -38,7 +39,11 @@ namespace SafeExamBrowser.Browser
|
|||
private IText text;
|
||||
private IUserInterfaceFactory uiFactory;
|
||||
|
||||
public ApplicationInfo Info { get; private set; }
|
||||
public bool AutoStart { get; private set; }
|
||||
public IconResource Icon { get; private set; }
|
||||
public Guid Id { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
public string Tooltip { get; private set; }
|
||||
|
||||
public event DownloadRequestedEventHandler ConfigurationDownloadRequested;
|
||||
public event WindowsChangedEventHandler WindowsChanged;
|
||||
|
@ -102,12 +107,11 @@ namespace SafeExamBrowser.Browser
|
|||
|
||||
private void InitializeApplicationInfo()
|
||||
{
|
||||
Info = new ApplicationInfo
|
||||
{
|
||||
Icon = new BrowserIconResource(),
|
||||
Name = "Safe Exam Browser",
|
||||
Tooltip = text.Get(TextKey.Browser_Tooltip)
|
||||
};
|
||||
AutoStart = true;
|
||||
Icon = new BrowserIconResource();
|
||||
Id = Guid.NewGuid();
|
||||
Name = text.Get(TextKey.Browser_Name);
|
||||
Tooltip = text.Get(TextKey.Browser_Tooltip);
|
||||
}
|
||||
|
||||
private void CreateNewInstance(string url = null)
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
|||
private AppConfig appConfig;
|
||||
private Mock<IActionCenter> actionCenter;
|
||||
private Mock<IApplicationMonitor> applicationMonitor;
|
||||
private Mock<IBrowserApplication> browserController;
|
||||
private Mock<IBrowserApplication> browser;
|
||||
private Mock<IClientHost> clientHost;
|
||||
private ClientContext context;
|
||||
private Mock<IDisplayMonitor> displayMonitor;
|
||||
|
@ -66,7 +66,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
|||
appConfig = new AppConfig();
|
||||
actionCenter = new Mock<IActionCenter>();
|
||||
applicationMonitor = new Mock<IApplicationMonitor>();
|
||||
browserController = new Mock<IBrowserApplication>();
|
||||
browser = new Mock<IBrowserApplication>();
|
||||
clientHost = new Mock<IClientHost>();
|
||||
context = new ClientContext();
|
||||
displayMonitor = new Mock<IDisplayMonitor>();
|
||||
|
@ -106,7 +106,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
|||
|
||||
context.AppConfig = appConfig;
|
||||
context.Activators.Add(terminationActivator.Object);
|
||||
context.Browser = browserController.Object;
|
||||
context.Browser = browser.Object;
|
||||
context.ClientHost = clientHost.Object;
|
||||
context.SessionId = sessionId;
|
||||
context.Settings = settings;
|
||||
|
@ -300,7 +300,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
|||
It.IsAny<IWindow>())).Returns(MessageBoxResult.Ok);
|
||||
|
||||
sut.TryStart();
|
||||
browserController.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", new DownloadEventArgs());
|
||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", new DownloadEventArgs());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -321,7 +321,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
|||
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath))).Returns(new CommunicationResult(true));
|
||||
|
||||
sut.TryStart();
|
||||
browserController.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||
args.Callback(true, downloadPath);
|
||||
|
||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath)), Times.Once);
|
||||
|
@ -348,7 +348,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
|||
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath))).Returns(new CommunicationResult(true));
|
||||
|
||||
sut.TryStart();
|
||||
browserController.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||
args.Callback(false, downloadPath);
|
||||
|
||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>()), Times.Never);
|
||||
|
@ -372,7 +372,7 @@ namespace SafeExamBrowser.Client.UnitTests
|
|||
runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath))).Returns(new CommunicationResult(false));
|
||||
|
||||
sut.TryStart();
|
||||
browserController.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
|
||||
args.Callback(true, downloadPath);
|
||||
|
||||
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>()), Times.Once);
|
||||
|
@ -632,9 +632,9 @@ namespace SafeExamBrowser.Client.UnitTests
|
|||
var application2 = new Mock<IApplication>();
|
||||
var application3 = new Mock<IApplication>();
|
||||
|
||||
application1.SetupGet(a => a.Info).Returns(new ApplicationInfo { AutoStart = true });
|
||||
application2.SetupGet(a => a.Info).Returns(new ApplicationInfo { AutoStart = false });
|
||||
application3.SetupGet(a => a.Info).Returns(new ApplicationInfo { AutoStart = true });
|
||||
application1.SetupGet(a => a.AutoStart).Returns(true);
|
||||
application2.SetupGet(a => a.AutoStart).Returns(false);
|
||||
application3.SetupGet(a => a.AutoStart).Returns(true);
|
||||
context.Applications.Add(application1.Object);
|
||||
context.Applications.Add(application2.Object);
|
||||
context.Applications.Add(application3.Object);
|
||||
|
@ -647,6 +647,23 @@ namespace SafeExamBrowser.Client.UnitTests
|
|||
application3.Verify(a => a.Start(), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Startup_MustAutoStartBrowser()
|
||||
{
|
||||
browser.SetupGet(b => b.AutoStart).Returns(true);
|
||||
operationSequence.Setup(o => o.TryPerform()).Returns(OperationResult.Success);
|
||||
|
||||
sut.TryStart();
|
||||
|
||||
browser.Verify(b => b.Start(), Times.Once);
|
||||
browser.Reset();
|
||||
browser.SetupGet(b => b.AutoStart).Returns(false);
|
||||
|
||||
sut.TryStart();
|
||||
|
||||
browser.Verify(b => b.Start(), Times.Never);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TerminationActivator_MustCorrectlyInitiateShutdown()
|
||||
{
|
||||
|
|
|
@ -241,14 +241,17 @@ namespace SafeExamBrowser.Client
|
|||
|
||||
private void AutoStartApplications()
|
||||
{
|
||||
logger.Info("Starting browser application...");
|
||||
Browser.Start();
|
||||
if (Browser.AutoStart)
|
||||
{
|
||||
logger.Info("Auto-starting browser...");
|
||||
Browser.Start();
|
||||
}
|
||||
|
||||
foreach (var application in context.Applications)
|
||||
{
|
||||
if (application.Info.AutoStart)
|
||||
if (application.AutoStart)
|
||||
{
|
||||
logger.Info($"Auto-starting '{application.Info.Name}'...");
|
||||
logger.Info($"Auto-starting '{application.Name}'...");
|
||||
application.Start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,8 +98,8 @@ namespace SafeExamBrowser.Client
|
|||
taskView = BuildTaskView();
|
||||
|
||||
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
|
||||
var applicationFactory = new ApplicationFactory(ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory);
|
||||
var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, new ProcessFactory(ModuleLogger(nameof(ProcessFactory))));
|
||||
var applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, processFactory);
|
||||
var applicationFactory = new ApplicationFactory(applicationMonitor, ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory);
|
||||
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
||||
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
|
||||
var hashAlgorithm = new HashAlgorithm();
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace SafeExamBrowser.I18n.Contracts
|
|||
Browser_BlockedPageButton,
|
||||
Browser_BlockedPageMessage,
|
||||
Browser_BlockedPageTitle,
|
||||
Browser_Name,
|
||||
Browser_Tooltip,
|
||||
BrowserWindow_DeveloperConsoleMenuItem,
|
||||
BrowserWindow_ZoomMenuItem,
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
<Entry key="Browser_BlockedPageTitle">
|
||||
Page Blocked
|
||||
</Entry>
|
||||
<Entry key="Browser_Name">
|
||||
Browser
|
||||
</Entry>
|
||||
<Entry key="Browser_Tooltip">
|
||||
Browser Application
|
||||
</Entry>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Monitoring.Contracts.Applications.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a new instance of a whitelisted application has been started.
|
||||
/// </summary>
|
||||
public delegate void InstanceStartedEventHandler(Guid applicationId, IProcess process);
|
||||
}
|
|
@ -21,6 +21,11 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
|
|||
/// </summary>
|
||||
event ExplorerStartedEventHandler ExplorerStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when a new instance of a whitelisted application has been started.
|
||||
/// </summary>
|
||||
event InstanceStartedEventHandler InstanceStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the automatic termination of a blacklisted application failed.
|
||||
/// </summary>
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Applications\Events\InstanceStartedEventHandler.cs" />
|
||||
<Compile Include="Applications\Events\TerminationFailedEventHandler.cs" />
|
||||
<Compile Include="Applications\RunningApplication.cs" />
|
||||
<Compile Include="Display\Events\DisplayChangedEventHandler.cs" />
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
private Window activeWindow;
|
||||
|
||||
public event ExplorerStartedEventHandler ExplorerStarted;
|
||||
public event InstanceStartedEventHandler InstanceStarted;
|
||||
public event TerminationFailedEventHandler TerminationFailed;
|
||||
|
||||
public ApplicationMonitor(int interval_ms, ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
|
||||
|
@ -134,7 +135,7 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
logger.Debug($"Process {process} has been started.");
|
||||
processes.Add(process);
|
||||
|
||||
if (process.Name == "explorer")
|
||||
if (process.Name == "explorer.exe")
|
||||
{
|
||||
HandleExplorerStart(process);
|
||||
}
|
||||
|
@ -142,6 +143,10 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
{
|
||||
AddFailed(process, failed);
|
||||
}
|
||||
else if (IsWhitelisted(process, out var applicationId))
|
||||
{
|
||||
HandleInstanceStart(applicationId.Value, process);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var process in terminated)
|
||||
|
@ -238,6 +243,12 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
Task.Run(() => ExplorerStarted?.Invoke());
|
||||
}
|
||||
|
||||
private void HandleInstanceStart(Guid applicationId, IProcess process)
|
||||
{
|
||||
logger.Debug($"Detected start of whitelisted application instance {process}.");
|
||||
Task.Run(() => InstanceStarted?.Invoke(applicationId, process));
|
||||
}
|
||||
|
||||
private void InitializeProcesses()
|
||||
{
|
||||
processes = processFactory.GetAllRunning();
|
||||
|
@ -329,24 +340,33 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
|
||||
if (processFactory.TryGetById(processId, out var process))
|
||||
{
|
||||
if (BelongsToSafeExamBrowser(process))
|
||||
if (BelongsToSafeExamBrowser(process) || IsWhitelisted(process, out _))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var application in whitelist)
|
||||
{
|
||||
if (BelongsToApplication(process, application))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
logger.Warn($"Window {window} belongs to not allowed process '{process.Name}'!");
|
||||
logger.Warn($"Window {window} belongs to not whitelisted process '{process.Name}'!");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Could not find process for window {window} and process with ID = {processId}!");
|
||||
logger.Error($"Could not find process for window {window} and process ID = {processId}!");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsWhitelisted(IProcess process, out Guid? applicationId)
|
||||
{
|
||||
applicationId = default(Guid?);
|
||||
|
||||
foreach (var application in whitelist)
|
||||
{
|
||||
if (BelongsToApplication(process, application))
|
||||
{
|
||||
applicationId = application.Id;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -62,6 +62,11 @@ namespace SafeExamBrowser.Settings.Applications
|
|||
/// </summary>
|
||||
public string ExecutablePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier to be used to identify the application during runtime.
|
||||
/// </summary>
|
||||
public Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The original file name of the main executable of the application, if available.
|
||||
/// </summary>
|
||||
|
@ -80,6 +85,7 @@ namespace SafeExamBrowser.Settings.Applications
|
|||
public WhitelistApplication()
|
||||
{
|
||||
Arguments = new List<string>();
|
||||
Id = Guid.NewGuid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
|||
{
|
||||
public partial class ActionCenterApplicationButton : UserControl
|
||||
{
|
||||
private ApplicationInfo info;
|
||||
private IApplication application;
|
||||
private IApplicationWindow window;
|
||||
|
||||
internal event EventHandler Clicked;
|
||||
|
||||
public ActionCenterApplicationButton(ApplicationInfo info, IApplicationWindow window = null)
|
||||
public ActionCenterApplicationButton(IApplication application, IApplicationWindow window = null)
|
||||
{
|
||||
this.info = info;
|
||||
this.application = application;
|
||||
this.window = window;
|
||||
|
||||
InitializeComponent();
|
||||
|
@ -32,10 +32,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
|||
|
||||
private void InitializeApplicationInstanceButton()
|
||||
{
|
||||
Icon.Content = IconResourceLoader.Load(info.Icon);
|
||||
Text.Text = window?.Title ?? info.Name;
|
||||
Icon.Content = IconResourceLoader.Load(application.Icon);
|
||||
Text.Text = window?.Title ?? application.Name;
|
||||
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
|
||||
Button.ToolTip = window?.Title ?? info.Tooltip;
|
||||
Button.ToolTip = window?.Title ?? application.Tooltip;
|
||||
|
||||
if (window != null)
|
||||
{
|
||||
|
|
|
@ -27,11 +27,11 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
|||
|
||||
private void InitializeApplicationControl()
|
||||
{
|
||||
var button = new ActionCenterApplicationButton(application.Info);
|
||||
var button = new ActionCenterApplicationButton(application);
|
||||
|
||||
application.WindowsChanged += Application_WindowsChanged;
|
||||
button.Clicked += (o, args) => application.Start();
|
||||
ApplicationName.Text = application.Info.Name;
|
||||
ApplicationName.Text = application.Name;
|
||||
ApplicationName.Visibility = Visibility.Collapsed;
|
||||
ApplicationButton.Content = button;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
|||
|
||||
foreach (var window in windows)
|
||||
{
|
||||
var button = new ActionCenterApplicationButton(application.Info, window);
|
||||
var button = new ActionCenterApplicationButton(application, window);
|
||||
|
||||
button.Clicked += (o, args) => window.Activate();
|
||||
WindowPanel.Children.Add(button);
|
||||
|
|
|
@ -38,10 +38,10 @@ namespace SafeExamBrowser.UserInterface.Desktop.Controls
|
|||
application.WindowsChanged += Application_WindowsChanged;
|
||||
|
||||
Button.Click += Button_Click;
|
||||
Button.Content = IconResourceLoader.Load(application.Info.Icon);
|
||||
Button.Content = IconResourceLoader.Load(application.Icon);
|
||||
Button.MouseEnter += (o, args) => WindowPopup.IsOpen = WindowStackPanel.Children.Count > 0;
|
||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = WindowPopup.IsMouseOver));
|
||||
Button.ToolTip = application.Info.Tooltip;
|
||||
Button.ToolTip = application.Tooltip;
|
||||
WindowPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = IsMouseOver));
|
||||
|
||||
WindowPopup.Opened += (o, args) =>
|
||||
|
|
|
@ -16,14 +16,14 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
|||
{
|
||||
public partial class ActionCenterApplicationButton : UserControl
|
||||
{
|
||||
private ApplicationInfo info;
|
||||
private IApplication application;
|
||||
private IApplicationWindow window;
|
||||
|
||||
internal event EventHandler Clicked;
|
||||
|
||||
public ActionCenterApplicationButton(ApplicationInfo info, IApplicationWindow window = null)
|
||||
public ActionCenterApplicationButton(IApplication application, IApplicationWindow window = null)
|
||||
{
|
||||
this.info = info;
|
||||
this.application = application;
|
||||
this.window = window;
|
||||
|
||||
InitializeComponent();
|
||||
|
@ -32,10 +32,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
|||
|
||||
private void InitializeApplicationInstanceButton()
|
||||
{
|
||||
Icon.Content = IconResourceLoader.Load(info.Icon);
|
||||
Text.Text = window?.Title ?? info.Name;
|
||||
Icon.Content = IconResourceLoader.Load(application.Icon);
|
||||
Text.Text = window?.Title ?? application.Name;
|
||||
Button.Click += (o, args) => Clicked?.Invoke(this, EventArgs.Empty);
|
||||
Button.ToolTip = window?.Title ?? info.Tooltip;
|
||||
Button.ToolTip = window?.Title ?? application.Tooltip;
|
||||
|
||||
if (window != null)
|
||||
{
|
||||
|
|
|
@ -27,11 +27,11 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
|||
|
||||
private void InitializeApplicationControl()
|
||||
{
|
||||
var button = new ActionCenterApplicationButton(application.Info);
|
||||
var button = new ActionCenterApplicationButton(application);
|
||||
|
||||
application.WindowsChanged += Application_WindowsChanged;
|
||||
button.Clicked += (o, args) => application.Start();
|
||||
ApplicationName.Text = application.Info.Name;
|
||||
ApplicationName.Text = application.Name;
|
||||
ApplicationName.Visibility = Visibility.Collapsed;
|
||||
ApplicationButton.Content = button;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
|||
|
||||
foreach (var window in windows)
|
||||
{
|
||||
var button = new ActionCenterApplicationButton(application.Info, window);
|
||||
var button = new ActionCenterApplicationButton(application, window);
|
||||
|
||||
button.Clicked += (o, args) => window.Activate();
|
||||
WindowPanel.Children.Add(button);
|
||||
|
|
|
@ -38,10 +38,10 @@ namespace SafeExamBrowser.UserInterface.Mobile.Controls
|
|||
application.WindowsChanged += Application_WindowsChanged;
|
||||
|
||||
Button.Click += Button_Click;
|
||||
Button.Content = IconResourceLoader.Load(application.Info.Icon);
|
||||
Button.Content = IconResourceLoader.Load(application.Icon);
|
||||
Button.MouseEnter += (o, args) => WindowPopup.IsOpen = WindowStackPanel.Children.Count > 0;
|
||||
Button.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = WindowPopup.IsMouseOver));
|
||||
Button.ToolTip = application.Info.Tooltip;
|
||||
Button.ToolTip = application.Tooltip;
|
||||
WindowPopup.MouseLeave += (o, args) => Task.Delay(250).ContinueWith(_ => Dispatcher.Invoke(() => WindowPopup.IsOpen = IsMouseOver));
|
||||
|
||||
WindowPopup.Opened += (o, args) =>
|
||||
|
|
Loading…
Reference in a new issue