SEBWIN-612, #625: Implemented basic clipboard functionality.
This commit is contained in:
parent
940baae655
commit
eff0051469
27 changed files with 463 additions and 47 deletions
|
@ -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_%%',
|
||||
|
|
172
SafeExamBrowser.Browser/Content/Clipboard.js
Normal file
172
SafeExamBrowser.Browser/Content/Clipboard.js
Normal 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);
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
28
SafeExamBrowser.Monitoring.Contracts/IClipboard.cs
Normal file
28
SafeExamBrowser.Monitoring.Contracts/IClipboard.cs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
|
|
77
SafeExamBrowser.Monitoring/Clipboard.cs
Normal file
77
SafeExamBrowser.Monitoring/Clipboard.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
31
SafeExamBrowser.Settings/Security/ClipboardPolicy.cs
Normal file
31
SafeExamBrowser.Settings/Security/ClipboardPolicy.cs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue