SEBWIN-612, #625: Implemented basic clipboard functionality.

This commit is contained in:
Damian Büchel 2023-07-21 09:31:59 +02:00
parent 940baae655
commit eff0051469
27 changed files with 463 additions and 47 deletions

View file

@ -1,4 +1,12 @@
SafeExamBrowser = {
/*
* Copyright (c) 2023 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/.
*/
SafeExamBrowser = {
version: 'SEB_Windows_%%_VERSION_%%',
security: {
browserExamKey: '%%_BEK_%%',

View file

@ -0,0 +1,172 @@
/*
* Copyright (c) 2023 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/.
*
* Original code taken and slightly adapted from https://github.com/eqsoft/seb2/blob/master/browser/app/modules/SebBrowser.jsm#L1215.
*/
SafeExamBrowser.clipboard = {
clear: function () {
ranges = [];
text = "";
},
ranges: [],
text: ""
}
function copySelectedData(e) {
if (e.target.contentEditable && e.target.setRangeText) {
SafeExamBrowser.clipboard.text = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd);
SafeExamBrowser.clipboard.ranges = [];
} else {
var selection = e.target.ownerDocument.defaultView.getSelection();
var text = "";
for (var i = 0; i < selection.rangeCount; i++) {
SafeExamBrowser.clipboard.ranges[i] = selection.getRangeAt(i).cloneContents();
text += SafeExamBrowser.clipboard.ranges[i].textContent;
}
SafeExamBrowser.clipboard.text = text;
}
}
function cutSelectedData(e) {
if (e.target.contentEditable && e.target.setRangeText) {
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
} else {
var designMode = e.target.ownerDocument.designMode;
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
var selection = e.target.ownerDocument.defaultView.getSelection();
for (var i = 0; i < selection.rangeCount; i++) {
var range = selection.getRangeAt(i);
if (designMode === 'on') {
range.deleteContents();
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
if (node.contains(range.commonAncestorContainer)) {
range.deleteContents();
}
});
}
}
}
}
}
function pasteSelectedData(e) {
if (e.target.contentEditable && e.target.setRangeText) {
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
e.target.setRangeText(SafeExamBrowser.clipboard.text, e.target.selectionStart, e.target.selectionStart + SafeExamBrowser.clipboard.text.length, 'end');
} else {
var w = e.target.ownerDocument.defaultView;
var designMode = e.target.ownerDocument.designMode;
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
var selection = w.getSelection();
for (var i = 0; i < selection.rangeCount; i++) {
var r = selection.getRangeAt(i);
if (designMode === 'on') {
r.deleteContents();
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
if (node.contains(r.commonAncestorContainer)) {
r.deleteContents();
}
});
}
}
}
if (designMode === 'on') {
var range = w.getSelection().getRangeAt(0);
if (SafeExamBrowser.clipboard.ranges.length > 0) {
SafeExamBrowser.clipboard.ranges.map(r => {
range = w.getSelection().getRangeAt(0);
range.collapse();
const newNode = r.cloneNode(true);
range.insertNode(newNode);
range.collapse();
});
} else {
range.collapse();
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
range.collapse();
}
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
var range = w.getSelection().getRangeAt(0);
if (node.contains(range.commonAncestorContainer)) {
if (SafeExamBrowser.clipboard.ranges.length > 0) {
SafeExamBrowser.clipboard.ranges.map(r => {
range = w.getSelection().getRangeAt(0);
range.collapse();
const newNode = r.cloneNode(true);
range.insertNode(newNode);
range.collapse();
});
} else {
range = w.getSelection().getRangeAt(0);
range.collapse();
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
range.collapse();
}
}
});
}
}
}
}
function onCopy(e) {
SafeExamBrowser.clipboard.clear();
try {
copySelectedData(e);
} finally {
e.preventDefault();
e.returnValue = false;
}
return false;
}
function onCut(e) {
SafeExamBrowser.clipboard.clear();
try {
copySelectedData(e);
cutSelectedData(e);
} finally {
e.preventDefault();
e.returnValue = false;
}
return false;
}
function onPaste(e) {
try {
pasteSelectedData(e);
} finally {
e.preventDefault();
e.returnValue = false;
}
return false;
}
window.document.addEventListener("copy", onCopy, true);
window.document.addEventListener("cut", onCut, true);
window.document.addEventListener("paste", onPaste, true);

