diff --git a/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs b/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs deleted file mode 100644 index aa4a32b0..00000000 --- a/SafeExamBrowser.Applications.Contracts/ApplicationInfo.cs +++ /dev/null @@ -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 -{ - /// - /// The information about an application which can be accessed via the shell. - /// - public class ApplicationInfo - { - /// - /// Indicates whether the application should be automatically started. - /// - public bool AutoStart { get; set; } - - /// - /// The name of the application. - /// - public string Name { get; set; } - - /// - /// The tooltip for the application. - /// - public string Tooltip { get; set; } - - /// - /// The resource providing the application icon. - /// - public IconResource Icon { get; set; } - } -} diff --git a/SafeExamBrowser.Applications.Contracts/IApplication.cs b/SafeExamBrowser.Applications.Contracts/IApplication.cs index 28f8def2..a37e9c88 100644 --- a/SafeExamBrowser.Applications.Contracts/IApplication.cs +++ b/SafeExamBrowser.Applications.Contracts/IApplication.cs @@ -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 { /// - /// Provides information about the application. + /// Indicates whether the application should be automatically started. /// - ApplicationInfo Info { get; } + bool AutoStart { get; } + + /// + /// The resource providing the application icon. + /// + IconResource Icon { get; } + + /// + /// The unique identifier of the application. + /// + Guid Id { get; } + + /// + /// The name of the application. + /// + string Name { get; } + + /// + /// The tooltip for the application. + /// + string Tooltip { get; } /// /// Event fired when the windows of the application have changed. diff --git a/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj b/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj index f2e84263..09a77d78 100644 --- a/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj +++ b/SafeExamBrowser.Applications.Contracts/SafeExamBrowser.Applications.Contracts.csproj @@ -60,7 +60,6 @@ - diff --git a/SafeExamBrowser.Applications/ApplicationFactory.cs b/SafeExamBrowser.Applications/ApplicationFactory.cs index 9e5549b8..492eebb4 100644 --- a/SafeExamBrowser.Applications/ApplicationFactory.cs +++ b/SafeExamBrowser.Applications/ApplicationFactory.cs @@ -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; } diff --git a/SafeExamBrowser.Applications/ExternalApplication.cs b/SafeExamBrowser.Applications/ExternalApplication.cs index 0e3e3e4c..708ef0cb 100644 --- a/SafeExamBrowser.Applications/ExternalApplication.cs +++ b/SafeExamBrowser.Applications/ExternalApplication.cs @@ -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 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(); this.processFactory = processFactory; + this.settings = settings; } public IEnumerable 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); } } } diff --git a/SafeExamBrowser.Applications/ExternalApplicationInstance.cs b/SafeExamBrowser.Applications/ExternalApplicationInstance.cs index 786c9b3d..2bd3ed2c 100644 --- a/SafeExamBrowser.Applications/ExternalApplicationInstance.cs +++ b/SafeExamBrowser.Applications/ExternalApplicationInstance.cs @@ -30,15 +30,14 @@ namespace SafeExamBrowser.Applications private Timer timer; private IList 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(); } diff --git a/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj b/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj index 546a58e2..1c748536 100644 --- a/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj +++ b/SafeExamBrowser.Applications/SafeExamBrowser.Applications.csproj @@ -74,6 +74,10 @@ {64ea30fb-11d4-436a-9c2b-88566285363e} SafeExamBrowser.Logging.Contracts + + {6d563a30-366d-4c35-815b-2c9e6872278b} + SafeExamBrowser.Monitoring.Contracts + {30b2d907-5861-4f39-abad-c4abf1b3470e} SafeExamBrowser.Settings diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs index 1b6578be..cd48c69e 100644 --- a/SafeExamBrowser.Browser/BrowserApplication.cs +++ b/SafeExamBrowser.Browser/BrowserApplication.cs @@ -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) diff --git a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs index bdb48d94..c3212ec2 100644 --- a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs +++ b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs @@ -40,7 +40,7 @@ namespace SafeExamBrowser.Client.UnitTests private AppConfig appConfig; private Mock actionCenter; private Mock applicationMonitor; - private Mock browserController; + private Mock browser; private Mock clientHost; private ClientContext context; private Mock displayMonitor; @@ -66,7 +66,7 @@ namespace SafeExamBrowser.Client.UnitTests appConfig = new AppConfig(); actionCenter = new Mock(); applicationMonitor = new Mock(); - browserController = new Mock(); + browser = new Mock(); clientHost = new Mock(); context = new ClientContext(); displayMonitor = new Mock(); @@ -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())).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(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(p => p == downloadPath)), Times.Once); @@ -348,7 +348,7 @@ namespace SafeExamBrowser.Client.UnitTests runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is(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()), Times.Never); @@ -372,7 +372,7 @@ namespace SafeExamBrowser.Client.UnitTests runtimeProxy.Setup(r => r.RequestReconfiguration(It.Is(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()), Times.Once); @@ -632,9 +632,9 @@ namespace SafeExamBrowser.Client.UnitTests var application2 = new Mock(); var application3 = new Mock(); - 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() { diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs index f26ecd93..8fe48d67 100644 --- a/SafeExamBrowser.Client/ClientController.cs +++ b/SafeExamBrowser.Client/ClientController.cs @@ -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(); } } diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index 386d4957..52bc5453 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -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(); diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs index fbf939e5..1e26f2b0 100644 --- a/SafeExamBrowser.I18n.Contracts/TextKey.cs +++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs @@ -18,6 +18,7 @@ namespace SafeExamBrowser.I18n.Contracts Browser_BlockedPageButton, Browser_BlockedPageMessage, Browser_BlockedPageTitle, + Browser_Name, Browser_Tooltip, BrowserWindow_DeveloperConsoleMenuItem, BrowserWindow_ZoomMenuItem, diff --git a/SafeExamBrowser.I18n/Text.xml b/SafeExamBrowser.I18n/Text.xml index 592feac9..228f06cb 100644 --- a/SafeExamBrowser.I18n/Text.xml +++ b/SafeExamBrowser.I18n/Text.xml @@ -12,6 +12,9 @@ Page Blocked + + Browser + Browser Application diff --git a/SafeExamBrowser.Monitoring.Contracts/Applications/Events/InstanceStartedEventHandler.cs b/SafeExamBrowser.Monitoring.Contracts/Applications/Events/InstanceStartedEventHandler.cs new file mode 100644 index 00000000..d37e6917 --- /dev/null +++ b/SafeExamBrowser.Monitoring.Contracts/Applications/Events/InstanceStartedEventHandler.cs @@ -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 +{ + /// + /// Indicates that a new instance of a whitelisted application has been started. + /// + public delegate void InstanceStartedEventHandler(Guid applicationId, IProcess process); +} diff --git a/SafeExamBrowser.Monitoring.Contracts/Applications/IApplicationMonitor.cs b/SafeExamBrowser.Monitoring.Contracts/Applications/IApplicationMonitor.cs index bd0062dd..fd3064e9 100644 --- a/SafeExamBrowser.Monitoring.Contracts/Applications/IApplicationMonitor.cs +++ b/SafeExamBrowser.Monitoring.Contracts/Applications/IApplicationMonitor.cs @@ -21,6 +21,11 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications /// event ExplorerStartedEventHandler ExplorerStarted; + /// + /// Event fired when a new instance of a whitelisted application has been started. + /// + event InstanceStartedEventHandler InstanceStarted; + /// /// Event fired when the automatic termination of a blacklisted application failed. /// diff --git a/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj b/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj index 86a1e456..b2ad98d9 100644 --- a/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj +++ b/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj @@ -54,6 +54,7 @@ + diff --git a/SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.cs b/SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.cs index f18f7009..5b67d180 100644 --- a/SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.cs +++ b/SafeExamBrowser.Monitoring/Applications/ApplicationMonitor.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; diff --git a/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs b/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs index 85b49d85..7fd0a960 100644 --- a/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs +++ b/SafeExamBrowser.Settings/Applications/WhitelistApplication.cs @@ -62,6 +62,11 @@ namespace SafeExamBrowser.Settings.Applications /// public string ExecutablePath { get; set; } + /// + /// Unique identifier to be used to identify the application during runtime. + /// + public Guid Id { get; } + /// /// The original file name of the main executable of the application, if available. /// @@ -80,6 +85,7 @@ namespace SafeExamBrowser.Settings.Applications public WhitelistApplication() { Arguments = new List(); + Id = Guid.NewGuid(); } } } diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs index 04a03b32..92226fff 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationButton.xaml.cs @@ -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) { diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs index d289ef42..7f8a4d23 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/ActionCenterApplicationControl.xaml.cs @@ -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); diff --git a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs index 18434b5c..9cac7212 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Controls/TaskbarApplicationControl.xaml.cs @@ -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) => diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs index b60c51f8..b2c581f7 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationButton.xaml.cs @@ -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) { diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs index 3f8ad7aa..21f0d78a 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/ActionCenterApplicationControl.xaml.cs @@ -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); diff --git a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs index fc98dcf0..df16c255 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Controls/TaskbarApplicationControl.xaml.cs @@ -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) =>