Restore SEBPatch

This commit is contained in:
2025-06-01 11:44:20 +02:00
commit 8c656e3137
1297 changed files with 142172 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using System.Linq;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Client.Operations.Events;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.Settings.Security;
namespace SafeExamBrowser.Client.Operations
{
internal class ApplicationOperation : ClientOperation
{
private IApplicationFactory factory;
private ILogger logger;
private IApplicationMonitor monitor;
private IText text;
public override event ActionRequiredEventHandler ActionRequired;
public override event StatusChangedEventHandler StatusChanged;
public ApplicationOperation(
ClientContext context,
IApplicationFactory factory,
IApplicationMonitor monitor,
ILogger logger,
IText text) : base(context)
{
this.factory = factory;
this.monitor = monitor;
this.logger = logger;
this.text = text;
}
public override OperationResult Perform()
{
logger.Info("Initializing applications...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeApplications);
var result = InitializeApplications();
if (result == OperationResult.Success)
{
StartMonitor();
}
return result;
}
public override OperationResult Revert()
{
logger.Info("Finalizing applications...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeApplications);
FinalizeApplications();
StopMonitor();
return OperationResult.Success;
}
private OperationResult InitializeApplications()
{
var initialization = monitor.Initialize(Context.Settings.Applications);
var result = OperationResult.Success;
if (initialization.FailedAutoTerminations.Any())
{
result = HandleAutoTerminationFailure(initialization.FailedAutoTerminations);
}
else if (initialization.RunningApplications.Any())
{
result = TryTerminate(initialization.RunningApplications);
}
if (result == OperationResult.Success)
{
foreach (var application in Context.Settings.Applications.Whitelist)
{
Initialize(application);
}
}
return result;
}
private void Initialize(WhitelistApplication settings)
{
var result = factory.TryCreate(settings, out var application);
while (result == FactoryResult.NotFound && settings.AllowCustomPath)
{
var args = new ApplicationNotFoundEventArgs(settings.DisplayName, settings.ExecutableName);
ActionRequired?.Invoke(args);
if (args.Success)
{
settings.ExecutablePath = args.CustomPath;
result = factory.TryCreate(settings, out application);
}
else
{
break;
}
}
if (result == FactoryResult.Success)
{
Context.Applications.Add(application);
}
else
{
logger.Error($"Failed to initialize application '{settings.DisplayName}' ({settings.ExecutableName}). Reason: {result}.");
ActionRequired?.Invoke(new ApplicationInitializationFailedEventArgs(settings.DisplayName, settings.ExecutableName, result));
}
}
private void FinalizeApplications()
{
foreach (var application in Context.Applications)
{
application.Terminate();
}
}
private OperationResult HandleAutoTerminationFailure(IList<RunningApplication> applications)
{
logger.Error($"{applications.Count} application(s) could not be automatically terminated: {string.Join(", ", applications.Select(a => a.Name))}");
ActionRequired?.Invoke(new ApplicationTerminationFailedEventArgs(applications));
return OperationResult.Failed;
}
private void StartMonitor()
{
if (Context.Settings.Security.KioskMode != KioskMode.None)
{
monitor.Start();
}
}
private void StopMonitor()
{
if (Context.Settings.Security.KioskMode != KioskMode.None)
{
monitor.Stop();
}
}
private OperationResult TryTerminate(IEnumerable<RunningApplication> runningApplications)
{
var args = new ApplicationTerminationEventArgs(runningApplications);
var failed = new List<RunningApplication>();
var result = OperationResult.Success;
logger.Info($"The following applications need to be terminated: {string.Join(", ", runningApplications.Select(a => a.Name))}.");
ActionRequired?.Invoke(args);
if (args.TerminateProcesses)
{
logger.Info($"The user chose to automatically terminate all running applications.");
foreach (var application in runningApplications)
{
var success = monitor.TryTerminate(application);
if (success)
{
logger.Info($"Successfully terminated application '{application.Name}'.");
}
else
{
result = OperationResult.Failed;
failed.Add(application);
logger.Error($"Failed to automatically terminate application '{application.Name}'!");
}
}
}
else
{
logger.Info("The user chose not to automatically terminate all running applications. Aborting...");
result = OperationResult.Aborted;
}
if (failed.Any())
{
ActionRequired?.Invoke(new ApplicationTerminationFailedEventArgs(failed));
}
return result;
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.Client.Operations
{
internal class BrowserOperation : ClientOperation
{
private readonly IActionCenter actionCenter;
private readonly ILogger logger;
private readonly ITaskbar taskbar;
private readonly ITaskview taskview;
private readonly IUserInterfaceFactory uiFactory;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public BrowserOperation(
IActionCenter actionCenter,
ClientContext context,
ILogger logger,
ITaskbar taskbar,
ITaskview taskview,
IUserInterfaceFactory uiFactory) : base(context)
{
this.actionCenter = actionCenter;
this.logger = logger;
this.taskbar = taskbar;
this.taskview = taskview;
this.uiFactory = uiFactory;
}
public override OperationResult Perform()
{
logger.Info("Initializing browser...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeBrowser);
if (Context.Settings.Browser.EnableBrowser)
{
Context.Browser.Initialize();
if (Context.Settings.UserInterface.ActionCenter.EnableActionCenter)
{
actionCenter.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.ActionCenter), true);
}
if (Context.Settings.UserInterface.Taskbar.EnableTaskbar)
{
taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar), true);
}
taskview.Add(Context.Browser);
}
else
{
logger.Info("Browser application is disabled for this session.");
}
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Terminating browser...");
StatusChanged?.Invoke(TextKey.OperationStatus_TerminateBrowser);
if (Context.Settings.Browser.EnableBrowser)
{
Context.Browser.Terminate();
}
else
{
logger.Info("Browser application was disabled for this session.");
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.Threading;
using SafeExamBrowser.Communication.Contracts.Events;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Client.Operations
{
/// <summary>
/// During application shutdown, it could happen that the client stops its communication host before the runtime had the chance to
/// disconnect from it. This operation prevents the described race condition by waiting on the runtime to disconnect from the client.
/// </summary>
internal class ClientHostDisconnectionOperation : ClientOperation
{
private ILogger logger;
private int timeout_ms;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ClientHostDisconnectionOperation(ClientContext context, ILogger logger, int timeout_ms) : base(context)
{
this.logger = logger;
this.timeout_ms = timeout_ms;
}
public override OperationResult Perform()
{
return OperationResult.Success;
}
public override OperationResult Revert()
{
StatusChanged?.Invoke(TextKey.OperationStatus_WaitRuntimeDisconnection);
if (Context.ClientHost.IsConnected)
{
var disconnected = false;
var disconnectedEvent = new AutoResetEvent(false);
var disconnectedEventHandler = new CommunicationEventHandler(() => disconnectedEvent.Set());
Context.ClientHost.RuntimeDisconnected += disconnectedEventHandler;
logger.Info("Waiting for runtime to disconnect from client communication host...");
disconnected = disconnectedEvent.WaitOne(timeout_ms);
Context.ClientHost.RuntimeDisconnected -= disconnectedEventHandler;
if (disconnected)
{
logger.Info("The runtime has successfully disconnected from the client communication host.");
}
else
{
logger.Error($"The runtime failed to disconnect within {timeout_ms / 1000} seconds!");
return OperationResult.Failed;
}
}
else
{
logger.Info("The runtime has already disconnected from the client communication host.");
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
namespace SafeExamBrowser.Client.Operations
{
/// <summary>
/// The base implementation to be used for all operations in the client operation sequence.
/// </summary>
internal abstract class ClientOperation : IOperation
{
protected ClientContext Context { get; private set; }
public abstract event ActionRequiredEventHandler ActionRequired;
public abstract event StatusChangedEventHandler StatusChanged;
public ClientOperation(ClientContext context)
{
Context = context;
}
public abstract OperationResult Perform();
public abstract OperationResult Revert();
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
namespace SafeExamBrowser.Client.Operations
{
internal class ClipboardOperation : ClientOperation
{
private readonly IClipboard clipboard;
private readonly ILogger logger;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ClipboardOperation(ClientContext context, IClipboard clipboard, ILogger logger) : base(context)
{
this.clipboard = clipboard;
this.logger = logger;
}
public override OperationResult Perform()
{
InitializeClipboard();
return OperationResult.Success;
}
public override OperationResult Revert()
{
FinalizeClipboard();
return OperationResult.Success;
}
private void InitializeClipboard()
{
logger.Info("Initializing clipboard...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeClipboard);
clipboard.Initialize(Context.Settings.Security.ClipboardPolicy);
}
private void FinalizeClipboard()
{
logger.Info("Finalizing clipboard...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeClipboard);
clipboard.Terminate();
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.Communication.Contracts.Proxies;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Client.Operations
{
internal class ConfigurationOperation : ClientOperation
{
private readonly ILogger logger;
private readonly IRuntimeProxy runtime;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ConfigurationOperation(ClientContext context, ILogger logger, IRuntimeProxy runtime) : base(context)
{
this.logger = logger;
this.runtime = runtime;
}
public override OperationResult Perform()
{
logger.Info("Initializing application configuration...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration);
var communication = runtime.GetConfiguration();
var configuration = communication.Value.Configuration;
Context.AppConfig = configuration.AppConfig;
Context.SessionId = configuration.SessionId;
Context.Settings = configuration.Settings;
logger.Info("Successfully retrieved the application configuration from the runtime.");
logger.Info($" -> Client-ID: {Context.AppConfig.ClientId}");
logger.Info($" -> Runtime-ID: {Context.AppConfig.RuntimeId}");
logger.Info($" -> Session-ID: {Context.SessionId}");
return OperationResult.Success;
}
public override OperationResult Revert()
{
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Display;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.Client.Operations
{
internal class DisplayMonitorOperation : ClientOperation
{
private readonly IDisplayMonitor displayMonitor;
private readonly ILogger logger;
private readonly ITaskbar taskbar;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public DisplayMonitorOperation(ClientContext context, IDisplayMonitor displayMonitor, ILogger logger, ITaskbar taskbar) : base(context)
{
this.displayMonitor = displayMonitor;
this.logger = logger;
this.taskbar = taskbar;
}
public override OperationResult Perform()
{
logger.Info("Initializing working area...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeWorkingArea);
displayMonitor.InitializePrimaryDisplay(Context.Settings.UserInterface.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0);
displayMonitor.StartMonitoringDisplayChanges();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Restoring working area...");
StatusChanged?.Invoke(TextKey.OperationStatus_RestoreWorkingArea);
displayMonitor.StopMonitoringDisplayChanges();
displayMonitor.ResetPrimaryDisplay();
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.Applications.Contracts;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
namespace SafeExamBrowser.Client.Operations.Events
{
internal class ApplicationInitializationFailedEventArgs : ActionRequiredEventArgs
{
internal string DisplayName { get; }
internal string ExecutableName { get; }
internal FactoryResult Result { get; }
internal ApplicationInitializationFailedEventArgs(string displayName, string executableName, FactoryResult result)
{
DisplayName = displayName;
ExecutableName = executableName;
Result = result;
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.OperationModel.Events;
namespace SafeExamBrowser.Client.Operations.Events
{
internal class ApplicationNotFoundEventArgs : ActionRequiredEventArgs
{
internal string CustomPath { get; set; }
internal string DisplayName { get; }
internal string ExecutableName { get; }
internal bool Success { get; set; }
internal ApplicationNotFoundEventArgs(string displayName, string executableName)
{
DisplayName = displayName;
ExecutableName = executableName;
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.Monitoring.Contracts.Applications;
namespace SafeExamBrowser.Client.Operations.Events
{
internal class ApplicationTerminationEventArgs : ActionRequiredEventArgs
{
internal IEnumerable<RunningApplication> RunningApplications { get; }
internal bool TerminateProcesses { get; set; }
internal ApplicationTerminationEventArgs(IEnumerable<RunningApplication> runningApplications)
{
RunningApplications = runningApplications;
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.Monitoring.Contracts.Applications;
namespace SafeExamBrowser.Client.Operations.Events
{
internal class ApplicationTerminationFailedEventArgs : ActionRequiredEventArgs
{
internal IEnumerable<RunningApplication> Applications { get; }
internal ApplicationTerminationFailedEventArgs(IEnumerable<RunningApplication> applications)
{
Applications = applications;
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Keyboard;
namespace SafeExamBrowser.Client.Operations
{
internal class KeyboardInterceptorOperation : ClientOperation
{
private IKeyboardInterceptor keyboardInterceptor;
private ILogger logger;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public KeyboardInterceptorOperation(ClientContext context, IKeyboardInterceptor keyboardInterceptor, ILogger logger) : base(context)
{
this.keyboardInterceptor = keyboardInterceptor;
this.logger = logger;
}
public override OperationResult Perform()
{
logger.Info("Starting keyboard interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StartKeyboardInterception);
keyboardInterceptor.Start();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Stopping keyboard interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopKeyboardInterception);
keyboardInterceptor.Stop();
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Mouse;
namespace SafeExamBrowser.Client.Operations
{
internal class MouseInterceptorOperation : ClientOperation
{
private ILogger logger;
private IMouseInterceptor mouseInterceptor;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public MouseInterceptorOperation(ClientContext context, ILogger logger, IMouseInterceptor mouseInterceptor) : base(context)
{
this.logger = logger;
this.mouseInterceptor = mouseInterceptor;
}
public override OperationResult Perform()
{
logger.Info("Starting mouse interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StartMouseInterception);
mouseInterceptor.Start();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Stopping mouse interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopMouseInterception);
mouseInterceptor.Stop();
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Settings;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.Client.Operations
{
internal class ProctoringOperation : ClientOperation
{
private readonly IActionCenter actionCenter;
private readonly IProctoringController controller;
private readonly ILogger logger;
private readonly ITaskbar taskbar;
private readonly IUserInterfaceFactory uiFactory;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ProctoringOperation(
IActionCenter actionCenter,
ClientContext context,
IProctoringController controller,
ILogger logger,
ITaskbar taskbar,
IUserInterfaceFactory uiFactory) : base(context)
{
this.actionCenter = actionCenter;
this.controller = controller;
this.logger = logger;
this.taskbar = taskbar;
this.uiFactory = uiFactory;
}
public override OperationResult Perform()
{
if (Context.Settings.Proctoring.Enabled)
{
logger.Info("Initializing proctoring...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeProctoring);
controller.Initialize(Context.Settings.Proctoring);
if (Context.Settings.SessionMode == SessionMode.Server && Context.Settings.Proctoring.ShowRaiseHandNotification)
{
actionCenter.AddNotificationControl(uiFactory.CreateRaiseHandControl(controller, Location.ActionCenter, Context.Settings.Proctoring));
taskbar.AddNotificationControl(uiFactory.CreateRaiseHandControl(controller, Location.Taskbar, Context.Settings.Proctoring));
}
foreach (var notification in controller.Notifications)
{
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.ActionCenter));
if (Context.Settings.Proctoring.ShowTaskbarNotification)
{
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.Taskbar));
}
}
}
return OperationResult.Success;
}
public override OperationResult Revert()
{
if (Context.Settings.Proctoring.Enabled)
{
logger.Info("Terminating proctoring...");
StatusChanged?.Invoke(TextKey.OperationStatus_TerminateProctoring);
controller.Terminate();
foreach (var notification in controller.Notifications)
{
notification.Terminate();
}
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.Communication.Contracts.Proxies;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Client.Operations
{
internal class RuntimeConnectionOperation : ClientOperation
{
private ILogger logger;
private IRuntimeProxy runtime;
private Guid token;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public RuntimeConnectionOperation(ClientContext context, ILogger logger, IRuntimeProxy runtime, Guid token) : base(context)
{
this.logger = logger;
this.runtime = runtime;
this.token = token;
}
public override OperationResult Perform()
{
logger.Info("Initializing runtime connection...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeRuntimeConnection);
var connected = runtime.Connect(token);
if (connected)
{
logger.Info("Successfully connected to the runtime.");
}
else
{
logger.Error("Failed to connect to the runtime. Aborting startup...");
}
return connected ? OperationResult.Success : OperationResult.Failed;
}
public override OperationResult Revert()
{
logger.Info("Closing runtime connection...");
StatusChanged?.Invoke(TextKey.OperationStatus_CloseRuntimeConnection);
if (runtime.IsConnected)
{
var success = runtime.Disconnect();
if (success)
{
logger.Info("Successfully disconnected from the runtime.");
}
else
{
logger.Error("Failed to disconnect from the runtime!");
}
return success ? OperationResult.Success : OperationResult.Failed;
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Settings;
namespace SafeExamBrowser.Client.Operations
{
internal class ServerOperation : ClientOperation
{
private readonly ILogger logger;
private readonly IServerProxy server;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ServerOperation(ClientContext context, ILogger logger, IServerProxy server) : base(context)
{
this.logger = logger;
this.server = server;
}
public override OperationResult Perform()
{
if (Context.Settings.SessionMode == SessionMode.Server)
{
logger.Info("Initializing server...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer);
server.Initialize(
Context.AppConfig.ServerApi,
Context.AppConfig.ServerConnectionToken,
Context.AppConfig.ServerExamId,
Context.AppConfig.ServerOauth2Token,
Context.Settings.Server);
server.StartConnectivity();
}
return OperationResult.Success;
}
public override OperationResult Revert()
{
if (Context.Settings?.SessionMode == SessionMode.Server)
{
logger.Info("Finalizing server...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServer);
server.StopConnectivity();
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,371 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.Linq;
using SafeExamBrowser.Core.Contracts.Notifications;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Client.Operations
{
internal class ShellOperation : ClientOperation
{
private readonly IActionCenter actionCenter;
private readonly IAudio audio;
private readonly INotification aboutNotification;
private readonly IKeyboard keyboard;
private readonly ILogger logger;
private readonly INotification logNotification;
private readonly INativeMethods nativeMethods;
private readonly INetworkAdapter networkAdapter;
private readonly IPowerSupply powerSupply;
private readonly ISystemInfo systemInfo;
private readonly ITaskbar taskbar;
private readonly ITaskview taskview;
private readonly IText text;
private readonly IUserInterfaceFactory uiFactory;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ShellOperation(
IActionCenter actionCenter,
IAudio audio,
INotification aboutNotification,
ClientContext context,
IKeyboard keyboard,
ILogger logger,
INotification logNotification,
INativeMethods nativeMethods,
INetworkAdapter networkAdapter,
IPowerSupply powerSupply,
ISystemInfo systemInfo,
ITaskbar taskbar,
ITaskview taskview,
IText text,
IUserInterfaceFactory uiFactory) : base(context)
{
this.aboutNotification = aboutNotification;
this.actionCenter = actionCenter;
this.audio = audio;
this.keyboard = keyboard;
this.logger = logger;
this.logNotification = logNotification;
this.nativeMethods = nativeMethods;
this.networkAdapter = networkAdapter;
this.powerSupply = powerSupply;
this.systemInfo = systemInfo;
this.text = text;
this.taskbar = taskbar;
this.taskview = taskview;
this.uiFactory = uiFactory;
}
public override OperationResult Perform()
{
logger.Info("Initializing shell...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeShell);
InitializeSystemComponents();
InitializeActionCenter();
InitializeTaskbar();
InitializeTaskview();
InitializeActivators();
InitializeAlwaysOnState();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Terminating shell...");
StatusChanged?.Invoke(TextKey.OperationStatus_TerminateShell);
TerminateActivators();
TerminateNotifications();
TerminateSystemComponents();
return OperationResult.Success;
}
private void InitializeActivators()
{
foreach (var activator in Context.Activators)
{
if (Context.Settings.UserInterface.ActionCenter.EnableActionCenter && activator is IActionCenterActivator actionCenterActivator)
{
actionCenter.Register(actionCenterActivator);
actionCenterActivator.Start();
}
if (Context.Settings.Keyboard.AllowAltTab && activator is ITaskviewActivator taskViewActivator)
{
taskview.Register(taskViewActivator);
taskViewActivator.Start();
}
if (Context.Settings.Security.AllowTermination && activator is ITerminationActivator terminationActivator)
{
terminationActivator.Start();
}
if (Context.Settings.UserInterface.Taskbar.EnableTaskbar && activator is ITaskbarActivator taskbarActivator)
{
taskbar.Register(taskbarActivator);
taskbarActivator.Start();
}
}
}
private void InitializeActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.EnableActionCenter)
{
logger.Info("Initializing action center...");
actionCenter.InitializeText(text);
InitializeApplicationsFor(Location.ActionCenter);
InitializeAboutNotificationForActionCenter();
InitializeAudioForActionCenter();
InitializeClockForActionCenter();
InitializeLogNotificationForActionCenter();
InitializeKeyboardLayoutForActionCenter();
InitializeNetworkForActionCenter();
InitializePowerSupplyForActionCenter();
InitializeQuitButtonForActionCenter();
}
else
{
logger.Info("Action center is disabled, skipping initialization.");
}
}
private void InitializeAlwaysOnState()
{
var display = Context.Settings.Display.AlwaysOn;
var system = Context.Settings.System.AlwaysOn;
nativeMethods.SetAlwaysOnState(display, system);
logger.Info($"Display(s) will {(display ? "be always on" : "use the operating system configuration and may turn off")}.");
logger.Info($"System will {(system ? "be always on" : "use the operating system configuration and may enter sleep mode or standby")}.");
}
private void InitializeTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.EnableTaskbar)
{
logger.Info("Initializing taskbar...");
taskbar.InitializeText(text);
InitializeApplicationsFor(Location.Taskbar);
InitializeAboutNotificationForTaskbar();
InitializeLogNotificationForTaskbar();
InitializePowerSupplyForTaskbar();
InitializeNetworkForTaskbar();
InitializeAudioForTaskbar();
InitializeKeyboardLayoutForTaskbar();
InitializeClockForTaskbar();
InitializeQuitButtonForTaskbar();
}
else
{
logger.Info("Taskbar is disabled, skipping initialization.");
}
}
private void InitializeTaskview()
{
logger.Info("Initializing task view...");
foreach (var application in Context.Applications)
{
taskview.Add(application);
}
}
private void InitializeApplicationsFor(Location location)
{
foreach (var application in Context.Applications)
{
var settings = Context.Settings.Applications.Whitelist.First(a => a.Id == application.Id);
if (settings.ShowInShell)
{
var control = uiFactory.CreateApplicationControl(application, location);
switch (location)
{
case Location.ActionCenter:
actionCenter.AddApplicationControl(control);
break;
case Location.Taskbar:
taskbar.AddApplicationControl(control);
break;
}
}
}
}
private void InitializeSystemComponents()
{
audio.Initialize();
keyboard.Initialize();
networkAdapter.Initialize();
powerSupply.Initialize();
}
private void InitializeAboutNotificationForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowApplicationInfo)
{
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(aboutNotification, Location.ActionCenter));
}
}
private void InitializeAboutNotificationForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowApplicationInfo)
{
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(aboutNotification, Location.Taskbar));
}
}
private void InitializeAudioForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowAudio)
{
actionCenter.AddSystemControl(uiFactory.CreateAudioControl(audio, Location.ActionCenter));
}
}
private void InitializeAudioForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowAudio)
{
taskbar.AddSystemControl(uiFactory.CreateAudioControl(audio, Location.Taskbar));
}
}
private void InitializeClockForActionCenter()
{
actionCenter.ShowClock = Context.Settings.UserInterface.ActionCenter.ShowClock;
}
private void InitializeClockForTaskbar()
{
taskbar.ShowClock = Context.Settings.UserInterface.Taskbar.ShowClock;
}
private void InitializeLogNotificationForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowApplicationLog)
{
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(logNotification, Location.ActionCenter));
}
}
private void InitializeLogNotificationForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowApplicationLog)
{
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(logNotification, Location.Taskbar));
}
}
private void InitializeKeyboardLayoutForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowKeyboardLayout)
{
actionCenter.AddSystemControl(uiFactory.CreateKeyboardLayoutControl(keyboard, Location.ActionCenter));
}
}
private void InitializeKeyboardLayoutForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowKeyboardLayout)
{
taskbar.AddSystemControl(uiFactory.CreateKeyboardLayoutControl(keyboard, Location.Taskbar));
}
}
private void InitializePowerSupplyForActionCenter()
{
if (systemInfo.HasBattery)
{
actionCenter.AddSystemControl(uiFactory.CreatePowerSupplyControl(powerSupply, Location.ActionCenter));
}
}
private void InitializePowerSupplyForTaskbar()
{
if (systemInfo.HasBattery)
{
taskbar.AddSystemControl(uiFactory.CreatePowerSupplyControl(powerSupply, Location.Taskbar));
}
}
private void InitializeQuitButtonForActionCenter()
{
actionCenter.ShowQuitButton = Context.Settings.Security.AllowTermination;
}
private void InitializeQuitButtonForTaskbar()
{
taskbar.ShowQuitButton = Context.Settings.Security.AllowTermination;
}
private void InitializeNetworkForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowNetwork)
{
actionCenter.AddSystemControl(uiFactory.CreateNetworkControl(networkAdapter, Location.ActionCenter));
}
}
private void InitializeNetworkForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowNetwork)
{
taskbar.AddSystemControl(uiFactory.CreateNetworkControl(networkAdapter, Location.Taskbar));
}
}
private void TerminateActivators()
{
foreach (var activator in Context.Activators)
{
activator.Stop();
}
}
private void TerminateNotifications()
{
aboutNotification.Terminate();
logNotification.Terminate();
}
private void TerminateSystemComponents()
{
audio.Terminate();
keyboard.Terminate();
networkAdapter.Terminate();
powerSupply.Terminate();
}
}
}