View file

@ -14,8 +14,10 @@ namespace SafeExamBrowser.Browser.Content
{
internal class ContentLoader
{
private readonly IText text;
private string api;
private IText text;
private string clipboard;
internal ContentLoader(IText text)
{
@ -24,7 +26,7 @@ namespace SafeExamBrowser.Browser.Content
internal string LoadApi(string browserExamKey, string configurationKey, string version)
{
if (api == default(string))
if (api == default)
{
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.Api.js";
@ -78,5 +80,22 @@ namespace SafeExamBrowser.Browser.Content
return html;
}
}
internal string LoadClipboard()
{
if (clipboard == default)
{
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.Clipboard.js";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
{
clipboard = reader.ReadToEnd();
}
}
return clipboard;
}
}
}

View file

@ -37,6 +37,7 @@ namespace SafeExamBrowser.Browser.Handlers
var browserExamKey = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, frame.Url);
var configurationKey = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, frame.Url);
var api = contentLoader.LoadApi(browserExamKey, configurationKey, appConfig.ProgramBuildVersion);
var clipboard = contentLoader.LoadClipboard();
frame.ExecuteJavaScriptAsync(api);
@ -44,6 +45,11 @@ namespace SafeExamBrowser.Browser.Handlers
{
frame.ExecuteJavaScriptAsync($"window.print = function(){{ alert('{text.Get(TextKey.Browser_PrintNotAllowed)}') }}");
}
if (settings.UseIsolatedClipboard)
{
frame.ExecuteJavaScriptAsync(clipboard);
}
}
public void OnContextReleased(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)

View file

@ -189,6 +189,9 @@
<ItemGroup>
<EmbeddedResource Include="Content\Api.js" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Content\Clipboard.js" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>

View file

@ -10,43 +10,44 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Security;
namespace SafeExamBrowser.Client.UnitTests.Operations
{
[TestClass]
public class ClipboardOperationTests
{
private Mock<IClipboard> clipboard;
private ClientContext context;
private Mock<ILogger> loggerMock;
private Mock<INativeMethods> nativeMethodsMock;
private ClipboardOperation sut;
[TestInitialize]
public void Initialize()
{
clipboard = new Mock<IClipboard>();
context = new ClientContext();
context.Settings = new AppSettings();
loggerMock = new Mock<ILogger>();
nativeMethodsMock = new Mock<INativeMethods>();
sut = new ClipboardOperation(context, loggerMock.Object, nativeMethodsMock.Object);
sut = new ClipboardOperation(context, clipboard.Object, loggerMock.Object);
}
[TestMethod]
public void MustPerformCorrectly()
{
sut.Perform();
nativeMethodsMock.Verify(n => n.EmptyClipboard(), Times.Once);
clipboard.Verify(n => n.Initialize(It.IsAny<ClipboardPolicy>()), Times.Once);
}
[TestMethod]
public void MustRevertCorrectly()
{
sut.Revert();
nativeMethodsMock.Verify(n => n.EmptyClipboard(), Times.Once);
clipboard.Verify(n => n.Terminate(), Times.Once);
}
}
}

View file

@ -28,6 +28,7 @@ using SafeExamBrowser.I18n;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring;
using SafeExamBrowser.Monitoring.Applications;
using SafeExamBrowser.Monitoring.Display;
using SafeExamBrowser.Monitoring.Keyboard;
@ -110,6 +111,7 @@ namespace SafeExamBrowser.Client
var processFactory = 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, new Registry(ModuleLogger(nameof(Registry))));
var clipboard = new Clipboard(ModuleLogger(nameof(Clipboard)), nativeMethods);
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var fileSystemDialog = BuildFileSystemDialog();
@ -136,7 +138,7 @@ namespace SafeExamBrowser.Client
operations.Enqueue(new LazyInitializationOperation(BuildBrowserOperation));
operations.Enqueue(new LazyInitializationOperation(BuildServerOperation));
operations.Enqueue(new LazyInitializationOperation(BuildProctoringOperation));
operations.Enqueue(new ClipboardOperation(context, logger, nativeMethods));
operations.Enqueue(new ClipboardOperation(context, clipboard, logger));
var sequence = new OperationSequence(logger, operations);

View file

@ -10,44 +10,50 @@ using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
namespace SafeExamBrowser.Client.Operations
{
internal class ClipboardOperation : ClientOperation
{
private ILogger logger;
private INativeMethods nativeMethods;
private readonly IClipboard clipboard;
private readonly ILogger logger;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ClipboardOperation(ClientContext context, ILogger logger, INativeMethods nativeMethods) : base(context)
public ClipboardOperation(ClientContext context, IClipboard clipboard, ILogger logger) : base(context)
{
this.clipboard = clipboard;
this.logger = logger;
this.nativeMethods = nativeMethods;
}
public override OperationResult Perform()
{
EmptyClipboard();
InitializeClipboard();
return OperationResult.Success;
}
public override OperationResult Revert()
{
EmptyClipboard();
FinalizeClipboard();
return OperationResult.Success;
}
private void EmptyClipboard()
private void InitializeClipboard()
{
logger.Info("Emptying clipboard...");
StatusChanged?.Invoke(TextKey.OperationStatus_EmptyClipboard);
logger.Info("Initializing clipboard...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeClipboard);
clipboard.Initialize(Context.Settings.Security.ClipboardPolicy);
}
nativeMethods.EmptyClipboard();
private void FinalizeClipboard()
{
logger.Info("Finalizing clipboard...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeClipboard);
clipboard.Terminate();
}
}
}

View file

@ -13,6 +13,7 @@ using System.Security.Cryptography;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.Settings.Security;
namespace SafeExamBrowser.Configuration.ConfigurationData
{
@ -22,7 +23,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
AllowBrowserToolbarForReloading(settings);
CalculateConfigurationKey(rawData, settings);
HandleBrowserHomeFunctionality(settings);
InitializeBrowserHomeFunctionality(settings);
InitializeClipboardSettings(settings);
InitializeProctoringSettings(settings);
RemoveLegacyBrowsers(settings);
}
@ -58,12 +60,20 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
}
}
private void HandleBrowserHomeFunctionality(AppSettings settings)
private void InitializeBrowserHomeFunctionality(AppSettings settings)
{
settings.Browser.MainWindow.ShowHomeButton = settings.Browser.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.Browser.HomeUrl);
settings.Browser.HomePasswordHash = settings.Security.QuitPasswordHash;
}
private void InitializeClipboardSettings(AppSettings settings)
{
settings.Browser.UseIsolatedClipboard = settings.Security.ClipboardPolicy == ClipboardPolicy.Isolated;
settings.Keyboard.AllowCtrlC = settings.Security.ClipboardPolicy != ClipboardPolicy.Block;
settings.Keyboard.AllowCtrlV = settings.Security.ClipboardPolicy != ClipboardPolicy.Block;
settings.Keyboard.AllowCtrlX = settings.Security.ClipboardPolicy != ClipboardPolicy.Block;
}
private void InitializeProctoringSettings(AppSettings settings)
{
settings.Proctoring.Enabled = settings.Proctoring.JitsiMeet.Enabled || settings.Proctoring.Zoom.Enabled;

View file

@ -260,6 +260,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Security.AllowApplicationLogAccess = false;
settings.Security.AllowTermination = true;
settings.Security.AllowReconfiguration = false;
settings.Security.ClipboardPolicy = ClipboardPolicy.Isolated;
settings.Security.KioskMode = KioskMode.CreateNewDesktop;
settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Deny;

View file

@ -172,13 +172,14 @@ namespace SafeExamBrowser.I18n.Contracts
Notification_ProctoringLowerHand,
Notification_ProctoringRaiseHand,
OperationStatus_CloseRuntimeConnection,
OperationStatus_EmptyClipboard,
OperationStatus_FinalizeApplications,
OperationStatus_FinalizeClipboard,
OperationStatus_FinalizeServer,
OperationStatus_FinalizeServiceSession,
OperationStatus_FinalizeSystemEvents,
OperationStatus_InitializeApplications,
OperationStatus_InitializeBrowser,
OperationStatus_InitializeClipboard,
OperationStatus_InitializeConfiguration,
OperationStatus_InitializeKioskMode,
OperationStatus_InitializeProctoring,

View file

@ -474,12 +474,12 @@
<Entry key="OperationStatus_CloseRuntimeConnection">
Schliesse Verbindung zur Runtime
</Entry>
<Entry key="OperationStatus_EmptyClipboard">
Lösche Zwischenablage
</Entry>
<Entry key="OperationStatus_FinalizeApplications">
Beende Applikationen
</Entry>
<Entry key="OperationStatus_FinalizeClipboard">
Finalisiere Zwischenablage
</Entry>
<Entry key="OperationStatus_FinalizeServer">
Finalisiere SEB-Server
</Entry>
@ -495,6 +495,9 @@
<Entry key="OperationStatus_InitializeBrowser">
Initialisiere Browser
</Entry>
<Entry key="OperationStatus_InitializeClipboard">
Initialisiere Zwischenablage
</Entry>
<Entry key="OperationStatus_InitializeConfiguration">
Initialisiere Konfiguration
</Entry>

View file

@ -474,12 +474,12 @@
<Entry key="OperationStatus_CloseRuntimeConnection">
Closing runtime connection
</Entry>
<Entry key="OperationStatus_EmptyClipboard">
Emptying clipboard
</Entry>
<Entry key="OperationStatus_FinalizeApplications">
Finalizing applications
</Entry>
<Entry key="OperationStatus_FinalizeClipboard">
Finalizing clipboard
</Entry>
<Entry key="OperationStatus_FinalizeServer">
Finalizing SEB-Server
</Entry>
@ -495,6 +495,9 @@
<Entry key="OperationStatus_InitializeBrowser">
Initializing browser
</Entry>
<Entry key="OperationStatus_InitializeClipboard">
Initializing clipboard
</Entry>
<Entry key="OperationStatus_InitializeConfiguration">
Initializing configuration
</Entry>

View file

@ -474,12 +474,12 @@
<Entry key="OperationStatus_CloseRuntimeConnection">
Cierre de la conexión con runtime
</Entry>
<Entry key="OperationStatus_EmptyClipboard">
Vaciado del portapapeles
</Entry>
<Entry key="OperationStatus_FinalizeApplications">
Finalización de las aplicaciones
</Entry>
<Entry key="OperationStatus_FinalizeClipboard">
Finalización del portapapeles
</Entry>
<Entry key="OperationStatus_FinalizeServer">
Finalización del SEB-Server
</Entry>
@ -495,6 +495,9 @@
<Entry key="OperationStatus_InitializeBrowser">
Inicialización del navegador
</Entry>
<Entry key="OperationStatus_InitializeClipboard">
Inicialización del portapapeles
</Entry>
<Entry key="OperationStatus_InitializeConfiguration">
Inicialización de la configuración
</Entry>

View file

@ -474,12 +474,12 @@
<Entry key="OperationStatus_CloseRuntimeConnection">
Fermeture de la connexion
</Entry>
<Entry key="OperationStatus_EmptyClipboard">
Vidage du presse-papiers
</Entry>
<Entry key="OperationStatus_FinalizeApplications">
Finalisation des applications
</Entry>
<Entry key="OperationStatus_FinalizeClipboard">
Finalisation du presse-papiers
</Entry>
<Entry key="OperationStatus_FinalizeServer">
Finalisation du serveur SEB
</Entry>
@ -495,6 +495,9 @@
<Entry key="OperationStatus_InitializeBrowser">
Initialisation du navigateur
</Entry>
<Entry key="OperationStatus_InitializeClipboard">
Initialisation du presse-papiers
</Entry>
<Entry key="OperationStatus_InitializeConfiguration">
Initialisation de la configuration
</Entry>

View file

@ -474,12 +474,12 @@
<Entry key="OperationStatus_CloseRuntimeConnection">
Chiusura della connessione runtime
</Entry>
<Entry key="OperationStatus_EmptyClipboard">
Eliminazione appunti
</Entry>
<Entry key="OperationStatus_FinalizeApplications">
Finalizzazione delle applicazioni
</Entry>
<Entry key="OperationStatus_FinalizeClipboard">
Finalizzazione degli appunti
</Entry>
<Entry key="OperationStatus_FinalizeServer">
Finalizzazione server SEB
</Entry>
@ -495,6 +495,9 @@
<Entry key="OperationStatus_InitializeBrowser">
Inizializzazione del browser
</Entry>
<Entry key="OperationStatus_InitializeClipboard">
Inizializzazione degli appunti
</Entry>
<Entry key="OperationStatus_InitializeConfiguration">
Inizializzazione della configurazione
</Entry>

View file

@ -438,12 +438,12 @@
<Entry key="OperationStatus_CloseRuntimeConnection">
关闭运行时连接
</Entry>
<Entry key="OperationStatus_EmptyClipboard">
清空剪贴板
</Entry>
<Entry key="OperationStatus_FinalizeApplications">
完成应用程序(运行已定型)
</Entry>
<Entry key="OperationStatus_FinalizeClipboard">
剪贴板定稿
</Entry>
<Entry key="OperationStatus_FinalizeServiceSession">
完成服务会话(运行已定型)
</Entry>
@ -453,6 +453,9 @@
<Entry key="OperationStatus_InitializeBrowser">
初始化浏览器
</Entry>
<Entry key="OperationStatus_InitializeClipboard">
初始化剪贴板
</Entry>
<Entry key="OperationStatus_InitializeConfiguration">
初始化配置
</Entry>

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 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.Settings.Security;
namespace SafeExamBrowser.Monitoring.Contracts
{
/// <summary>
/// Monitors the system clipboard and provides related functionality.
/// </summary>
public interface IClipboard
{
/// <summary>
/// Initializes the system clipboard according to the given policy.
/// </summary>
void Initialize(ClipboardPolicy policy);
/// <summary>
/// Finalizes the system clipboard.
/// </summary>
void Terminate();
}
}

View file

@ -62,6 +62,7 @@
<Compile Include="Applications\Events\ExplorerStartedEventHandler.cs" />
<Compile Include="Display\IDisplayMonitor.cs" />
<Compile Include="Display\ValidationResult.cs" />
<Compile Include="IClipboard.cs" />
<Compile Include="Keyboard\IKeyboardInterceptor.cs" />
<Compile Include="Mouse\IMouseInterceptor.cs" />
<Compile Include="Applications\InitializationResult.cs" />

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2023 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.Timers;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
using SafeExamBrowser.Settings.Security;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Monitoring
{
public class Clipboard : IClipboard
{
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly Timer timer;
private ClipboardPolicy policy;
public Clipboard(ILogger logger, INativeMethods nativeMethods, int timeout_ms = 50)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
this.timer = new Timer(timeout_ms);
}
public void Initialize(ClipboardPolicy policy)
{
this.policy = policy;
nativeMethods.EmptyClipboard();
logger.Debug("Cleared clipboard.");
if (policy != ClipboardPolicy.Allow)
{
timer.Elapsed += Timer_Elapsed;
timer.Start();
logger.Debug($"Started clipboard monitoring with interval {timer.Interval} ms.");
}
else
{
logger.Debug("Clipboard is allowed, not starting monitoring.");
}
logger.Info($"Initialized clipboard for policy '{policy}'.");
}
public void Terminate()
{
nativeMethods.EmptyClipboard();
logger.Debug("Cleared clipboard.");
if (policy != ClipboardPolicy.Allow)
{
timer.Stop();
logger.Debug("Stopped clipboard monitoring.");
}
else
{
logger.Debug("Clipboard monitoring was not active.");
}
logger.Info($"Finalized clipboard.");
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
nativeMethods.EmptyClipboard();
}
}
}

View file

@ -20,9 +20,9 @@ namespace SafeExamBrowser.Monitoring.Keyboard
public class KeyboardInterceptor : IKeyboardInterceptor
{
private Guid? hookId;
private ILogger logger;
private INativeMethods nativeMethods;
private KeyboardSettings settings;
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly KeyboardSettings settings;
public KeyboardInterceptor(ILogger logger, INativeMethods nativeMethods, KeyboardSettings settings)
{
@ -66,11 +66,16 @@ namespace SafeExamBrowser.Monitoring.Keyboard
block |= key == Key.LWin && !settings.AllowSystemKey;
block |= key == Key.PrintScreen && !settings.AllowPrintScreen;
block |= key == Key.RWin && !settings.AllowSystemKey;
block |= modifier.HasFlag(KeyModifier.Alt) && key == Key.Escape && !settings.AllowAltEsc;
block |= modifier.HasFlag(KeyModifier.Alt) && key == Key.F4 && !settings.AllowAltF4;
block |= modifier.HasFlag(KeyModifier.Alt) && key == Key.Space;
block |= modifier.HasFlag(KeyModifier.Alt) && key == Key.Tab;
block |= modifier.HasFlag(KeyModifier.Ctrl) && key == Key.C && !settings.AllowCtrlC;
block |= modifier.HasFlag(KeyModifier.Ctrl) && key == Key.Escape && !settings.AllowCtrlEsc;
block |= modifier.HasFlag(KeyModifier.Ctrl) && key == Key.V && !settings.AllowCtrlV;
block |= modifier.HasFlag(KeyModifier.Ctrl) && key == Key.X && !settings.AllowCtrlX;
if (block)
{

View file

@ -58,6 +58,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Applications\Window.cs" />
<Compile Include="Clipboard.cs" />
<Compile Include="Display\Bounds.cs" />
<Compile Include="Display\Display.cs" />
<Compile Include="Display\DisplayMonitor.cs" />

View file

@ -203,6 +203,11 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary>
public bool UseCustomUserAgent { get; set; }
/// <summary>
/// Determines whether the browser application will use an isolated clipboard only working within the browser itself.
/// </summary>
public bool UseIsolatedClipboard { get; set; }
/// <summary>
/// Determines whether the <see cref="StartUrlQuery"/> will be appended to the <see cref="StartUrl"/>.
/// </summary>

View file

@ -31,11 +31,26 @@ namespace SafeExamBrowser.Settings.Monitoring
/// </summary>
public bool AllowAltTab { get; set; }
/// <summary>
/// Determines whether the user may use the CTRL+C shortcut.
/// </summary>
public bool AllowCtrlC { get; set; }
/// <summary>
/// Determines whether the user may use the CTRL+ESC shortcut.
/// </summary>
public bool AllowCtrlEsc { get; set; }
/// <summary>
/// Determines whether the user may use the CTRL+V shortcut.
/// </summary>
public bool AllowCtrlV { get; set; }
/// <summary>
/// Determines whether the user may use the CTRL+X shortcut.
/// </summary>
public bool AllowCtrlX { get; set; }
/// <summary>
/// Determines whether the user may use the escape key.
/// </summary>

View file

@ -77,6 +77,7 @@
<Compile Include="Proctoring\ProctoringSettings.cs" />
<Compile Include="Proctoring\WindowVisibility.cs" />
<Compile Include="Proctoring\ZoomSettings.cs" />
<Compile Include="Security\ClipboardPolicy.cs" />
<Compile Include="Security\VersionRestriction.cs" />
<Compile Include="SessionMode.cs" />
<Compile Include="Security\KioskMode.cs" />

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 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/.
*/
namespace SafeExamBrowser.Settings.Security
{
/// <summary>
/// Defines all policies with respect to the usage of the clipboard.
/// </summary>
public enum ClipboardPolicy
{
/// <summary>
/// Allows the usage of the system clipboard without restrictions.
/// </summary>
Allow,
/// <summary>
/// Completely blocks the usage of the system clipboard by continuously clearing its content and blocking all related keyboard shortcuts.
/// </summary>
Block,
/// <summary>
/// Continuously clears the content of the system clipboard and enables an isolated clipboard only working within the browser application.
/// </summary>
Isolated
}
}

View file

@ -37,6 +37,11 @@ namespace SafeExamBrowser.Settings.Security
/// </summary>
public bool AllowReconfiguration { get; set; }
/// <summary>
/// Determines whether the user is allowed to use the system clipboard, a custom clipboard or no clipboard at all.
/// </summary>
public ClipboardPolicy ClipboardPolicy { get; set; }
/// <summary>
/// The kiosk mode which determines how the computer is locked down.
/// </summary>