Compare commits

...

53 commits

Author SHA1 Message Date
Damian Büchel
7029b12d81 SEBWIN-935: Removed old browser engine runtime references. 2024-09-09 15:08:39 +02:00
Damian Büchel
9762019499 SEBWIN-936: Resolved errors and improved transmission spooler, reduced and improved logging of service and server proxies resp. requests, specified timeout for service requests and fixed missing cache path in proctoring finalization dialog. 2024-09-04 15:14:55 +02:00
Damian Büchel
d5b182ae2f SEBWIN-852: Ensured page zoom via CTRL+MOUSEWHEEL also works according to the respective configuration value. 2024-08-29 10:08:36 +02:00
Damian Büchel
4c0f3cfa6c SEBWIN-934: Ensured window title of active application is always current and fixed encoding of screen shot metadata. 2024-08-28 14:57:20 +02:00
Damian Büchel
f096b96741 SEBWIN-925: Reintegrated Themida. 2024-08-27 17:46:25 +02:00
Damian Büchel
50ac28f9ea SEBWIN-925: Removed Themida. 2024-08-22 16:29:47 +02:00
Damian Büchel
144c3ba752 Minor refactoring. 2024-08-22 16:26:58 +02:00
Damian Büchel
21353e6d6d SEBWIN-865: Integrated final screen proctoring notification icons. 2024-08-21 15:15:51 +02:00
Damian Büchel
26f14f235d SEBWIN-931: Added missing default settings related to clipboard and ensured data processor is also executed with default settings. 2024-08-19 12:19:32 +02:00
Damian Büchel
1ff7d84375 Corrected comment for ImageQuantiziation.Color16bpp. 2024-08-16 11:08:14 +02:00
Damian Büchel
febfd944e0 SEBWIN-923: Removed hardcoded client credentials for SPS and used actual ones from join instruction. 2024-08-12 14:42:09 +02:00
Damian Büchel
a1bfaadcd9 SEBWIN-882, SEBWIN-904, #914: Ensured monitoring is terminated before reconfiguration. 2024-07-29 16:37:17 +02:00
Damian Büchel
0b1746a82e SEBWIN-916: Ensured timestamp of a screen shot request is capture and not transmission time. 2024-07-25 17:41:55 +02:00
Damian Büchel
ede6a926cc SEBWIN-913: Removed URLs from browser meta data for screen proctoring. 2024-07-25 17:14:17 +02:00
Damian Büchel
d4f5f203db SEBWIN-909: Ensured individual keys are not transmitted as part of the screen proctoring meta data and improved presentation of single modifier key triggers. 2024-07-25 16:49:52 +02:00
Damian Büchel
a350949b1b SEBWIN-917: Consolidated detectors in monitoring assembly. 2024-07-25 15:30:56 +02:00
Damian Büchel
6a77a41564 SEBWIN-917: Consolidated system (events) monitoring in sentinel. 2024-07-25 12:22:49 +02:00
Damian Büchel
1f50ab74c9 SEBWIN-902: Implemented fix for GHSA-9cr5-q96r-887f and refactored various integrity aspects. 2024-07-24 20:31:08 +02:00
Damian Büchel
b48ef21708 SEBWIN-771: Implemented reconfiguration safeguard and refactored and moved reconfiguration and session locking to new coordinator module. 2024-07-15 18:34:30 +02:00
Damian Büchel
f3a9030505 SEBWIN-914: Reactivated disclaimer for screen proctoring. 2024-07-09 13:38:04 +02:00
Damian Büchel
89091acaac SEBWIN-914: Build without disclaimer for SPS load tests. 2024-07-09 13:36:17 +02:00
Damian Büchel
04843d3fa8 SEBWIN-907: Fixed bug where start URL query parameters wouldn't be applied when using SEB Server. 2024-07-01 15:25:08 +02:00
Damian Büchel
a3c9271faf SEBWIN-742: Improved build log for server environment. 2024-06-25 17:21:33 +02:00
Damian Büchel
68d6d47fe6 SEBWIN-742: Integrated Themida into build process. 2024-06-24 14:40:42 +02:00
Damian Büchel
b4366adab1 SEBWIN-896, #805: Removed duplicated entries for default list of prohibited applications. 2024-06-17 12:12:36 +02:00
Damian Büchel
c23b78488c SEBWIN-897: Corrected default values for down- and uploads. 2024-06-13 17:34:36 +02:00
Damian Büchel
58c8e69716 SEBWIN-893, #883: Implemented unit test for concurrency issue resolution. 2024-06-13 15:29:58 +02:00
Damian Büchel
0fb7f23bcb SEBWIN-836: Grouped all settings related to the user interface. 2024-06-12 18:18:52 +02:00
Damian Büchel
05f46cd6b4 SEBWIN-836: Implemented configuration value for lock screen background color. 2024-06-12 17:30:19 +02:00
Damian Büchel
f2798581a4 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2024-06-10 19:40:00 +02:00
Damian Büchel
471e69d460 SEBWIN-788: Improved network adapter implementation. 2024-06-10 19:39:58 +02:00
Damian Büchel
62dc690a52
Updated security policy. 2024-06-10 10:13:08 +02:00
Damian Büchel
a41b40d428
Updated security policy. 2024-06-10 10:11:49 +02:00
Damian Büchel
8cf214b39c
Created security policy. 2024-06-10 10:08:20 +02:00
Damian Büchel
04dce13d86 SEBWIN-893, #883: Attempt to fix possible concurrency issue with (configuration) key hash calculation. 2024-06-06 18:47:04 +02:00
Damian Büchel
767ac84391 SEBWIN-844, #790: Implemented configuration option for session integrity verification. 2024-06-05 19:30:35 +02:00
Damian Büchel
84bbcb82ef SEBWIN-788: Implemented automatic connection attempt and retry on invalid credentials. Improved wording of username label in credentials dialog. 2024-06-03 19:41:54 +02:00
Damian Büchel
b3228aedef SEBWIN-782, #703: Ensured browser session remains active after reconfiguration by browser resource. 2024-05-24 15:46:01 +02:00
Damian Büchel
3b099688f7 SEBWIN-849: Implemented index suffix for already existing files when downloading. 2024-05-22 15:25:52 +02:00
Damian Büchel
639700abd8 Merge branch 'master' of https://github.com/SafeExamBrowser/seb-win-refactoring 2024-05-21 19:11:43 +02:00
Damian Büchel
473edc7a2e SEBWIN-788: Finished implementation of new (wireless) network adapter and authentication functionality. 2024-05-21 19:11:42 +02:00
Damian Büchel
60ee95a9ee
Removed WebView2 dependency. 2024-05-07 11:49:36 +02:00
Damian Büchel
1edde7b6f5
Updated build server URL. 2024-05-07 11:48:10 +02:00
Damian Büchel
4015e9a574 SEBWIN-788: Implemented scaffolding for wireless network credentials. 2024-05-02 10:30:26 +02:00
Damian Büchel
bbb5ec2571
Merge pull request #852 from Notselwyn/patch-1
SEBWIN-789: remove historic hw VM check
2024-04-22 18:33:38 +02:00
Lau
8b3f9b0838
Update VirtualMachineDetector.cs 2024-04-22 11:26:20 +02:00
Damian Büchel
e4a82e2f63 SEBWIN-795: Improved user session resolution with SEB Server. 2024-04-22 11:09:37 +02:00
Damian Büchel
01db8fd84e SEBWIN-878, #848: Fixed session cookie name for user resolution with Moodle and SEB Server. 2024-04-17 12:05:34 +02:00
Damian Büchel
a397446252 Added KGy SOFT to license and version information. 2024-04-17 09:38:18 +02:00
Damian Büchel
e8ebd2840e SEBWIN-833: Completely deleted all Jitsi Meet and Zoom video proctoring code and removed WebView2 dependency. 2024-04-17 09:19:18 +02:00
Damian Büchel
d9662ec31e
Added known issues to exemptions for issue maintenance. 2024-04-11 12:07:56 +02:00
Damian Büchel
c2f61ea6ab SEBWIN-871: Fixed unit tests due to proctoring implementation changes. 2024-04-04 17:36:19 +02:00
Damian Büchel
7801d68b97 Updated version to 3.8.0 beta. 2024-04-04 17:26:11 +02:00
183 changed files with 4237 additions and 2992 deletions

View file

@ -21,5 +21,5 @@ jobs:
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
exempt-issue-labels: "bug,enhancement,feature request"
exempt-issue-labels: "bug,enhancement,feature request,known issue"
repo-token: ${{ secrets.GITHUB_TOKEN }}

Binary file not shown.

View file

@ -7,7 +7,6 @@ Refactored version of Safe Exam Browser for Windows with Chromium as integrated
SEB 3.x requires the prerequisites listed below in order to work correctly. These are automatically installed with the setup bundle and need only be manually installed when using the MSI packages.
* .NET Framework 4.8 Runtime: https://dotnet.microsoft.com/download/dotnet-framework/net48
* Microsoft Edge WebView2 Runtime: https://go.microsoft.com/fwlink/p/?LinkId=2124703
* Visual C++ 2015-2019 Redistributable: https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads
## Project Status
@ -17,7 +16,7 @@ SEB 3.x requires the prerequisites listed below in order to work correctly. Thes
| Aspect | Status | Details |
| ----------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Development Build | ![Development Build Status](https://sebdev-let.ethz.ch/api/projects/status/kq78qrjtnpk82ti0?svg=true) | https://sebdev-let.ethz.ch/project/appveyor/seb-win-refactoring |
| Development Build | ![Development Build Status](https://sebdev.ethz.ch/api/projects/status/kq78qrjtnpk82ti0?svg=true) | https://sebdev.ethz.ch/project/appveyor/seb-win-refactoring |
| Test Build | ![Test Build Status](https://ci.appveyor.com/api/projects/status/a56akt9r174570m7?svg=true) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Test Run | ![AppVeyor Tests](https://img.shields.io/appveyor/tests/dbuechel/seb-win-refactoring?logo=appveyor&logoColor=%23ccc) | https://ci.appveyor.com/project/dbuechel/seb-win-refactoring |
| Code Coverage | ![Code Coverage](https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring/branch/master/graph/badge.svg) | https://codecov.io/gh/SafeExamBrowser/seb-win-refactoring |

35
SECURITY.md Normal file
View file

@ -0,0 +1,35 @@
# Security Policy
We only support the latest official relese version with respect to security vulnerabilities. Thus, only the latest or then the upcoming next release version
will receive vulnerability fixes and security updates. A vulnerability may however be reported for any version, unless it already has been fixed with a later
release version.
## Reporting a Vulnerability
> [!IMPORTANT]
> - Please _always_ verify that no later release version exists which fixes the vulnerability.
> - Please _always_ consult the documentation first before creating a vulnerability report: https://safeexambrowser.org/windows/win_usermanual_en.html.
> - Please _always_ attach the log file(s) of the affected session(s)! They can be found under `%LocalAppData%\SafeExamBrowser\Logs`.
**Describe the Vulnerability**
A clear and concise description of what the vulnerability is.
**Steps to Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See ...
**Expected Behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Version Information**
- OS: [e.g. Windows 10 Professional, Version 1803]
- SEB-Version [e.g. SEB 3.0.1]
**Additional Context**
Add any other context about the vulnerability here.

View file

@ -6,8 +6,6 @@
<Import Project="..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props')" />
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props')" />
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props')" />
<Import Project="..\packages\cef.redist.x86.120.2.7\build\cef.redist.x86.props" Condition="Exists('..\packages\cef.redist.x86.120.2.7\build\cef.redist.x86.props')" />
<Import Project="..\packages\cef.redist.x64.120.2.7\build\cef.redist.x64.props" Condition="Exists('..\packages\cef.redist.x64.120.2.7\build\cef.redist.x64.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -206,8 +204,6 @@
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\cef.redist.x64.120.2.7\build\cef.redist.x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\cef.redist.x64.120.2.7\build\cef.redist.x64.props'))" />
<Error Condition="!Exists('..\packages\cef.redist.x86.120.2.7\build\cef.redist.x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\cef.redist.x86.120.2.7\build\cef.redist.x86.props'))" />
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x64.121.3.13\build\chromiumembeddedframework.runtime.win-x64.props'))" />
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x86.121.3.13\build\chromiumembeddedframework.runtime.win-x86.props'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.props'))" />

View file

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="5.1.1" targetFramework="net48" />
<package id="cef.redist.x64" version="120.2.7" targetFramework="net48" />
<package id="cef.redist.x86" version="120.2.7" targetFramework="net48" />
<package id="CefSharp.Common" version="121.3.130" targetFramework="net48" />
<package id="chromiumembeddedframework.runtime.win-x64" version="121.3.13" targetFramework="net48" />
<package id="chromiumembeddedframework.runtime.win-x86" version="121.3.13" targetFramework="net48" />

View file

@ -18,6 +18,7 @@ namespace SafeExamBrowser.Browser.Content
private string api;
private string clipboard;
private string pageZoom;
internal ContentLoader(IText text)
{
@ -97,5 +98,22 @@ namespace SafeExamBrowser.Browser.Content
return clipboard;
}
internal string LoadPageZoom()
{
if (pageZoom == default)
{
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.PageZoom.js";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
{
pageZoom = reader.ReadToEnd();
}
}
return pageZoom;
}
}
}

View file

@ -0,0 +1,16 @@
/*
* 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/.
*/
function disableMouseWheelZoom(e) {
if (e.ctrlKey) {
e.preventDefault();
e.stopPropagation();
}
}
document.addEventListener('wheel', disableMouseWheelZoom, { passive: false });

View file

@ -138,6 +138,11 @@ namespace SafeExamBrowser.Browser.Handlers
filePath = Path.Combine(KnownFolders.Downloads.ExpandedPath, downloadItem.SuggestedFileName);
}
if (File.Exists(filePath))
{
filePath = AppendIndexSuffixTo(filePath);
}
if (showDialog)
{
logger.Debug($"Allowing user to select custom download location, with '{filePath}' as suggestion.");
@ -155,6 +160,26 @@ namespace SafeExamBrowser.Browser.Handlers
}
}
private string AppendIndexSuffixTo(string filePath)
{
var directory = Path.GetDirectoryName(filePath);
var extension = Path.GetExtension(filePath);
var name = Path.GetFileNameWithoutExtension(filePath);
var path = default(string);
for (var suffix = 1; suffix < int.MaxValue; suffix++)
{
path = Path.Combine(directory, $"{name}({suffix}){extension}");
if (!File.Exists(path))
{
break;
}
}
return path;
}
private void RequestConfigurationFileDownload(DownloadItem downloadItem, IBeforeDownloadCallback callback)
{
var args = new DownloadEventArgs { Url = downloadItem.Url };

View file

@ -40,9 +40,15 @@ namespace SafeExamBrowser.Browser.Handlers
var configurationKey = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, frame.Url);
var api = contentLoader.LoadApi(browserExamKey, configurationKey, appConfig.ProgramBuildVersion);
var clipboardScript = contentLoader.LoadClipboard();
var pageZoomScript = contentLoader.LoadPageZoom();
frame.ExecuteJavaScriptAsync(api);
if (!settings.AllowPageZoom)
{
frame.ExecuteJavaScriptAsync(pageZoomScript);
}
if (!settings.AllowPrint)
{
frame.ExecuteJavaScriptAsync($"window.print = function() {{ alert('{text.Get(TextKey.Browser_PrintNotAllowed)}') }}");

View file

@ -396,6 +396,7 @@ namespace SafeExamBrowser.Browser.Handlers
var endpointUrl = default(string);
var start = session.IndexOf("=") + 1;
var end = session.IndexOf(";");
var name = session.Substring(0, start - 1);
var value = session.Substring(start, end - start);
var uri = new Uri(requestUrl);
@ -413,7 +414,7 @@ namespace SafeExamBrowser.Browser.Handlers
using (var handler = new HttpClientHandler { UseCookies = false })
using (var client = new HttpClient(handler))
{
message.Headers.Add("Cookie", $"MoodleSession={value}");
message.Headers.Add("Cookie", $"{name}={value}");
var result = await client.SendAsync(message);

View file

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

View file

@ -14,6 +14,7 @@ using Moq;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Client.Contracts;
using SafeExamBrowser.Client.Operations.Events;
using SafeExamBrowser.Communication.Contracts.Data;
using SafeExamBrowser.Communication.Contracts.Events;
@ -33,7 +34,8 @@ using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Monitoring;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.Settings.UserInterface;
using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
@ -54,6 +56,7 @@ namespace SafeExamBrowser.Client.UnitTests
private Mock<IBrowserApplication> browser;
private Mock<IClientHost> clientHost;
private ClientContext context;
private Mock<ICoordinator> coordinator;
private Mock<IDisplayMonitor> displayMonitor;
private Mock<IExplorerShell> explorerShell;
private Mock<IFileSystemDialog> fileSystemDialog;
@ -61,15 +64,15 @@ namespace SafeExamBrowser.Client.UnitTests
private Mock<IIntegrityModule> integrityModule;
private Mock<ILogger> logger;
private Mock<IMessageBox> messageBox;
private Mock<INetworkAdapter> networkAdapter;
private Mock<IOperationSequence> operationSequence;
private Mock<IRegistry> registry;
private Mock<IRuntimeProxy> runtimeProxy;
private Mock<ISystemSentinel> sentinel;
private Mock<IServerProxy> server;
private Guid sessionId;
private AppSettings settings;
private Mock<Action> shutdown;
private Mock<ISplashScreen> splashScreen;
private Mock<ISystemMonitor> systemMonitor;
private Mock<ITaskbar> taskbar;
private Mock<IText> text;
private Mock<IUserInterfaceFactory> uiFactory;
@ -87,6 +90,7 @@ namespace SafeExamBrowser.Client.UnitTests
browser = new Mock<IBrowserApplication>();
clientHost = new Mock<IClientHost>();
context = new ClientContext();
coordinator = new Mock<ICoordinator>();
displayMonitor = new Mock<IDisplayMonitor>();
explorerShell = new Mock<IExplorerShell>();
fileSystemDialog = new Mock<IFileSystemDialog>();
@ -94,15 +98,15 @@ namespace SafeExamBrowser.Client.UnitTests
integrityModule = new Mock<IIntegrityModule>();
logger = new Mock<ILogger>();
messageBox = new Mock<IMessageBox>();
networkAdapter = new Mock<INetworkAdapter>();
operationSequence = new Mock<IOperationSequence>();
registry = new Mock<IRegistry>();
runtimeProxy = new Mock<IRuntimeProxy>();
sentinel = new Mock<ISystemSentinel>();
server = new Mock<IServerProxy>();
sessionId = Guid.NewGuid();
settings = new AppSettings();
shutdown = new Mock<Action>();
splashScreen = new Mock<ISplashScreen>();
systemMonitor = new Mock<ISystemMonitor>();
taskbar = new Mock<ITaskbar>();
text = new Mock<IText>();
uiFactory = new Mock<IUserInterfaceFactory>();
@ -116,18 +120,19 @@ namespace SafeExamBrowser.Client.UnitTests
actionCenter.Object,
applicationMonitor.Object,
context,
coordinator.Object,
displayMonitor.Object,
explorerShell.Object,
fileSystemDialog.Object,
hashAlgorithm.Object,
logger.Object,
messageBox.Object,
networkAdapter.Object,
operationSequence.Object,
registry.Object,
runtimeProxy.Object,
shutdown.Object,
splashScreen.Object,
systemMonitor.Object,
sentinel.Object,
taskbar.Object,
text.Object,
uiFactory.Object);
@ -151,7 +156,7 @@ namespace SafeExamBrowser.Client.UnitTests
var shell = 0;
var workingArea = 0;
settings.Taskbar.EnableTaskbar = true;
settings.UserInterface.Taskbar.EnableTaskbar = true;
actionCenter.Setup(a => a.InitializeBounds()).Callback(() => boundsActionCenter = ++order);
explorerShell.Setup(e => e.Terminate()).Callback(() => shell = ++order);
@ -185,7 +190,7 @@ namespace SafeExamBrowser.Client.UnitTests
var shell = 0;
var workingArea = 0;
settings.Taskbar.EnableTaskbar = false;
settings.UserInterface.Taskbar.EnableTaskbar = false;
actionCenter.Setup(a => a.InitializeBounds()).Callback(() => boundsActionCenter = ++order);
explorerShell.Setup(e => e.Terminate()).Callback(() => shell = ++order);
@ -218,9 +223,9 @@ namespace SafeExamBrowser.Client.UnitTests
lockScreen.Setup(l => l.WaitForResult()).Returns(result);
runtimeProxy.Setup(p => p.RequestShutdown()).Returns(new CommunicationResult(true));
uiFactory
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>()))
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
.Returns(lockScreen.Object)
.Callback<string, string, IEnumerable<LockScreenOption>>((m, t, o) => result.OptionId = o.First().Id);
.Callback<string, string, IEnumerable<LockScreenOption>, LockScreenSettings>((m, t, o, s) => result.OptionId = o.First().Id);
sut.TryStart();
applicationMonitor.Raise(m => m.TerminationFailed += null, new List<RunningApplication>());
@ -237,9 +242,9 @@ namespace SafeExamBrowser.Client.UnitTests
lockScreen.Setup(l => l.WaitForResult()).Returns(result);
runtimeProxy.Setup(p => p.RequestShutdown()).Returns(new CommunicationResult(true));
uiFactory
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>()))
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
.Returns(lockScreen.Object)
.Callback<string, string, IEnumerable<LockScreenOption>>((m, t, o) => result.OptionId = o.Last().Id);
.Callback<string, string, IEnumerable<LockScreenOption>, LockScreenSettings>((m, t, o, s) => result.OptionId = o.Last().Id);
sut.TryStart();
applicationMonitor.Raise(m => m.TerminationFailed += null, new List<RunningApplication>());
@ -271,7 +276,7 @@ namespace SafeExamBrowser.Client.UnitTests
lockScreen.Setup(l => l.WaitForResult()).Callback(() => wait = ++order).Returns(result);
lockScreen.Setup(l => l.Close()).Callback(() => close = ++order);
uiFactory
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>()))
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
.Returns(lockScreen.Object);
sut.TryStart();
@ -308,7 +313,7 @@ namespace SafeExamBrowser.Client.UnitTests
hashAlgorithm.Setup(a => a.GenerateHashFor(It.Is<string>(p => p == result.Password))).Returns(hash);
lockScreen.Setup(l => l.WaitForResult()).Returns(lockScreenResult);
uiFactory
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>()))
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
.Returns(lockScreen.Object);
sut.TryStart();
@ -497,7 +502,7 @@ namespace SafeExamBrowser.Client.UnitTests
var order = 0;
var workingArea = 0;
settings.Taskbar.EnableTaskbar = true;
settings.UserInterface.Taskbar.EnableTaskbar = true;
actionCenter.Setup(t => t.InitializeBounds()).Callback(() => boundsActionCenter = ++order);
displayMonitor.Setup(m => m.InitializePrimaryDisplay(It.Is<int>(h => h == height))).Callback(() => workingArea = ++order);
@ -528,7 +533,7 @@ namespace SafeExamBrowser.Client.UnitTests
var order = 0;
var workingArea = 0;
settings.Taskbar.EnableTaskbar = false;
settings.UserInterface.Taskbar.EnableTaskbar = false;
actionCenter.Setup(t => t.InitializeBounds()).Callback(() => boundsActionCenter = ++order);
displayMonitor.Setup(w => w.InitializePrimaryDisplay(It.Is<int>(h => h == 0))).Callback(() => workingArea = ++order);
@ -557,7 +562,9 @@ namespace SafeExamBrowser.Client.UnitTests
displayMonitor.Setup(m => m.ValidateConfiguration(It.IsAny<DisplaySettings>())).Returns(new ValidationResult { IsAllowed = false });
lockScreen.Setup(l => l.WaitForResult()).Returns(new LockScreenResult());
uiFactory.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>())).Returns(lockScreen.Object);
uiFactory
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
.Returns(lockScreen.Object);
sut.TryStart();
displayMonitor.Raise(d => d.DisplayChanged += null);
@ -722,13 +729,17 @@ namespace SafeExamBrowser.Client.UnitTests
var args = new DownloadEventArgs();
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
runtimeProxy.Setup(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>())).Returns(new CommunicationResult(true));
sut.TryStart();
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
args.Callback(true, string.Empty);
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
Assert.IsTrue(args.AllowDownload);
}
@ -745,7 +756,30 @@ namespace SafeExamBrowser.Client.UnitTests
sut.TryStart();
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Never);
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
Assert.IsFalse(args.AllowDownload);
}
[TestMethod]
public void Reconfiguration_MustNotAllowConcurrentExecution()
{
var args = new DownloadEventArgs();
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(false);
runtimeProxy.Setup(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>())).Returns(new CommunicationResult(true));
sut.TryStart();
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
args.Callback?.Invoke(true, string.Empty);
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
Assert.IsFalse(args.AllowDownload);
}
@ -755,6 +789,7 @@ namespace SafeExamBrowser.Client.UnitTests
var args = new DownloadEventArgs { Url = "sebs://www.somehost.org/some/path/some_configuration.seb?query=123" };
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
settings.Security.AllowReconfiguration = true;
settings.Security.QuitPasswordHash = "abc123";
settings.Security.ReconfigurationUrl = "sebs://www.somehost.org/some/path/*.seb?query=123";
@ -764,7 +799,10 @@ namespace SafeExamBrowser.Client.UnitTests
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
args.Callback(true, string.Empty);
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
Assert.IsTrue(args.AllowDownload);
}
@ -779,7 +817,10 @@ namespace SafeExamBrowser.Client.UnitTests
sut.TryStart();
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Never);
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
Assert.IsFalse(args.AllowDownload);
}
@ -795,7 +836,10 @@ namespace SafeExamBrowser.Client.UnitTests
sut.TryStart();
browser.Raise(b => b.ConfigurationDownloadRequested += null, "filepath.seb", args);
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Never);
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
Assert.IsFalse(args.AllowDownload);
}
@ -808,7 +852,7 @@ namespace SafeExamBrowser.Client.UnitTests
var args = new DownloadEventArgs();
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
settings.Security.AllowReconfiguration = true;
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
messageBox.Setup(m => m.Show(
It.IsAny<TextKey>(),
It.IsAny<TextKey>(),
@ -818,11 +862,14 @@ namespace SafeExamBrowser.Client.UnitTests
runtimeProxy.Setup(r => r.RequestReconfiguration(
It.Is<string>(p => p == downloadPath),
It.Is<string>(u => u == downloadUrl))).Returns(new CommunicationResult(true));
settings.Security.AllowReconfiguration = true;
sut.TryStart();
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
args.Callback(true, downloadUrl, downloadPath);
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Never);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.Is<string>(p => p == downloadPath), It.Is<string>(u => u == downloadUrl)), Times.Once);
Assert.AreEqual(downloadPath, args.DownloadPath);
@ -838,7 +885,7 @@ namespace SafeExamBrowser.Client.UnitTests
var args = new DownloadEventArgs();
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
settings.Security.AllowReconfiguration = true;
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
messageBox.Setup(m => m.Show(
It.IsAny<TextKey>(),
It.IsAny<TextKey>(),
@ -848,11 +895,14 @@ namespace SafeExamBrowser.Client.UnitTests
runtimeProxy.Setup(r => r.RequestReconfiguration(
It.Is<string>(p => p == downloadPath),
It.Is<string>(u => u == downloadUrl))).Returns(new CommunicationResult(true));
settings.Security.AllowReconfiguration = true;
sut.TryStart();
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
args.Callback(false, downloadPath);
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Once);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
}
@ -865,7 +915,7 @@ namespace SafeExamBrowser.Client.UnitTests
var args = new DownloadEventArgs();
appConfig.TemporaryDirectory = @"C:\Folder\Does\Not\Exist";
settings.Security.AllowReconfiguration = true;
coordinator.Setup(c => c.RequestReconfigurationLock()).Returns(true);
messageBox.Setup(m => m.Show(
It.IsAny<TextKey>(),
It.IsAny<TextKey>(),
@ -875,18 +925,21 @@ namespace SafeExamBrowser.Client.UnitTests
runtimeProxy.Setup(r => r.RequestReconfiguration(
It.Is<string>(p => p == downloadPath),
It.Is<string>(u => u == downloadUrl))).Returns(new CommunicationResult(false));
settings.Security.AllowReconfiguration = true;
sut.TryStart();
browser.Raise(b => b.ConfigurationDownloadRequested += null, filename, args);
args.Callback(true, downloadUrl, downloadPath);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
coordinator.Verify(c => c.RequestReconfigurationLock(), Times.Once);
coordinator.Verify(c => c.ReleaseReconfigurationLock(), Times.Once);
messageBox.Verify(m => m.Show(
It.IsAny<TextKey>(),
It.IsAny<TextKey>(),
It.IsAny<MessageBoxAction>(),
It.Is<MessageBoxIcon>(i => i == MessageBoxIcon.Error),
It.IsAny<IWindow>()), Times.Once);
runtimeProxy.Verify(r => r.RequestReconfiguration(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
[TestMethod]
@ -942,8 +995,8 @@ namespace SafeExamBrowser.Client.UnitTests
[TestMethod]
public void Shutdown_MustCloseActionCenterAndTaskbarIfEnabled()
{
settings.ActionCenter.EnableActionCenter = true;
settings.Taskbar.EnableTaskbar = true;
settings.UserInterface.ActionCenter.EnableActionCenter = true;
settings.UserInterface.Taskbar.EnableTaskbar = true;
sut.Terminate();
@ -954,8 +1007,8 @@ namespace SafeExamBrowser.Client.UnitTests
[TestMethod]
public void Shutdown_MustNotCloseActionCenterAndTaskbarIfNotEnabled()
{
settings.ActionCenter.EnableActionCenter = false;
settings.Taskbar.EnableTaskbar = false;
settings.UserInterface.ActionCenter.EnableActionCenter = false;
settings.UserInterface.Taskbar.EnableTaskbar = false;
sut.Terminate();
@ -1126,7 +1179,7 @@ namespace SafeExamBrowser.Client.UnitTests
[TestMethod]
public void Startup_MustCorrectlyShowTaskbar()
{
settings.Taskbar.EnableTaskbar = true;
settings.UserInterface.Taskbar.EnableTaskbar = true;
sut.TryStart();
taskbar.Verify(t => t.Show(), Times.Once);
@ -1138,7 +1191,7 @@ namespace SafeExamBrowser.Client.UnitTests
taskbar.Verify(t => t.Show(), Times.Never);
taskbar.Reset();
settings.Taskbar.EnableTaskbar = false;
settings.UserInterface.Taskbar.EnableTaskbar = false;
operationSequence.Setup(o => o.TryPerform()).Returns(OperationResult.Success);
sut.TryStart();
@ -1148,7 +1201,7 @@ namespace SafeExamBrowser.Client.UnitTests
[TestMethod]
public void Startup_MustCorrectlyShowActionCenter()
{
settings.ActionCenter.EnableActionCenter = true;
settings.UserInterface.ActionCenter.EnableActionCenter = true;
sut.TryStart();
actionCenter.Verify(t => t.Promote(), Times.Once);
@ -1162,7 +1215,7 @@ namespace SafeExamBrowser.Client.UnitTests
actionCenter.Verify(t => t.Show(), Times.Never);
actionCenter.Reset();
settings.ActionCenter.EnableActionCenter = false;
settings.UserInterface.ActionCenter.EnableActionCenter = false;
operationSequence.Setup(o => o.TryPerform()).Returns(OperationResult.Success);
sut.TryStart();
@ -1224,13 +1277,18 @@ namespace SafeExamBrowser.Client.UnitTests
{
var lockScreen = new Mock<ILockScreen>();
settings.Service.IgnoreService = true;
coordinator.Setup(c => c.RequestSessionLock()).Returns(true);
lockScreen.Setup(l => l.WaitForResult()).Returns(new LockScreenResult());
uiFactory.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>())).Returns(lockScreen.Object);
settings.Service.IgnoreService = true;
uiFactory
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
.Returns(lockScreen.Object);
sut.TryStart();
systemMonitor.Raise(m => m.SessionChanged += null);
sentinel.Raise(s => s.SessionChanged += null);
coordinator.Verify(c => c.RequestSessionLock(), Times.Once);
coordinator.Verify(c => c.ReleaseSessionLock(), Times.Once);
lockScreen.Verify(l => l.Show(), Times.Once);
}
@ -1240,17 +1298,20 @@ namespace SafeExamBrowser.Client.UnitTests
var lockScreen = new Mock<ILockScreen>();
var result = new LockScreenResult();
settings.Service.IgnoreService = true;
coordinator.Setup(c => c.RequestSessionLock()).Returns(true);
lockScreen.Setup(l => l.WaitForResult()).Returns(result);
runtimeProxy.Setup(r => r.RequestShutdown()).Returns(new CommunicationResult(true));
settings.Service.IgnoreService = true;
uiFactory
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>()))
.Callback(new Action<string, string, IEnumerable<LockScreenOption>>((message, title, options) => result.OptionId = options.Last().Id))
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
.Callback(new Action<string, string, IEnumerable<LockScreenOption>, LockScreenSettings>((message, title, options, settings) => result.OptionId = options.Last().Id))
.Returns(lockScreen.Object);
sut.TryStart();
systemMonitor.Raise(m => m.SessionChanged += null);
sentinel.Raise(s => s.SessionChanged += null);
coordinator.Verify(c => c.RequestSessionLock(), Times.Once);
coordinator.Verify(c => c.ReleaseSessionLock(), Times.Once);
lockScreen.Verify(l => l.Show(), Times.Once);
runtimeProxy.Verify(p => p.RequestShutdown(), Times.Once);
}
@ -1264,10 +1325,12 @@ namespace SafeExamBrowser.Client.UnitTests
settings.Service.DisableUserLock = false;
settings.Service.DisableUserSwitch = false;
lockScreen.Setup(l => l.WaitForResult()).Returns(new LockScreenResult());
uiFactory.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>())).Returns(lockScreen.Object);
uiFactory
.Setup(f => f.CreateLockScreen(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<IEnumerable<LockScreenOption>>(), It.IsAny<LockScreenSettings>()))
.Returns(lockScreen.Object);
sut.TryStart();
systemMonitor.Raise(m => m.SessionChanged += null);
sentinel.Raise(s => s.SessionChanged += null);
lockScreen.Verify(l => l.Show(), Times.Never);
}

View file

@ -0,0 +1,118 @@
/*
* 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 System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SafeExamBrowser.Client.UnitTests
{
[TestClass]
public class CoordinatorTests
{
private Coordinator sut;
[TestInitialize]
public void Initialize()
{
sut = new Coordinator();
}
[TestMethod]
public void ReconfigurationLock_MustWorkCorrectly()
{
Assert.IsFalse(sut.IsReconfigurationLocked());
sut.RequestReconfigurationLock();
var result = Parallel.For(1, 1000, (_) =>
{
Assert.IsTrue(sut.IsReconfigurationLocked());
Assert.IsFalse(sut.RequestReconfigurationLock());
});
Assert.IsTrue(result.IsCompleted);
result = Parallel.For(1, 1000, (_) =>
{
sut.ReleaseReconfigurationLock();
});
Assert.IsFalse(sut.IsReconfigurationLocked());
Assert.IsTrue(result.IsCompleted);
}
[TestMethod]
public void RequestReconfigurationLock_MustOnlyAllowLockingOnce()
{
var count = 0;
Assert.IsFalse(sut.IsReconfigurationLocked());
var result = Parallel.For(1, 1000, (_) =>
{
var acquired = sut.RequestReconfigurationLock();
if (acquired)
{
Interlocked.Increment(ref count);
}
});
Assert.AreEqual(1, count);
Assert.IsTrue(sut.IsReconfigurationLocked());
Assert.IsTrue(result.IsCompleted);
}
[TestMethod]
public void RequestSessionLock_MustOnlyAllowLockingOnce()
{
var count = 0;
Assert.IsFalse(sut.IsSessionLocked());
var result = Parallel.For(1, 1000, (_) =>
{
var acquired = sut.RequestSessionLock();
if (acquired)
{
Interlocked.Increment(ref count);
}
});
Assert.AreEqual(1, count);
Assert.IsTrue(sut.IsSessionLocked());
Assert.IsTrue(result.IsCompleted);
}
[TestMethod]
public void SessionLock_MustWorkCorrectly()
{
Assert.IsFalse(sut.IsSessionLocked());
sut.RequestSessionLock();
var result = Parallel.For(1, 1000, (_) =>
{
Assert.IsTrue(sut.IsSessionLocked());
Assert.IsFalse(sut.RequestSessionLock());
});
Assert.IsTrue(result.IsCompleted);
result = Parallel.For(1, 1000, (_) =>
{
sut.ReleaseSessionLock();
});
Assert.IsFalse(sut.IsSessionLocked());
Assert.IsTrue(result.IsCompleted);
}
}
}

View file

@ -64,9 +64,9 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod]
public void Perform_MustNotInitializeBrowserIfNotEnabled()
{
settings.ActionCenter.EnableActionCenter = true;
settings.Browser.EnableBrowser = false;
settings.Taskbar.EnableTaskbar = true;
settings.UserInterface.ActionCenter.EnableActionCenter = true;
settings.UserInterface.Taskbar.EnableTaskbar = true;
sut.Perform();
@ -79,17 +79,17 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod]
public void Perform_MustCorrectlyInitializeControls()
{
settings.ActionCenter.EnableActionCenter = false;
settings.Browser.EnableBrowser = true;
settings.Taskbar.EnableTaskbar = false;
settings.UserInterface.ActionCenter.EnableActionCenter = false;
settings.UserInterface.Taskbar.EnableTaskbar = false;
sut.Perform();
actionCenter.Verify(a => a.AddApplicationControl(It.IsAny<IApplicationControl>(), true), Times.Never);
taskbar.Verify(t => t.AddApplicationControl(It.IsAny<IApplicationControl>(), true), Times.Never);
settings.ActionCenter.EnableActionCenter = true;
settings.Taskbar.EnableTaskbar = true;
settings.UserInterface.ActionCenter.EnableActionCenter = true;
settings.UserInterface.Taskbar.EnableTaskbar = true;
sut.Perform();

View file

@ -43,7 +43,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
var order = 0;
context.Settings = new AppSettings();
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
displayMonitor.Setup(d => d.InitializePrimaryDisplay(It.IsAny<int>())).Callback(() => Assert.AreEqual(++order, 1));
displayMonitor.Setup(d => d.StartMonitoringDisplayChanges()).Callback(() => Assert.AreEqual(++order, 2));
@ -60,7 +60,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
var height = 25;
context.Settings = new AppSettings();
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
taskbar.Setup(t => t.GetAbsoluteHeight()).Returns(height);
sut.Perform();
@ -75,7 +75,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
var height = 25;
context.Settings = new AppSettings();
context.Settings.Taskbar.EnableTaskbar = false;
context.Settings.UserInterface.Taskbar.EnableTaskbar = false;
taskbar.Setup(t => t.GetAbsoluteHeight()).Returns(height);
sut.Perform();

View file

@ -100,9 +100,9 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
context.Activators.Add(actionCenterActivator.Object);
context.Activators.Add(taskviewActivator.Object);
context.Activators.Add(terminationActivator.Object);
context.Settings.ActionCenter.EnableActionCenter = true;
context.Settings.Keyboard.AllowAltTab = true;
context.Settings.Security.AllowTermination = true;
context.Settings.UserInterface.ActionCenter.EnableActionCenter = true;
sut.Perform();
@ -130,11 +130,11 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
context.Applications.Add(application1.Object);
context.Applications.Add(application2.Object);
context.Applications.Add(application3.Object);
context.Settings.ActionCenter.EnableActionCenter = true;
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.Applications.Whitelist.Add(application1Settings);
context.Settings.Applications.Whitelist.Add(application2Settings);
context.Settings.Applications.Whitelist.Add(application3Settings);
context.Settings.UserInterface.ActionCenter.EnableActionCenter = true;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
sut.Perform();
@ -168,11 +168,11 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
context.Applications.Add(application1.Object);
context.Applications.Add(application2.Object);
context.Applications.Add(application3.Object);
context.Settings.ActionCenter.EnableActionCenter = false;
context.Settings.Taskbar.EnableTaskbar = false;
context.Settings.Applications.Whitelist.Add(application1Settings);
context.Settings.Applications.Whitelist.Add(application2Settings);
context.Settings.Applications.Whitelist.Add(application3Settings);
context.Settings.UserInterface.ActionCenter.EnableActionCenter = false;
context.Settings.UserInterface.Taskbar.EnableTaskbar = false;
sut.Perform();
@ -211,10 +211,10 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod]
public void Perform_MustInitializeClock()
{
context.Settings.ActionCenter.EnableActionCenter = true;
context.Settings.ActionCenter.ShowClock = true;
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.Taskbar.ShowClock = true;
context.Settings.UserInterface.ActionCenter.EnableActionCenter = true;
context.Settings.UserInterface.ActionCenter.ShowClock = true;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
context.Settings.UserInterface.Taskbar.ShowClock = true;
sut.Perform();
@ -225,10 +225,10 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod]
public void Perform_MustNotInitializeClock()
{
context.Settings.ActionCenter.EnableActionCenter = true;
context.Settings.ActionCenter.ShowClock = false;
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.Taskbar.ShowClock = false;
context.Settings.UserInterface.ActionCenter.EnableActionCenter = true;
context.Settings.UserInterface.ActionCenter.ShowClock = false;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
context.Settings.UserInterface.Taskbar.ShowClock = false;
sut.Perform();
@ -239,9 +239,9 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod]
public void Perform_MustInitializeQuitButton()
{
context.Settings.ActionCenter.EnableActionCenter = true;
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.Security.AllowTermination = false;
context.Settings.UserInterface.ActionCenter.EnableActionCenter = true;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
sut.Perform();
@ -265,12 +265,12 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod]
public void Perform_MustInitializeNotifications()
{
context.Settings.ActionCenter.EnableActionCenter = true;
context.Settings.ActionCenter.ShowApplicationInfo = true;
context.Settings.ActionCenter.ShowApplicationLog = true;
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.Taskbar.ShowApplicationInfo = true;
context.Settings.Taskbar.ShowApplicationLog = true;
context.Settings.UserInterface.ActionCenter.EnableActionCenter = true;
context.Settings.UserInterface.ActionCenter.ShowApplicationInfo = true;
context.Settings.UserInterface.ActionCenter.ShowApplicationLog = true;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
context.Settings.UserInterface.Taskbar.ShowApplicationInfo = true;
context.Settings.UserInterface.Taskbar.ShowApplicationLog = true;
sut.Perform();
@ -283,10 +283,10 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
{
var logControl = new Mock<INotificationControl>();
context.Settings.ActionCenter.EnableActionCenter = true;
context.Settings.ActionCenter.ShowApplicationLog = false;
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.Taskbar.ShowApplicationLog = false;
context.Settings.UserInterface.ActionCenter.EnableActionCenter = true;
context.Settings.UserInterface.ActionCenter.ShowApplicationLog = false;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
context.Settings.UserInterface.Taskbar.ShowApplicationLog = false;
uiFactory
.Setup(f => f.CreateNotificationControl(It.IsAny<INotification>(), It.IsAny<Location>()))
@ -301,14 +301,14 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod]
public void Perform_MustInitializeSystemComponents()
{
context.Settings.ActionCenter.EnableActionCenter = true;
context.Settings.ActionCenter.ShowAudio = true;
context.Settings.ActionCenter.ShowKeyboardLayout = true;
context.Settings.ActionCenter.ShowNetwork = true;
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.Taskbar.ShowAudio = true;
context.Settings.Taskbar.ShowKeyboardLayout = true;
context.Settings.Taskbar.ShowNetwork = true;
context.Settings.UserInterface.ActionCenter.EnableActionCenter = true;
context.Settings.UserInterface.ActionCenter.ShowAudio = true;
context.Settings.UserInterface.ActionCenter.ShowKeyboardLayout = true;
context.Settings.UserInterface.ActionCenter.ShowNetwork = true;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
context.Settings.UserInterface.Taskbar.ShowAudio = true;
context.Settings.UserInterface.Taskbar.ShowKeyboardLayout = true;
context.Settings.UserInterface.Taskbar.ShowNetwork = true;
systemInfo.SetupGet(s => s.HasBattery).Returns(true);
uiFactory.Setup(f => f.CreateAudioControl(It.IsAny<IAudio>(), It.IsAny<Location>())).Returns(new Mock<ISystemControl>().Object);
@ -329,14 +329,14 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod]
public void Perform_MustNotInitializeSystemComponents()
{
context.Settings.ActionCenter.EnableActionCenter = true;
context.Settings.ActionCenter.ShowAudio = false;
context.Settings.ActionCenter.ShowKeyboardLayout = false;
context.Settings.ActionCenter.ShowNetwork = false;
context.Settings.Taskbar.EnableTaskbar = true;
context.Settings.Taskbar.ShowAudio = false;
context.Settings.Taskbar.ShowKeyboardLayout = false;
context.Settings.Taskbar.ShowNetwork = false;
context.Settings.UserInterface.ActionCenter.EnableActionCenter = true;
context.Settings.UserInterface.ActionCenter.ShowAudio = false;
context.Settings.UserInterface.ActionCenter.ShowKeyboardLayout = false;
context.Settings.UserInterface.ActionCenter.ShowNetwork = false;
context.Settings.UserInterface.Taskbar.EnableTaskbar = true;
context.Settings.UserInterface.Taskbar.ShowAudio = false;
context.Settings.UserInterface.Taskbar.ShowKeyboardLayout = false;
context.Settings.UserInterface.Taskbar.ShowNetwork = false;
systemInfo.SetupGet(s => s.HasBattery).Returns(false);
uiFactory.Setup(f => f.CreateAudioControl(It.IsAny<IAudio>(), It.IsAny<Location>())).Returns(new Mock<ISystemControl>().Object);
@ -360,7 +360,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
var actionCenterActivator = new Mock<IActionCenterActivator>();
context.Activators.Add(actionCenterActivator.Object);
context.Settings.ActionCenter.EnableActionCenter = false;
context.Settings.UserInterface.ActionCenter.EnableActionCenter = false;
sut.Perform();
@ -385,7 +385,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
[TestMethod]
public void Perform_MustNotInitializeTaskbarIfNotEnabled()
{
context.Settings.Taskbar.EnableTaskbar = false;
context.Settings.UserInterface.Taskbar.EnableTaskbar = false;
sut.Perform();
taskbar.VerifyNoOtherCalls();
}

View file

@ -1,53 +0,0 @@
/*
* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Client.Operations;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System;
namespace SafeExamBrowser.Client.UnitTests.Operations
{
[TestClass]
public class SystemMonitorOperationTests
{
private ClientContext context;
private Mock<ISystemMonitor> systemMonitor;
private Mock<ILogger> logger;
private SystemMonitorOperation sut;
[TestInitialize]
public void Initialize()
{
context = new ClientContext();
systemMonitor = new Mock<ISystemMonitor>();
logger = new Mock<ILogger>();
sut = new SystemMonitorOperation(context, systemMonitor.Object, logger.Object);
}
[TestMethod]
public void Perform_MustStartMonitor()
{
sut.Perform();
systemMonitor.Verify(s => s.Start(), Times.Once);
systemMonitor.VerifyNoOtherCalls();
}
[TestMethod]
public void Revert_MustStopMonitor()
{
sut.Revert();
systemMonitor.Verify(s => s.Stop(), Times.Once);
systemMonitor.VerifyNoOtherCalls();
}
}
}

View file

@ -163,9 +163,9 @@
<Compile Include="Communication\ClientHostTests.cs" />
<Compile Include="Notifications\AboutNotificationControllerTests.cs" />
<Compile Include="Notifications\LogNotificationControllerTests.cs" />
<Compile Include="Operations\SystemMonitorOperationTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ClientControllerTests.cs" />
<Compile Include="CoordinatorTests.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config">

View file

@ -20,7 +20,7 @@ namespace SafeExamBrowser.Client
private const int ILMCM_LANGUAGEBAROFF = 0x00002;
private static readonly Mutex Mutex = new Mutex(true, AppConfig.CLIENT_MUTEX_NAME);
private CompositionRoot instances = new CompositionRoot();
private readonly CompositionRoot instances = new CompositionRoot();
[STAThread]
public static void Main()

View file

@ -16,6 +16,7 @@ using System.Threading.Tasks;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Client.Contracts;
using SafeExamBrowser.Client.Operations.Events;
using SafeExamBrowser.Communication.Contracts.Data;
using SafeExamBrowser.Communication.Contracts.Events;
@ -30,12 +31,14 @@ using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Monitoring.Contracts.Display;
using SafeExamBrowser.Monitoring.Contracts.System;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Proctoring.Contracts.Events;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Data;
using SafeExamBrowser.Settings;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.Network.Events;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
@ -51,18 +54,19 @@ namespace SafeExamBrowser.Client
private readonly IActionCenter actionCenter;
private readonly IApplicationMonitor applicationMonitor;
private readonly ClientContext context;
private readonly ICoordinator coordinator;
private readonly IDisplayMonitor displayMonitor;
private readonly IExplorerShell explorerShell;
private readonly IFileSystemDialog fileSystemDialog;
private readonly IHashAlgorithm hashAlgorithm;
private readonly ILogger logger;
private readonly IMessageBox messageBox;
private readonly INetworkAdapter networkAdapter;
private readonly IOperationSequence operations;
private readonly IRegistry registry;
private readonly IRuntimeProxy runtime;
private readonly Action shutdown;
private readonly ISplashScreen splashScreen;
private readonly ISystemMonitor systemMonitor;
private readonly ISystemSentinel sentinel;
private readonly ITaskbar taskbar;
private readonly IText text;
private readonly IUserInterfaceFactory uiFactory;
@ -75,24 +79,24 @@ namespace SafeExamBrowser.Client
private AppSettings Settings => context.Settings;
private ILockScreen lockScreen;
private bool sessionLocked;
internal ClientController(
IActionCenter actionCenter,
IApplicationMonitor applicationMonitor,
ClientContext context,
ICoordinator coordinator,
IDisplayMonitor displayMonitor,
IExplorerShell explorerShell,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
ILogger logger,
IMessageBox messageBox,
INetworkAdapter networkAdapter,
IOperationSequence operations,
IRegistry registry,
IRuntimeProxy runtime,
Action shutdown,
ISplashScreen splashScreen,
ISystemMonitor systemMonitor,
ISystemSentinel sentinel,
ITaskbar taskbar,
IText text,
IUserInterfaceFactory uiFactory)
@ -100,18 +104,19 @@ namespace SafeExamBrowser.Client
this.actionCenter = actionCenter;
this.applicationMonitor = applicationMonitor;
this.context = context;
this.coordinator = coordinator;
this.displayMonitor = displayMonitor;
this.explorerShell = explorerShell;
this.fileSystemDialog = fileSystemDialog;
this.hashAlgorithm = hashAlgorithm;
this.logger = logger;
this.messageBox = messageBox;
this.networkAdapter = networkAdapter;
this.operations = operations;
this.registry = registry;
this.runtime = runtime;
this.shutdown = shutdown;
this.splashScreen = splashScreen;
this.systemMonitor = systemMonitor;
this.sentinel = sentinel;
this.taskbar = taskbar;
this.text = text;
this.uiFactory = uiFactory;
@ -136,6 +141,7 @@ namespace SafeExamBrowser.Client
ShowShell();
AutoStartApplications();
ScheduleIntegrityVerification();
StartMonitoring();
var communication = runtime.InformClientReady();
@ -174,7 +180,6 @@ namespace SafeExamBrowser.Client
CloseShell();
DeregisterEvents();
UpdateSessionIntegrity();
TerminateIntegrityVerification();
var success = operations.TryRevert() == OperationResult.Success;
@ -214,9 +219,12 @@ namespace SafeExamBrowser.Client
ClientHost.ServerFailureActionRequested += ClientHost_ServerFailureActionRequested;
ClientHost.Shutdown += ClientHost_Shutdown;
displayMonitor.DisplayChanged += DisplayMonitor_DisplaySettingsChanged;
registry.ValueChanged += Registry_ValueChanged;
networkAdapter.CredentialsRequired += NetworkAdapter_CredentialsRequired;
runtime.ConnectionLost += Runtime_ConnectionLost;
systemMonitor.SessionChanged += SystemMonitor_SessionChanged;
sentinel.CursorChanged += Sentinel_CursorChanged;
sentinel.EaseOfAccessChanged += Sentinel_EaseOfAccessChanged;
sentinel.SessionChanged += Sentinel_SessionChanged;
sentinel.StickyKeysChanged += Sentinel_StickyKeysChanged;
taskbar.LoseFocusRequested += Taskbar_LoseFocusRequested;
taskbar.QuitButtonClicked += Shell_QuitButtonClicked;
@ -239,9 +247,11 @@ namespace SafeExamBrowser.Client
applicationMonitor.ExplorerStarted -= ApplicationMonitor_ExplorerStarted;
applicationMonitor.TerminationFailed -= ApplicationMonitor_TerminationFailed;
displayMonitor.DisplayChanged -= DisplayMonitor_DisplaySettingsChanged;
registry.ValueChanged -= Registry_ValueChanged;
runtime.ConnectionLost -= Runtime_ConnectionLost;
systemMonitor.SessionChanged -= SystemMonitor_SessionChanged;
sentinel.CursorChanged -= Sentinel_CursorChanged;
sentinel.EaseOfAccessChanged -= Sentinel_EaseOfAccessChanged;
sentinel.SessionChanged -= Sentinel_SessionChanged;
sentinel.StickyKeysChanged -= Sentinel_StickyKeysChanged;
taskbar.LoseFocusRequested -= Taskbar_LoseFocusRequested;
taskbar.QuitButtonClicked -= Shell_QuitButtonClicked;
@ -279,12 +289,12 @@ namespace SafeExamBrowser.Client
private void CloseShell()
{
if (Settings?.ActionCenter.EnableActionCenter == true)
if (Settings?.UserInterface.ActionCenter.EnableActionCenter == true)
{
actionCenter.Close();
}
if (Settings?.Taskbar.EnableTaskbar == true)
if (Settings?.UserInterface.Taskbar.EnableTaskbar == true)
{
taskbar.Close();
}
@ -292,12 +302,12 @@ namespace SafeExamBrowser.Client
private void ShowShell()
{
if (Settings.ActionCenter.EnableActionCenter)
if (Settings.UserInterface.ActionCenter.EnableActionCenter)
{
actionCenter.Promote();
}
if (Settings.Taskbar.EnableTaskbar)
if (Settings.UserInterface.Taskbar.EnableTaskbar)
{
taskbar.Show();
}
@ -324,6 +334,7 @@ namespace SafeExamBrowser.Client
private void PrepareShutdown()
{
FinalizeProctoring();
StopMonitoring();
}
private void FinalizeProctoring()
@ -355,28 +366,33 @@ namespace SafeExamBrowser.Client
timer.Elapsed += (o, args) => VerifyApplicationIntegrity();
timer.Interval = TEN_MINUTES + (new Random().NextDouble() * FIVE_MINUTES);
timer.Start();
}
private void StartMonitoring()
{
sentinel.StartMonitoringSystemEvents();
if (!Settings.Security.AllowStickyKeys)
{
sentinel.StartMonitoringStickyKeys();
}
if (Settings.Security.VerifyCursorConfiguration)
{
if (registry.TryGetNames(RegistryValue.UserHive.Cursors_Key, out var names))
{
foreach (var name in names)
{
registry.StartMonitoring(RegistryValue.UserHive.Cursors_Key, name);
}
}
else
{
logger.Warn("Failed to start monitoring cursor registry values!");
}
sentinel.StartMonitoringCursors();
}
if (Settings.Service.IgnoreService)
{
registry.StartMonitoring(RegistryValue.MachineHive.EaseOfAccess_Key, RegistryValue.MachineHive.EaseOfAccess_Name);
sentinel.StartMonitoringEaseOfAccess();
}
}
private void StopMonitoring()
{
sentinel.StopMonitoring();
}
private void VerifyApplicationIntegrity()
{
logger.Info($"Attempting to verify application integrity...");
@ -403,7 +419,7 @@ namespace SafeExamBrowser.Client
{
var hasQuitPassword = !string.IsNullOrEmpty(Settings.Security.QuitPasswordHash);
if (hasQuitPassword)
if (hasQuitPassword && Settings.Security.VerifySessionIntegrity)
{
logger.Info($"Attempting to verify session integrity...");
@ -440,17 +456,12 @@ namespace SafeExamBrowser.Client
}
}
private void TerminateIntegrityVerification()
{
registry.StopMonitoring();
}
private void ApplicationMonitor_ExplorerStarted()
{
logger.Info("Trying to terminate Windows explorer...");
explorerShell.Terminate();
logger.Info("Re-initializing working area...");
displayMonitor.InitializePrimaryDisplay(Settings.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0);
displayMonitor.InitializePrimaryDisplay(Settings.UserInterface.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0);
logger.Info("Re-initializing shell...");
actionCenter.InitializeBounds();
taskbar.InitializeBounds();
@ -481,6 +492,36 @@ namespace SafeExamBrowser.Client
}
private void Browser_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args)
{
args.AllowDownload = false;
if (IsAllowedToReconfigure(args.Url))
{
if (coordinator.RequestReconfigurationLock())
{
args.AllowDownload = true;
args.Callback = Browser_ConfigurationDownloadFinished;
args.DownloadPath = Path.Combine(context.AppConfig.TemporaryDirectory, fileName);
splashScreen.Show();
splashScreen.BringToForeground();
splashScreen.SetIndeterminate();
splashScreen.UpdateStatus(TextKey.OperationStatus_InitializeSession, true);
logger.Info($"Allowed download request for configuration file '{fileName}'.");
}
else
{
logger.Warn($"A reconfiguration is already in progress, denied download request for configuration file '{fileName}'!");
}
}
else
{
logger.Info($"Reconfiguration is not allowed, denied download request for configuration file '{fileName}'.");
}
}
private bool IsAllowedToReconfigure(string url)
{
var allow = false;
var hasQuitPassword = !string.IsNullOrWhiteSpace(Settings.Security.QuitPasswordHash);
@ -492,9 +533,9 @@ namespace SafeExamBrowser.Client
{
var expression = Regex.Escape(Settings.Security.ReconfigurationUrl).Replace(@"\*", ".*");
var regex = new Regex($"^{expression}$", RegexOptions.IgnoreCase);
var sebUrl = args.Url.Replace(Uri.UriSchemeHttps, context.AppConfig.SebUriSchemeSecure).Replace(Uri.UriSchemeHttp, context.AppConfig.SebUriScheme);
var sebUrl = url.Replace(Uri.UriSchemeHttps, context.AppConfig.SebUriSchemeSecure).Replace(Uri.UriSchemeHttp, context.AppConfig.SebUriScheme);
allow = Settings.Security.AllowReconfiguration && (regex.IsMatch(args.Url) || regex.IsMatch(sebUrl));
allow = Settings.Security.AllowReconfiguration && (regex.IsMatch(url) || regex.IsMatch(sebUrl));
}
else
{
@ -506,24 +547,7 @@ namespace SafeExamBrowser.Client
allow = Settings.ConfigurationMode == ConfigurationMode.ConfigureClient || Settings.Security.AllowReconfiguration;
}
if (allow)
{
args.AllowDownload = true;
args.Callback = Browser_ConfigurationDownloadFinished;
args.DownloadPath = Path.Combine(context.AppConfig.TemporaryDirectory, fileName);
splashScreen.Show();
splashScreen.BringToForeground();
splashScreen.SetIndeterminate();
splashScreen.UpdateStatus(TextKey.OperationStatus_InitializeSession, true);
logger.Info($"Allowed download request for configuration file '{fileName}'.");
}
else
{
args.AllowDownload = false;
logger.Info($"Denied download request for configuration file '{fileName}'.");
}
return allow;
}
private void Browser_ConfigurationDownloadFinished(bool success, string url, string filePath = null)
@ -541,15 +565,19 @@ namespace SafeExamBrowser.Client
else
{
logger.Error($"Failed to communicate reconfiguration request for '{filePath}'!");
messageBox.Show(TextKey.MessageBox_ReconfigurationError, TextKey.MessageBox_ReconfigurationErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen);
splashScreen.Hide();
coordinator.ReleaseReconfigurationLock();
}
}
else
{
logger.Error($"Failed to download configuration file '{filePath}'!");
messageBox.Show(TextKey.MessageBox_ConfigurationDownloadError, TextKey.MessageBox_ConfigurationDownloadErrorTitle, icon: MessageBoxIcon.Error, parent: splashScreen);
splashScreen.Hide();
coordinator.ReleaseReconfigurationLock();
}
}
@ -637,6 +665,7 @@ namespace SafeExamBrowser.Client
{
logger.Info("The reconfiguration was aborted by the runtime.");
splashScreen.Hide();
coordinator.ReleaseReconfigurationLock();
}
private void ClientHost_ReconfigurationDenied(ReconfigurationEventArgs args)
@ -644,6 +673,7 @@ namespace SafeExamBrowser.Client
logger.Info($"The reconfiguration request for '{args.ConfigurationPath}' was denied by the runtime!");
messageBox.Show(TextKey.MessageBox_ReconfigurationDenied, TextKey.MessageBox_ReconfigurationDeniedTitle, parent: splashScreen);
splashScreen.Hide();
coordinator.ReleaseReconfigurationLock();
}
private void ClientHost_ServerFailureActionRequested(ServerFailureActionRequestEventArgs args)
@ -665,7 +695,7 @@ namespace SafeExamBrowser.Client
private void DisplayMonitor_DisplaySettingsChanged()
{
logger.Info("Re-initializing working area...");
displayMonitor.InitializePrimaryDisplay(Settings.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0);
displayMonitor.InitializePrimaryDisplay(Settings.UserInterface.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0);
logger.Info("Re-initializing shell...");
actionCenter.InitializeBounds();
@ -690,6 +720,18 @@ namespace SafeExamBrowser.Client
}
}
private void NetworkAdapter_CredentialsRequired(CredentialsRequiredEventArgs args)
{
var message = text.Get(TextKey.CredentialsDialog_WirelessNetworkMessage).Replace("%%_NAME_%%", args.NetworkName);
var title = text.Get(TextKey.CredentialsDialog_WirelessNetworkTitle);
var dialog = uiFactory.CreateCredentialsDialog(CredentialsDialogPurpose.WirelessNetwork, message, title);
var result = dialog.Show();
args.Password = result.Password;
args.Success = result.Success;
args.Username = result.Username;
}
private void Operations_ActionRequired(ActionRequiredEventArgs args)
{
switch (args)
@ -742,31 +784,25 @@ namespace SafeExamBrowser.Client
splashScreen.UpdateStatus(status, true);
}
private void Registry_ValueChanged(string key, string name, object oldValue, object newValue)
private void Runtime_ConnectionLost()
{
if (key == RegistryValue.UserHive.Cursors_Key)
{
HandleCursorRegistryChange(key, name, oldValue, newValue);
}
else if (key == RegistryValue.MachineHive.EaseOfAccess_Key)
{
HandleEaseOfAccessRegistryChange(key, name, oldValue, newValue);
}
logger.Error("Lost connection to the runtime!");
messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error);
shutdown.Invoke();
}
private void HandleCursorRegistryChange(string key, string name, object oldValue, object newValue)
private void Sentinel_CursorChanged(SentinelEventArgs args)
{
logger.Warn($@"The cursor registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen...");
if (!sessionLocked)
if (coordinator.RequestSessionLock())
{
var message = text.Get(TextKey.LockScreen_CursorMessage);
var title = text.Get(TextKey.LockScreen_Title);
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorContinueOption) };
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorTerminateOption) };
sessionLocked = true;
registry.StopMonitoring(key, name);
args.Allow = true;
logger.Info("Cursor changed! Attempting to show lock screen...");
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
@ -780,27 +816,25 @@ namespace SafeExamBrowser.Client
TryRequestShutdown();
}
sessionLocked = false;
coordinator.ReleaseSessionLock();
}
else
{
logger.Info("Lock screen is already active.");
logger.Info("Cursor changed but lock screen is already active.");
}
}
private void HandleEaseOfAccessRegistryChange(string key, string name, object oldValue, object newValue)
private void Sentinel_EaseOfAccessChanged(SentinelEventArgs args)
{
logger.Warn($@"The ease of access registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen...");
if (!sessionLocked)
if (coordinator.RequestSessionLock())
{
var message = text.Get(TextKey.LockScreen_EaseOfAccessMessage);
var title = text.Get(TextKey.LockScreen_Title);
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_EaseOfAccessContinueOption) };
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_EaseOfAccessTerminateOption) };
sessionLocked = true;
registry.StopMonitoring(key, name);
args.Allow = true;
logger.Info("Ease of access changed! Attempting to show lock screen...");
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
@ -814,20 +848,85 @@ namespace SafeExamBrowser.Client
TryRequestShutdown();
}
sessionLocked = false;
coordinator.ReleaseSessionLock();
}
else
{
logger.Info("Lock screen is already active.");
logger.Info("Ease of access changed but lock screen is already active.");
}
}
private void Runtime_ConnectionLost()
private void Sentinel_SessionChanged()
{
logger.Error("Lost connection to the runtime!");
messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error);
var allow = !Settings.Service.IgnoreService && (!Settings.Service.DisableUserLock || !Settings.Service.DisableUserSwitch);
var disable = Settings.Security.DisableSessionChangeLockScreen;
shutdown.Invoke();
if (allow || disable)
{
logger.Info($"Detected user session change, but {(allow ? "session locking and/or switching is allowed" : "lock screen is deactivated")}.");
}
else
{
var message = text.Get(TextKey.LockScreen_UserSessionMessage);
var title = text.Get(TextKey.LockScreen_Title);
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_UserSessionContinueOption) };
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_UserSessionTerminateOption) };
logger.Warn("User session changed! Attempting to show lock screen...");
if (coordinator.RequestSessionLock())
{
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
if (result.OptionId == continueOption.Id)
{
logger.Info("The session will be allowed to resume as requested by the user...");
}
else if (result.OptionId == terminateOption.Id)
{
logger.Info("Attempting to shutdown as requested by the user...");
TryRequestShutdown();
}
coordinator.ReleaseSessionLock();
}
else
{
logger.Warn("User session changed but lock screen is already active.");
}
}
}
private void Sentinel_StickyKeysChanged(SentinelEventArgs args)
{
if (coordinator.RequestSessionLock())
{
var message = text.Get(TextKey.LockScreen_StickyKeysMessage);
var title = text.Get(TextKey.LockScreen_Title);
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_StickyKeysContinueOption) };
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_StickyKeysTerminateOption) };
args.Allow = true;
logger.Info("Sticky keys changed! Attempting to show lock screen...");
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
if (result.OptionId == continueOption.Id)
{
logger.Info("The session will be allowed to resume as requested by the user...");
}
else if (result.OptionId == terminateOption.Id)
{
logger.Info("Attempting to shutdown as requested by the user...");
TryRequestShutdown();
}
coordinator.ReleaseSessionLock();
}
else
{
logger.Info("Sticky keys changed but lock screen is already active.");
}
}
private void Server_LockScreenConfirmed()
@ -840,11 +939,10 @@ namespace SafeExamBrowser.Client
{
logger.Info("Attempting to show lock screen as requested by the server...");
if (!sessionLocked)
if (coordinator.RequestSessionLock())
{
sessionLocked = true;
ShowLockScreen(message, text.Get(TextKey.LockScreen_Title), Enumerable.Empty<LockScreenOption>());
sessionLocked = false;
coordinator.ReleaseSessionLock();
}
else
{
@ -865,44 +963,6 @@ namespace SafeExamBrowser.Client
ResumeActivators();
}
private void SystemMonitor_SessionChanged()
{
var allow = !Settings.Service.IgnoreService && (!Settings.Service.DisableUserLock || !Settings.Service.DisableUserSwitch);
var disable = Settings.Security.DisableSessionChangeLockScreen;
var message = text.Get(TextKey.LockScreen_UserSessionMessage);
var title = text.Get(TextKey.LockScreen_Title);
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_UserSessionContinueOption) };
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_UserSessionTerminateOption) };
if (allow || disable)
{
logger.Info($"Detected user session change, but {(allow ? "session locking and/or switching is allowed" : "lock screen is deactivated")}.");
}
else
{
logger.Warn("Detected user session change!");
if (!sessionLocked)
{
sessionLocked = true;
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
if (result.OptionId == terminateOption.Id)
{
logger.Info("Attempting to shutdown as requested by the user...");
TryRequestShutdown();
}
sessionLocked = false;
}
else
{
logger.Info("Lock screen is already active.");
}
}
}
private void Taskbar_LoseFocusRequested(bool forward)
{
Browser.Focus(forward);
@ -990,7 +1050,7 @@ namespace SafeExamBrowser.Client
logger.Info("Showing lock screen...");
PauseActivators();
lockScreen = uiFactory.CreateLockScreen(message, title, options);
lockScreen = uiFactory.CreateLockScreen(message, title, options, Settings.UserInterface.LockScreen);
lockScreen.Show();
if (Settings.SessionMode == SessionMode.Server)

View file

@ -114,12 +114,13 @@ namespace SafeExamBrowser.Client
var applicationFactory = new ApplicationFactory(applicationMonitor, ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory, new Registry(ModuleLogger(nameof(Registry))));
var clipboard = new Clipboard(ModuleLogger(nameof(Clipboard)), nativeMethods);
var coordinator = new Coordinator();
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var fileSystemDialog = BuildFileSystemDialog();
var hashAlgorithm = new HashAlgorithm();
var sentinel = new SystemSentinel(ModuleLogger(nameof(SystemSentinel)), nativeMethods, registry);
var splashScreen = uiFactory.CreateSplashScreen();
var systemMonitor = new SystemMonitor(ModuleLogger(nameof(SystemMonitor)));
var operations = new Queue<IOperation>();
@ -135,7 +136,6 @@ namespace SafeExamBrowser.Client
operations.Enqueue(new LazyInitializationOperation(BuildMouseInterceptorOperation));
operations.Enqueue(new ApplicationOperation(context, applicationFactory, applicationMonitor, logger, text));
operations.Enqueue(new DisplayMonitorOperation(context, displayMonitor, logger, taskbar));
operations.Enqueue(new SystemMonitorOperation(context, systemMonitor, logger));
operations.Enqueue(new LazyInitializationOperation(BuildShellOperation));
operations.Enqueue(new LazyInitializationOperation(BuildBrowserOperation));
operations.Enqueue(new LazyInitializationOperation(BuildServerOperation));
@ -148,18 +148,19 @@ namespace SafeExamBrowser.Client
actionCenter,
applicationMonitor,
context,
coordinator,
displayMonitor,
explorerShell,
fileSystemDialog,
hashAlgorithm,
logger,
messageBox,
networkAdapter,
sequence,
registry,
runtimeProxy,
shutdown,
splashScreen,
systemMonitor,
sentinel,
taskbar,
text,
uiFactory);

View file

@ -0,0 +1,46 @@
/*
* 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/.
*/
namespace SafeExamBrowser.Client.Contracts
{
/// <summary>
/// Coordinates concurrent operations of the client application.
/// </summary>
internal interface ICoordinator
{
/// <summary>
/// Indicates whether the reconfiguration lock is currently occupied.
/// </summary>
bool IsReconfigurationLocked();
/// <summary>
/// Indicates whether the session lock is currently occupied.
/// </summary>
bool IsSessionLocked();
/// <summary>
/// Releases the reconfiguration lock.
/// </summary>
void ReleaseReconfigurationLock();
/// <summary>
/// Releases the session lock.
/// </summary>
void ReleaseSessionLock();
/// <summary>
/// Attempts to acquire the unique reconfiguration lock. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool RequestReconfigurationLock();
/// <summary>
/// Attempts to acquire the unique session lock. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool RequestSessionLock();
}
}

View file

@ -0,0 +1,78 @@
/*
* 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 System.Collections.Concurrent;
using SafeExamBrowser.Client.Contracts;
namespace SafeExamBrowser.Client
{
internal class Coordinator : ICoordinator
{
private readonly ConcurrentBag<Guid> reconfiguration;
private readonly ConcurrentBag<Guid> session;
internal Coordinator()
{
reconfiguration = new ConcurrentBag<Guid>();
session = new ConcurrentBag<Guid>();
}
public bool IsReconfigurationLocked()
{
return !reconfiguration.IsEmpty;
}
public bool IsSessionLocked()
{
return !session.IsEmpty;
}
public void ReleaseReconfigurationLock()
{
reconfiguration.TryTake(out _);
}
public void ReleaseSessionLock()
{
session.TryTake(out _);
}
public bool RequestReconfigurationLock()
{
var acquired = false;
lock (reconfiguration)
{
if (reconfiguration.IsEmpty)
{
reconfiguration.Add(Guid.NewGuid());
acquired = true;
}
}
return acquired;
}
public bool RequestSessionLock()
{
var acquired = false;
lock (session)
{
if (session.IsEmpty)
{
session.Add(Guid.NewGuid());
acquired = true;
}
}
return acquired;
}
}
}

View file

@ -50,12 +50,12 @@ namespace SafeExamBrowser.Client.Operations
{
Context.Browser.Initialize();
if (Context.Settings.ActionCenter.EnableActionCenter)
if (Context.Settings.UserInterface.ActionCenter.EnableActionCenter)
{
actionCenter.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.ActionCenter), true);
}
if (Context.Settings.Taskbar.EnableTaskbar)
if (Context.Settings.UserInterface.Taskbar.EnableTaskbar)
{
taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar), true);
}

View file

@ -36,7 +36,7 @@ namespace SafeExamBrowser.Client.Operations
logger.Info("Initializing working area...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeWorkingArea);
displayMonitor.InitializePrimaryDisplay(Context.Settings.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0);
displayMonitor.InitializePrimaryDisplay(Context.Settings.UserInterface.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0);
displayMonitor.StartMonitoringDisplayChanges();
return OperationResult.Success;

View file

@ -107,7 +107,7 @@ namespace SafeExamBrowser.Client.Operations
{
foreach (var activator in Context.Activators)
{
if (Context.Settings.ActionCenter.EnableActionCenter && activator is IActionCenterActivator actionCenterActivator)
if (Context.Settings.UserInterface.ActionCenter.EnableActionCenter && activator is IActionCenterActivator actionCenterActivator)
{
actionCenter.Register(actionCenterActivator);
actionCenterActivator.Start();
@ -124,7 +124,7 @@ namespace SafeExamBrowser.Client.Operations
terminationActivator.Start();
}
if (Context.Settings.Taskbar.EnableTaskbar && activator is ITaskbarActivator taskbarActivator)
if (Context.Settings.UserInterface.Taskbar.EnableTaskbar && activator is ITaskbarActivator taskbarActivator)
{
taskbar.Register(taskbarActivator);
taskbarActivator.Start();
@ -134,7 +134,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeActionCenter()
{
if (Context.Settings.ActionCenter.EnableActionCenter)
if (Context.Settings.UserInterface.ActionCenter.EnableActionCenter)
{
logger.Info("Initializing action center...");
actionCenter.InitializeText(text);
@ -168,7 +168,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeTaskbar()
{
if (Context.Settings.Taskbar.EnableTaskbar)
if (Context.Settings.UserInterface.Taskbar.EnableTaskbar)
{
logger.Info("Initializing taskbar...");
taskbar.InitializeText(text);
@ -232,7 +232,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeAboutNotificationForActionCenter()
{
if (Context.Settings.ActionCenter.ShowApplicationInfo)
if (Context.Settings.UserInterface.ActionCenter.ShowApplicationInfo)
{
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(aboutNotification, Location.ActionCenter));
}
@ -240,7 +240,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeAboutNotificationForTaskbar()
{
if (Context.Settings.Taskbar.ShowApplicationInfo)
if (Context.Settings.UserInterface.Taskbar.ShowApplicationInfo)
{
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(aboutNotification, Location.Taskbar));
}
@ -248,7 +248,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeAudioForActionCenter()
{
if (Context.Settings.ActionCenter.ShowAudio)
if (Context.Settings.UserInterface.ActionCenter.ShowAudio)
{
actionCenter.AddSystemControl(uiFactory.CreateAudioControl(audio, Location.ActionCenter));
}
@ -256,7 +256,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeAudioForTaskbar()
{
if (Context.Settings.Taskbar.ShowAudio)
if (Context.Settings.UserInterface.Taskbar.ShowAudio)
{
taskbar.AddSystemControl(uiFactory.CreateAudioControl(audio, Location.Taskbar));
}
@ -264,17 +264,17 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeClockForActionCenter()
{
actionCenter.ShowClock = Context.Settings.ActionCenter.ShowClock;
actionCenter.ShowClock = Context.Settings.UserInterface.ActionCenter.ShowClock;
}
private void InitializeClockForTaskbar()
{
taskbar.ShowClock = Context.Settings.Taskbar.ShowClock;
taskbar.ShowClock = Context.Settings.UserInterface.Taskbar.ShowClock;
}
private void InitializeLogNotificationForActionCenter()
{
if (Context.Settings.ActionCenter.ShowApplicationLog)
if (Context.Settings.UserInterface.ActionCenter.ShowApplicationLog)
{
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(logNotification, Location.ActionCenter));
}
@ -282,7 +282,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeLogNotificationForTaskbar()
{
if (Context.Settings.Taskbar.ShowApplicationLog)
if (Context.Settings.UserInterface.Taskbar.ShowApplicationLog)
{
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(logNotification, Location.Taskbar));
}
@ -290,7 +290,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeKeyboardLayoutForActionCenter()
{
if (Context.Settings.ActionCenter.ShowKeyboardLayout)
if (Context.Settings.UserInterface.ActionCenter.ShowKeyboardLayout)
{
actionCenter.AddSystemControl(uiFactory.CreateKeyboardLayoutControl(keyboard, Location.ActionCenter));
}
@ -298,7 +298,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeKeyboardLayoutForTaskbar()
{
if (Context.Settings.Taskbar.ShowKeyboardLayout)
if (Context.Settings.UserInterface.Taskbar.ShowKeyboardLayout)
{
taskbar.AddSystemControl(uiFactory.CreateKeyboardLayoutControl(keyboard, Location.Taskbar));
}
@ -332,7 +332,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeNetworkForActionCenter()
{
if (Context.Settings.ActionCenter.ShowNetwork)
if (Context.Settings.UserInterface.ActionCenter.ShowNetwork)
{
actionCenter.AddSystemControl(uiFactory.CreateNetworkControl(networkAdapter, Location.ActionCenter));
}
@ -340,7 +340,7 @@ namespace SafeExamBrowser.Client.Operations
private void InitializeNetworkForTaskbar()
{
if (Context.Settings.Taskbar.ShowNetwork)
if (Context.Settings.UserInterface.Taskbar.ShowNetwork)
{
taskbar.AddSystemControl(uiFactory.CreateNetworkControl(networkAdapter, Location.Taskbar));
}

View file

@ -1,51 +0,0 @@
/*
* 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.System;
namespace SafeExamBrowser.Client.Operations
{
internal class SystemMonitorOperation : ClientOperation
{
private readonly ILogger logger;
private readonly ISystemMonitor systemMonitor;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public SystemMonitorOperation(ClientContext context, ISystemMonitor systemMonitor, ILogger logger) : base(context)
{
this.logger = logger;
this.systemMonitor = systemMonitor;
}
public override OperationResult Perform()
{
logger.Info("Initializing system events...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSystemEvents);
systemMonitor.Start();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Finalizing system events...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeSystemEvents);
systemMonitor.Stop();
return OperationResult.Success;
}
}
}

View file

@ -16,6 +16,9 @@ using System.Windows;
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// Required for mocking internal contracts with Moq
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("SafeExamBrowser.Client.UnitTests")]
//In order to begin building localizable applications, set

View file

@ -74,6 +74,8 @@
<Compile Include="App.cs" />
<Compile Include="ClientContext.cs" />
<Compile Include="ClientController.cs" />
<Compile Include="Contracts\ICoordinator.cs" />
<Compile Include="Coordinator.cs" />
<Compile Include="Operations\ClientHostDisconnectionOperation.cs" />
<Compile Include="Operations\ClientOperation.cs" />
<Compile Include="Operations\ConfigurationOperation.cs" />
@ -95,7 +97,6 @@
<Compile Include="Operations\ApplicationOperation.cs" />
<Compile Include="Operations\ServerOperation.cs" />
<Compile Include="Operations\ShellOperation.cs" />
<Compile Include="Operations\SystemMonitorOperation.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>

View file

@ -26,6 +26,11 @@ namespace SafeExamBrowser.Configuration.Contracts
/// </summary>
public Guid ClientAuthenticationToken { get; set; }
/// <summary>
/// Indicates whether a configuration resource needs to be loaded in the browser because it requires authentication or is a webpage.
/// </summary>
public bool IsBrowserResource { get; set; }
/// <summary>
/// The unique session identifier.
/// </summary>

View file

@ -0,0 +1,62 @@
/*
* 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 System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Integrity;
using SafeExamBrowser.Configuration.Cryptography;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Configuration.UnitTests.Cryptography
{
[TestClass]
public class KeyGeneratorTests
{
private AppConfig appConfig;
private Mock<IIntegrityModule> integrityModule;
private Mock<ILogger> logger;
private KeyGenerator sut;
[TestInitialize]
public void Initialize()
{
appConfig = new AppConfig();
integrityModule = new Mock<IIntegrityModule>();
logger = new Mock<ILogger>();
sut = new KeyGenerator(appConfig, integrityModule.Object, logger.Object);
}
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void CalculateBrowserExamKeyHash_MustFailWithoutUrl()
{
sut.CalculateBrowserExamKeyHash(default, default, default);
}
[TestMethod]
[ExpectedException(typeof(Exception), AllowDerivedTypes = true)]
public void CalculateConfigurationKeyHash_MustFailWithoutUrl()
{
sut.CalculateConfigurationKeyHash(default, default);
}
[TestMethod]
public void MustAllowForConcurrentKeyHashCalculation()
{
Parallel.For(0, 1000, (_) =>
{
sut.CalculateBrowserExamKeyHash(default, default, "https://www.safeexambrowser.org");
sut.CalculateConfigurationKeyHash(default, "https://www.safeexambrowser.org");
});
}
}
}

View file

@ -148,6 +148,7 @@
<Compile Include="Cryptography\PasswordEncryptionTests.cs" />
<Compile Include="Cryptography\PublicKeyEncryptionTests.cs" />
<Compile Include="Cryptography\PublicKeySymmetricEncryptionTests.cs" />
<Compile Include="Cryptography\KeyGeneratorTests.cs" />
<Compile Include="DataCompression\GZipCompressorTests.cs" />
<Compile Include="DataFormats\BinaryParserTests.cs" />
<Compile Include="DataFormats\BinarySerializerTests.cs" />

View file

@ -31,7 +31,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
new UserInterfaceDataMapper()
};
internal void MapRawDataToSettings(IDictionary<string, object> rawData, AppSettings settings)
internal void Map(IDictionary<string, object> rawData, AppSettings settings)
{
foreach (var item in rawData)
{

View file

@ -575,12 +575,12 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
var useCustomForDesktop = rawData.TryGetValue(Keys.Browser.UserAgentModeDesktop, out var value) && value as int? != DEFAULT;
var useCustomForMobile = rawData.TryGetValue(Keys.Browser.UserAgentModeMobile, out value) && value as int? != DEFAULT;
if (settings.UserInterfaceMode == UserInterfaceMode.Desktop && useCustomForDesktop)
if (settings.UserInterface.Mode == UserInterfaceMode.Desktop && useCustomForDesktop)
{
settings.Browser.UseCustomUserAgent = true;
settings.Browser.CustomUserAgent = rawData[Keys.Browser.CustomUserAgentDesktop] as string;
}
else if (settings.UserInterfaceMode == UserInterfaceMode.Mobile && useCustomForMobile)
else if (settings.UserInterface.Mode == UserInterfaceMode.Mobile && useCustomForMobile)
{
settings.Browser.UseCustomUserAgent = true;
settings.Browser.CustomUserAgent = rawData[Keys.Browser.CustomUserAgentMobile] as string;

View file

@ -21,60 +21,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
case Keys.Proctoring.ForceRaiseHandMessage:
MapForceRaiseHandMessage(settings, value);
break;
case Keys.Proctoring.JitsiMeet.AllowChat:
MapJitsiMeetAllowChat(settings, value);
break;
case Keys.Proctoring.JitsiMeet.AllowClosedCaptions:
MapJitsiMeetAllowClosedCaptions(settings, value);
break;
case Keys.Proctoring.JitsiMeet.AllowRaiseHand:
MapJitsiMeetAllowRaiseHands(settings, value);
break;
case Keys.Proctoring.JitsiMeet.AllowRecording:
MapJitsiMeetAllowRecording(settings, value);
break;
case Keys.Proctoring.JitsiMeet.AllowTileView:
MapJitsiMeetAllowTileView(settings, value);
break;
case Keys.Proctoring.JitsiMeet.AudioMuted:
MapJitsiMeetAudioMuted(settings, value);
break;
case Keys.Proctoring.JitsiMeet.AudioOnly:
MapJitsiMeetAudioOnly(settings, value);
break;
case Keys.Proctoring.JitsiMeet.Enabled:
MapJitsiMeetEnabled(settings, value);
break;
case Keys.Proctoring.JitsiMeet.ReceiveAudio:
MapJitsiMeetReceiveAudio(settings, value);
break;
case Keys.Proctoring.JitsiMeet.ReceiveVideo:
MapJitsiMeetReceiveVideo(settings, value);
break;
case Keys.Proctoring.JitsiMeet.RoomName:
MapJitsiMeetRoomName(settings, value);
break;
case Keys.Proctoring.JitsiMeet.SendAudio:
MapJitsiMeetSendAudio(settings, value);
break;
case Keys.Proctoring.JitsiMeet.SendVideo:
MapJitsiMeetSendVideo(settings, value);
break;
case Keys.Proctoring.JitsiMeet.ServerUrl:
MapJitsiMeetServerUrl(settings, value);
break;
case Keys.Proctoring.JitsiMeet.ShowMeetingName:
MapJitsiMeetShowMeetingName(settings, value);
break;
case Keys.Proctoring.JitsiMeet.Subject:
MapJitsiMeetSubject(settings, value);
break;
case Keys.Proctoring.JitsiMeet.Token:
MapJitsiMeetToken(settings, value);
break;
case Keys.Proctoring.JitsiMeet.VideoMuted:
MapJitsiMeetVideoMuted(settings, value);
break;
case Keys.Proctoring.ScreenProctoring.ClientId:
MapClientId(settings, value);
break;
@ -120,51 +66,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
case Keys.Proctoring.ShowTaskbarNotification:
MapShowTaskbarNotification(settings, value);
break;
case Keys.Proctoring.WindowVisibility:
MapWindowVisibility(settings, value);
break;
case Keys.Proctoring.Zoom.AllowChat:
MapZoomAllowChat(settings, value);
break;
case Keys.Proctoring.Zoom.AllowClosedCaptions:
MapZoomAllowClosedCaptions(settings, value);
break;
case Keys.Proctoring.Zoom.AllowRaiseHand:
MapZoomAllowRaiseHands(settings, value);
break;
case Keys.Proctoring.Zoom.AudioMuted:
MapZoomAudioMuted(settings, value);
break;
case Keys.Proctoring.Zoom.Enabled:
MapZoomEnabled(settings, value);
break;
case Keys.Proctoring.Zoom.MeetingNumber:
MapZoomMeetingNumber(settings, value);
break;
case Keys.Proctoring.Zoom.ReceiveAudio:
MapZoomReceiveAudio(settings, value);
break;
case Keys.Proctoring.Zoom.ReceiveVideo:
MapZoomReceiveVideo(settings, value);
break;
case Keys.Proctoring.Zoom.SendAudio:
MapZoomSendAudio(settings, value);
break;
case Keys.Proctoring.Zoom.SendVideo:
MapZoomSendVideo(settings, value);
break;
case Keys.Proctoring.Zoom.Signature:
MapZoomSignature(settings, value);
break;
case Keys.Proctoring.Zoom.Subject:
MapZoomSubject(settings, value);
break;
case Keys.Proctoring.Zoom.UserName:
MapZoomUserName(settings, value);
break;
case Keys.Proctoring.Zoom.VideoMuted:
MapZoomVideoMuted(settings, value);
break;
}
}
@ -176,150 +77,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
}
}
private void MapJitsiMeetAllowChat(AppSettings settings, object value)
{
if (value is bool allow)
{
settings.Proctoring.JitsiMeet.AllowChat = allow;
}
}
private void MapJitsiMeetAllowClosedCaptions(AppSettings settings, object value)
{
if (value is bool allow)
{
settings.Proctoring.JitsiMeet.AllowClosedCaptions = allow;
}
}
private void MapJitsiMeetAllowRaiseHands(AppSettings settings, object value)
{
if (value is bool allow)
{
settings.Proctoring.JitsiMeet.AllowRaiseHand = allow;
}
}
private void MapJitsiMeetAllowRecording(AppSettings settings, object value)
{
if (value is bool allow)
{
settings.Proctoring.JitsiMeet.AllowRecording = allow;
}
}
private void MapJitsiMeetAllowTileView(AppSettings settings, object value)
{
if (value is bool allow)
{
settings.Proctoring.JitsiMeet.AllowTileView = allow;
}
}
private void MapJitsiMeetAudioMuted(AppSettings settings, object value)
{
if (value is bool audioMuted)
{
settings.Proctoring.JitsiMeet.AudioMuted = audioMuted;
}
}
private void MapJitsiMeetAudioOnly(AppSettings settings, object value)
{
if (value is bool audioOnly)
{
settings.Proctoring.JitsiMeet.AudioOnly = audioOnly;
}
}
private void MapJitsiMeetEnabled(AppSettings settings, object value)
{
if (value is bool enabled)
{
settings.Proctoring.JitsiMeet.Enabled = enabled;
}
}
private void MapJitsiMeetReceiveAudio(AppSettings settings, object value)
{
if (value is bool receive)
{
settings.Proctoring.JitsiMeet.ReceiveAudio = receive;
}
}
private void MapJitsiMeetReceiveVideo(AppSettings settings, object value)
{
if (value is bool receive)
{
settings.Proctoring.JitsiMeet.ReceiveVideo = receive;
}
}
private void MapJitsiMeetRoomName(AppSettings settings, object value)
{
if (value is string name)
{
settings.Proctoring.JitsiMeet.RoomName = name;
}
}
private void MapJitsiMeetSendAudio(AppSettings settings, object value)
{
if (value is bool send)
{
settings.Proctoring.JitsiMeet.SendAudio = send;
}
}
private void MapJitsiMeetSendVideo(AppSettings settings, object value)
{
if (value is bool send)
{
settings.Proctoring.JitsiMeet.SendVideo = send;
}
}
private void MapJitsiMeetServerUrl(AppSettings settings, object value)
{
if (value is string url)
{
settings.Proctoring.JitsiMeet.ServerUrl = url;
}
}
private void MapJitsiMeetShowMeetingName(AppSettings settings, object value)
{
if (value is bool show)
{
settings.Proctoring.JitsiMeet.ShowMeetingName = show;
}
}
private void MapJitsiMeetSubject(AppSettings settings, object value)
{
if (value is string subject)
{
settings.Proctoring.JitsiMeet.Subject = subject;
}
}
private void MapJitsiMeetToken(AppSettings settings, object value)
{
if (value is string token)
{
settings.Proctoring.JitsiMeet.Token = token;
}
}
private void MapJitsiMeetVideoMuted(AppSettings settings, object value)
{
if (value is bool muted)
{
settings.Proctoring.JitsiMeet.VideoMuted = muted;
}
}
private void MapCaptureApplicationData(AppSettings settings, object value)
{
if (value is bool capture)
@ -463,144 +220,5 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
settings.Proctoring.ShowTaskbarNotification = show;
}
}
private void MapWindowVisibility(AppSettings settings, object value)
{
const int HIDDEN = 0;
const int ALLOW_SHOW = 1;
const int ALLOW_HIDE = 2;
const int VISIBLE = 3;
if (value is int visibility)
{
switch (visibility)
{
case HIDDEN:
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
break;
case ALLOW_SHOW:
settings.Proctoring.WindowVisibility = WindowVisibility.AllowToShow;
break;
case ALLOW_HIDE:
settings.Proctoring.WindowVisibility = WindowVisibility.AllowToHide;
break;
case VISIBLE:
settings.Proctoring.WindowVisibility = WindowVisibility.Visible;
break;
}
}
}
private void MapZoomAllowChat(AppSettings settings, object value)
{
if (value is bool allow)
{
settings.Proctoring.Zoom.AllowChat = allow;
}
}
private void MapZoomAllowClosedCaptions(AppSettings settings, object value)
{
if (value is bool allow)
{
settings.Proctoring.Zoom.AllowClosedCaptions = allow;
}
}
private void MapZoomAllowRaiseHands(AppSettings settings, object value)
{
if (value is bool allow)
{
settings.Proctoring.Zoom.AllowRaiseHand = allow;
}
}
private void MapZoomAudioMuted(AppSettings settings, object value)
{
if (value is bool muted)
{
settings.Proctoring.Zoom.AudioMuted = muted;
}
}
private void MapZoomEnabled(AppSettings settings, object value)
{
if (value is bool enabled)
{
settings.Proctoring.Zoom.Enabled = enabled;
}
}
private void MapZoomMeetingNumber(AppSettings settings, object value)
{
if (value is string number)
{
settings.Proctoring.Zoom.MeetingNumber = number;
}
}
private void MapZoomReceiveAudio(AppSettings settings, object value)
{
if (value is bool receive)
{
settings.Proctoring.Zoom.ReceiveAudio = receive;
}
}
private void MapZoomReceiveVideo(AppSettings settings, object value)
{
if (value is bool receive)
{
settings.Proctoring.Zoom.ReceiveVideo = receive;
}
}
private void MapZoomSendAudio(AppSettings settings, object value)
{
if (value is bool send)
{
settings.Proctoring.Zoom.SendAudio = send;
}
}
private void MapZoomSendVideo(AppSettings settings, object value)
{
if (value is bool send)
{
settings.Proctoring.Zoom.SendVideo = send;
}
}
private void MapZoomSignature(AppSettings settings, object value)
{
if (value is string signature)
{
settings.Proctoring.Zoom.Signature = signature;
}
}
private void MapZoomSubject(AppSettings settings, object value)
{
if (value is string subject)
{
settings.Proctoring.Zoom.Subject = subject;
}
}
private void MapZoomUserName(AppSettings settings, object value)
{
if (value is string name)
{
settings.Proctoring.Zoom.UserName = name;
}
}
private void MapZoomVideoMuted(AppSettings settings, object value)
{
if (value is bool muted)
{
settings.Proctoring.Zoom.VideoMuted = muted;
}
}
}
}

View file

@ -26,6 +26,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
case Keys.Security.AllowReconfiguration:
MapAllowReconfiguration(settings, value);
break;
case Keys.Security.AllowStickyKeys:
MapAllowStickyKeys(settings, value);
break;
case Keys.Security.AllowTermination:
MapAllowTermination(settings, value);
break;
@ -47,6 +50,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
case Keys.Security.VerifyCursorConfiguration:
MapVerifyCursorConfiguration(settings, value);
break;
case Keys.Security.VerifySessionIntegrity:
MapVerifySessionIntegrity(settings, value);
break;
case Keys.Security.VersionRestrictions:
MapVersionRestrictions(settings, value);
break;
@ -75,6 +81,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
}
}
private void MapAllowStickyKeys(AppSettings settings, object value)
{
if (value is bool allow)
{
settings.Security.AllowStickyKeys = allow;
}
}
private void MapAllowTermination(AppSettings settings, object value)
{
if (value is bool allow)
@ -94,12 +108,12 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
if (settings.Security.AllowApplicationLogAccess)
{
settings.ActionCenter.ShowApplicationLog = true;
settings.UserInterface.ActionCenter.ShowApplicationLog = true;
}
else
{
settings.ActionCenter.ShowApplicationLog = false;
settings.Taskbar.ShowApplicationLog = false;
settings.UserInterface.ActionCenter.ShowApplicationLog = false;
settings.UserInterface.Taskbar.ShowApplicationLog = false;
}
}
@ -175,6 +189,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
}
}
private void MapVerifySessionIntegrity(AppSettings settings, object value)
{
if (value is bool verify)
{
settings.Security.VerifySessionIntegrity = verify;
}
}
private void MapVersionRestrictions(AppSettings settings, object value)
{
if (value is IList<object> restrictions)

View file

@ -20,6 +20,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
case Keys.UserInterface.ActionCenter.EnableActionCenter:
MapEnableActionCenter(settings, value);
break;
case Keys.UserInterface.LockScreen.BackgroundColor:
MapLockScreenBackgroundColor(settings, value);
break;
case Keys.UserInterface.SystemControls.Audio.Show:
MapShowAudio(settings, value);
break;
@ -54,7 +57,15 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is bool enable)
{
settings.ActionCenter.EnableActionCenter = enable;
settings.UserInterface.ActionCenter.EnableActionCenter = enable;
}
}
private void MapLockScreenBackgroundColor(AppSettings settings, object value)
{
if (value is string color)
{
settings.UserInterface.LockScreen.BackgroundColor = color;
}
}
@ -62,8 +73,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is bool show)
{
settings.ActionCenter.ShowAudio = show;
settings.Taskbar.ShowAudio = show;
settings.UserInterface.ActionCenter.ShowAudio = show;
settings.UserInterface.Taskbar.ShowAudio = show;
}
}
@ -71,8 +82,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is bool show)
{
settings.ActionCenter.ShowClock = show;
settings.Taskbar.ShowClock = show;
settings.UserInterface.ActionCenter.ShowClock = show;
settings.UserInterface.Taskbar.ShowClock = show;
}
}
@ -80,8 +91,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is bool show)
{
settings.ActionCenter.ShowKeyboardLayout = show;
settings.Taskbar.ShowKeyboardLayout = show;
settings.UserInterface.ActionCenter.ShowKeyboardLayout = show;
settings.UserInterface.Taskbar.ShowKeyboardLayout = show;
}
}
@ -89,8 +100,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is bool show)
{
settings.ActionCenter.ShowNetwork = show;
settings.Taskbar.ShowNetwork = show;
settings.UserInterface.ActionCenter.ShowNetwork = show;
settings.UserInterface.Taskbar.ShowNetwork = show;
}
}
@ -114,7 +125,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is bool enable)
{
settings.Taskbar.EnableTaskbar = enable;
settings.UserInterface.Taskbar.EnableTaskbar = enable;
}
}
@ -122,7 +133,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is bool show)
{
settings.Taskbar.ShowApplicationLog = show;
settings.UserInterface.Taskbar.ShowApplicationLog = show;
}
}
@ -130,7 +141,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is bool mobile)
{
settings.UserInterfaceMode = mobile ? UserInterfaceMode.Mobile : UserInterfaceMode.Desktop;
settings.UserInterface.Mode = mobile ? UserInterfaceMode.Mobile : UserInterfaceMode.Desktop;
}
}
}

View file

@ -12,7 +12,6 @@ using System.IO;
using System.Security.Cryptography;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.Settings.Security;
namespace SafeExamBrowser.Configuration.ConfigurationData
@ -21,8 +20,13 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
internal void Process(IDictionary<string, object> rawData, AppSettings settings)
{
AllowBrowserToolbarForReloading(settings);
ProcessDefault(settings);
CalculateConfigurationKey(rawData, settings);
}
internal void ProcessDefault(AppSettings settings)
{
AllowBrowserToolbarForReloading(settings);
InitializeBrowserHomeFunctionality(settings);
InitializeClipboardSettings(settings);
InitializeProctoringSettings(settings);
@ -77,15 +81,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
private void InitializeProctoringSettings(AppSettings settings)
{
settings.Proctoring.Enabled = settings.Proctoring.ScreenProctoring.Enabled;
// The video proctoring implementations are disabled for version 3.7.0 and will be completely removed for version 3.8.0.
settings.Proctoring.JitsiMeet.Enabled = false;
settings.Proctoring.Zoom.Enabled = false;
if (settings.Proctoring.JitsiMeet.Enabled && !settings.Proctoring.JitsiMeet.ReceiveVideo)
{
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
}
}
private void RemoveLegacyBrowsers(AppSettings settings)

View file

@ -100,13 +100,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{
var settings = new AppSettings();
settings.ActionCenter.EnableActionCenter = true;
settings.ActionCenter.ShowApplicationInfo = true;
settings.ActionCenter.ShowApplicationLog = false;
settings.ActionCenter.ShowClock = true;
settings.ActionCenter.ShowKeyboardLayout = true;
settings.ActionCenter.ShowNetwork = false;
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "AA_v3.exe", OriginalName = "AA_v3.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "AeroAdmin.exe", OriginalName = "AeroAdmin.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "beamyourscreen-host.exe", OriginalName = "beamyourscreen-host.exe" });
@ -122,7 +115,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Discord.exe", OriginalName = "Discord.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Element.exe", OriginalName = "Element.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "g2mcomm.exe", OriginalName = "g2mcomm.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "g2mcomm.exe", OriginalName = "g2mcomm.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "g2mlauncher.exe", OriginalName = "g2mlauncher.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "g2mstart.exe", OriginalName = "g2mstart.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "GotoMeetingWinStore.exe", OriginalName = "GotoMeetingWinStore.exe" });
@ -140,6 +132,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "remoting_host.exe", OriginalName = "remoting_host.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "RPCService.exe", OriginalName = "RPCService.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "RPCSuite.exe", OriginalName = "RPCSuite.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "sethc.exe", OriginalName = "sethc.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Skype.exe", OriginalName = "Skype.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "SkypeApp.exe", OriginalName = "SkypeApp.exe" });
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "SkypeHost.exe", OriginalName = "SkypeHost.exe" });
@ -172,13 +165,13 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Browser.AdditionalWindow.UrlPolicy = UrlPolicy.Never;
settings.Browser.AllowConfigurationDownloads = true;
settings.Browser.AllowCustomDownAndUploadLocation = false;
settings.Browser.AllowDownloads = false;
settings.Browser.AllowDownloads = true;
settings.Browser.AllowFind = true;
settings.Browser.AllowPageZoom = true;
settings.Browser.AllowPdfReader = true;
settings.Browser.AllowPdfReaderToolbar = false;
settings.Browser.AllowPrint = false;
settings.Browser.AllowUploads = false;
settings.Browser.AllowUploads = true;
settings.Browser.DeleteCacheOnShutdown = true;
settings.Browser.DeleteCookiesOnShutdown = true;
settings.Browser.DeleteCookiesOnStartup = true;
@ -203,6 +196,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Browser.ShowFileSystemElementPath = true;
settings.Browser.StartUrl = "https://www.safeexambrowser.org/start";
settings.Browser.UseCustomUserAgent = false;
settings.Browser.UseIsolatedClipboard = true;
settings.Browser.UseQueryParameter = false;
settings.Browser.UseTemporaryDownAndUploadDirectory = false;
@ -216,7 +210,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Keyboard.AllowAltEsc = false;
settings.Keyboard.AllowAltF4 = false;
settings.Keyboard.AllowAltTab = true;
settings.Keyboard.AllowCtrlC = true;
settings.Keyboard.AllowCtrlEsc = false;
settings.Keyboard.AllowCtrlV = true;
settings.Keyboard.AllowCtrlX = true;
settings.Keyboard.AllowEsc = true;
settings.Keyboard.AllowF1 = true;
settings.Keyboard.AllowF2 = true;
@ -243,20 +240,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Proctoring.Enabled = false;
settings.Proctoring.ForceRaiseHandMessage = false;
settings.Proctoring.JitsiMeet.AllowChat = false;
settings.Proctoring.JitsiMeet.AllowClosedCaptions = false;
settings.Proctoring.JitsiMeet.AllowRaiseHand = false;
settings.Proctoring.JitsiMeet.AllowRecording = false;
settings.Proctoring.JitsiMeet.AllowTileView = false;
settings.Proctoring.JitsiMeet.AudioMuted = true;
settings.Proctoring.JitsiMeet.AudioOnly = false;
settings.Proctoring.JitsiMeet.Enabled = false;
settings.Proctoring.JitsiMeet.ReceiveAudio = false;
settings.Proctoring.JitsiMeet.ReceiveVideo = false;
settings.Proctoring.JitsiMeet.SendAudio = true;
settings.Proctoring.JitsiMeet.SendVideo = true;
settings.Proctoring.JitsiMeet.ShowMeetingName = false;
settings.Proctoring.JitsiMeet.VideoMuted = false;
settings.Proctoring.ScreenProctoring.Enabled = false;
settings.Proctoring.ScreenProctoring.ImageDownscaling = 1.0;
settings.Proctoring.ScreenProctoring.ImageFormat = ImageFormat.Png;
@ -268,25 +251,16 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.Proctoring.ScreenProctoring.MinInterval = 1000;
settings.Proctoring.ShowRaiseHandNotification = true;
settings.Proctoring.ShowTaskbarNotification = true;
settings.Proctoring.WindowVisibility = WindowVisibility.Hidden;
settings.Proctoring.Zoom.AllowChat = false;
settings.Proctoring.Zoom.AllowClosedCaptions = false;
settings.Proctoring.Zoom.AllowRaiseHand = false;
settings.Proctoring.Zoom.AudioMuted = true;
settings.Proctoring.Zoom.Enabled = false;
settings.Proctoring.Zoom.ReceiveAudio = false;
settings.Proctoring.Zoom.ReceiveVideo = false;
settings.Proctoring.Zoom.SendAudio = true;
settings.Proctoring.Zoom.SendVideo = true;
settings.Proctoring.Zoom.VideoMuted = false;
settings.Security.AllowApplicationLogAccess = false;
settings.Security.AllowTermination = true;
settings.Security.AllowReconfiguration = false;
settings.Security.AllowStickyKeys = false;
settings.Security.ClipboardPolicy = ClipboardPolicy.Isolated;
settings.Security.DisableSessionChangeLockScreen = false;
settings.Security.KioskMode = KioskMode.CreateNewDesktop;
settings.Security.VerifyCursorConfiguration = true;
settings.Security.VerifySessionIntegrity = true;
settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Deny;
settings.Server.PingInterval = 1000;
@ -316,14 +290,20 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
settings.System.AlwaysOn = true;
settings.Taskbar.EnableTaskbar = true;
settings.Taskbar.ShowApplicationInfo = false;
settings.Taskbar.ShowApplicationLog = false;
settings.Taskbar.ShowClock = true;
settings.Taskbar.ShowKeyboardLayout = true;
settings.Taskbar.ShowNetwork = false;
settings.UserInterfaceMode = UserInterfaceMode.Desktop;
settings.UserInterface.ActionCenter.EnableActionCenter = true;
settings.UserInterface.ActionCenter.ShowApplicationInfo = true;
settings.UserInterface.ActionCenter.ShowApplicationLog = false;
settings.UserInterface.ActionCenter.ShowClock = true;
settings.UserInterface.ActionCenter.ShowKeyboardLayout = true;
settings.UserInterface.ActionCenter.ShowNetwork = false;
settings.UserInterface.LockScreen.BackgroundColor = "#ff0000";
settings.UserInterface.Mode = UserInterfaceMode.Desktop;
settings.UserInterface.Taskbar.EnableTaskbar = true;
settings.UserInterface.Taskbar.ShowApplicationInfo = false;
settings.UserInterface.Taskbar.ShowApplicationLog = false;
settings.UserInterface.Taskbar.ShowClock = true;
settings.UserInterface.Taskbar.ShowKeyboardLayout = true;
settings.UserInterface.Taskbar.ShowNetwork = false;
return settings;
}

View file

@ -234,29 +234,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string ForceRaiseHandMessage = "raiseHandButtonAlwaysPromptMessage";
internal const string ShowRaiseHand = "raiseHandButtonShow";
internal const string ShowTaskbarNotification = "showProctoringViewButton";
internal const string WindowVisibility = "remoteProctoringViewShow";
internal static class JitsiMeet
{
internal const string AllowChat = "jitsiMeetFeatureFlagChat";
internal const string AllowClosedCaptions = "jitsiMeetFeatureFlagCloseCaptions";
internal const string AllowRaiseHand = "jitsiMeetFeatureFlagRaiseHand";
internal const string AllowRecording = "jitsiMeetFeatureFlagRecording";
internal const string AllowTileView = "jitsiMeetFeatureFlagTileView";
internal const string AudioMuted = "jitsiMeetAudioMuted";
internal const string AudioOnly = "jitsiMeetAudioOnly";
internal const string Enabled = "jitsiMeetEnable";
internal const string ReceiveAudio = "jitsiMeetReceiveAudio";
internal const string ReceiveVideo = "jitsiMeetReceiveVideo";
internal const string RoomName = "jitsiMeetRoom";
internal const string SendAudio = "jitsiMeetSendAudio";
internal const string SendVideo = "jitsiMeetSendVideo";
internal const string ServerUrl = "jitsiMeetServerURL";
internal const string ShowMeetingName = "jitsiMeetFeatureFlagDisplayMeetingName";
internal const string Subject = "jitsiMeetSubject";
internal const string Token = "jitsiMeetToken";
internal const string VideoMuted = "jitsiMeetVideoMuted";
}
internal static class ScreenProctoring
{
@ -278,24 +255,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string CaptureWindowTitle = "screenProctoringMetadataWindowTitleEnabled";
}
}
internal static class Zoom
{
internal const string AllowChat = "zoomFeatureFlagChat";
internal const string AllowClosedCaptions = "zoomFeatureFlagCloseCaptions";
internal const string AllowRaiseHand = "zoomFeatureFlagRaiseHand";
internal const string AudioMuted = "zoomAudioMuted";
internal const string Enabled = "zoomEnable";
internal const string MeetingNumber = "zoomRoom";
internal const string ReceiveAudio = "zoomReceiveAudio";
internal const string ReceiveVideo = "zoomReceiveVideo";
internal const string SendAudio = "zoomSendAudio";
internal const string SendVideo = "zoomSendVideo";
internal const string Signature = "zoomToken";
internal const string Subject = "zoomSubject";
internal const string UserName = "zoomUserInfoDisplayName";
internal const string VideoMuted = "zoomVideoMuted";
}
}
internal static class Security
@ -303,6 +262,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string AdminPasswordHash = "hashedAdminPassword";
internal const string AllowApplicationLog = "allowApplicationLog";
internal const string AllowReconfiguration = "examSessionReconfigureAllow";
internal const string AllowStickyKeys = "allowStickyKeys";
internal const string AllowTermination = "allowQuit";
internal const string AllowVirtualMachine = "allowVirtualMachine";
internal const string ClipboardPolicy = "clipboardPolicy";
@ -312,6 +272,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string QuitPasswordHash = "hashedQuitPassword";
internal const string ReconfigurationUrl = "examSessionReconfigureConfigURL";
internal const string VerifyCursorConfiguration = "enableCursorVerification";
internal const string VerifySessionIntegrity = "enableSessionVerification";
internal const string VersionRestrictions = "sebAllowedVersions";
}
@ -366,6 +327,11 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string EnableActionCenter = "showSideMenu";
}
internal static class LockScreen
{
internal const string BackgroundColor = "lockScreenBackgroundColor";
}
internal static class SystemControls
{
internal static class Audio

View file

@ -122,6 +122,8 @@ namespace SafeExamBrowser.Configuration
var status = default(LoadStatus);
settings = LoadDefaultSettings();
dataProcessor.ProcessDefault(settings);
logger.Info($"Initialized default settings, now attempting to load '{resource}'...");
try
@ -136,7 +138,7 @@ namespace SafeExamBrowser.Configuration
if (status == LoadStatus.Success)
{
dataMapper.MapRawDataToSettings(data, settings);
dataMapper.Map(data, settings);
dataProcessor.Process(data, settings);
}
}

View file

@ -9,6 +9,7 @@
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Configuration.Contracts.Integrity;
@ -20,7 +21,7 @@ namespace SafeExamBrowser.Configuration.Cryptography
{
private readonly object @lock = new object();
private readonly SHA256Managed algorithm;
private readonly ThreadLocal<SHA256Managed> algorithm;
private readonly AppConfig appConfig;
private readonly IIntegrityModule integrityModule;
private readonly ILogger logger;
@ -29,7 +30,7 @@ namespace SafeExamBrowser.Configuration.Cryptography
public KeyGenerator(AppConfig appConfig, IIntegrityModule integrityModule, ILogger logger)
{
this.algorithm = new SHA256Managed();
this.algorithm = new ThreadLocal<SHA256Managed>(() => new SHA256Managed());
this.appConfig = appConfig;
this.integrityModule = integrityModule;
this.logger = logger;
@ -52,7 +53,7 @@ namespace SafeExamBrowser.Configuration.Cryptography
public string CalculateBrowserExamKeyHash(string configurationKey, byte[] salt, string url)
{
var urlWithoutFragment = url.Split('#')[0];
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey(configurationKey, salt))));
var hash = algorithm.Value.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey(configurationKey, salt))));
var key = ToString(hash);
return key;
@ -61,7 +62,7 @@ namespace SafeExamBrowser.Configuration.Cryptography
public string CalculateConfigurationKeyHash(string configurationKey, string url)
{
var urlWithoutFragment = url.Split('#')[0];
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + configurationKey));
var hash = algorithm.Value.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + configurationKey));
var key = ToString(hash);
return key;
@ -111,6 +112,8 @@ namespace SafeExamBrowser.Configuration.Cryptography
browserExamKey = key;
}
logger.Debug("Successfully calculated browser exam key using simplified calculation.");
}
}
}

View file

@ -149,7 +149,7 @@ namespace SafeExamBrowser.Configuration.DataResources
{
data = default;
logger.Debug($"The {(IsUnauthorized(response) ? "resource needs authentication" : " response data is HTML")}.");
logger.Debug($"The {(IsUnauthorized(response) ? "resource needs authentication" : "response data is HTML")}.");
return LoadStatus.LoadWithBrowser;
}

View file

@ -47,6 +47,11 @@ namespace SafeExamBrowser.I18n.Contracts
BrowserWindow_ZoomMenuMinus,
BrowserWindow_ZoomMenuPlus,
Build,
CredentialsDialog_PasswordLabel,
CredentialsDialog_UsernameLabel,
CredentialsDialog_UsernameOptionalLabel,
CredentialsDialog_WirelessNetworkMessage,
CredentialsDialog_WirelessNetworkTitle,
ExamSelectionDialog_Cancel,
ExamSelectionDialog_Message,
ExamSelectionDialog_Select,
@ -78,6 +83,9 @@ namespace SafeExamBrowser.I18n.Contracts
LockScreen_EaseOfAccessMessage,
LockScreen_EaseOfAccessTerminateOption,
LockScreen_SessionIntegrityMessage,
LockScreen_StickyKeysMessage,
LockScreen_StickyKeysContinueOption,
LockScreen_StickyKeysTerminateOption,
LockScreen_Title,
LockScreen_UnlockButton,
LockScreen_UserSessionContinueOption,

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Passwort:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Benutzername:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Benutzername (falls erforderlich):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Bitte geben Sie die erforderlichen Anmeldeinformationen ein, um eine Verbindung mit dem drahtlosen Netzwerk "%%_NAME_%%" herzustellen.
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Netzwerk-Authentifizierung erforderlich
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Abbrechen
</Entry>
@ -192,6 +207,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
Die letzte Sitzung mit der momentan aktiven Konfiguration oder Start-URL wurde nicht ordnungsgemäss beendet! Geben Sie bitte das korrekte Passwort ein, um SEB zu entsperren.
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
Zustand der Sticky-Keys temporär erlauben. Dies gilt nur für die momentan laufende Sitzung!
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
Ein verbotener Zustand der Sticky-Keys wurde detektiert. Bitte wählen Sie eine der verfügbaren Optionen aus und geben Sie das korrekte Passwort ein, um SEB zu entsperren.
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
Safe Exam Browser beenden. WARNUNG: Sie werden keine Möglichkeit haben, Daten zu speichern oder weitere Aktionen auszuführen, SEB wird sofort beendet!
</Entry>
<Entry key="LockScreen_Title">
SEB GESPERRT
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Password:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Username:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Username (if required):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Please enter the required credentials to establish a connection to the wireless network "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Network Authentication Required
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Cancel
</Entry>
@ -192,6 +207,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
The last session with the currently active configuration or start URL was not terminated properly! Please enter the correct password to unlock SEB.
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
Temporarily allow the state of the sticky keys. This applies only to the currently running session!
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
A prohibited state of the sticky keys has been detected. In order to unlock SEB, please select one of the available options and enter the correct unlock password.
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
Terminate Safe Exam Browser. WARNING: There will be no possibility to save data or perform any further actions, the shutdown will be initiated immediately!
</Entry>
<Entry key="LockScreen_Title">
SEB LOCKED
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Contraseña:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Nombre de usuario:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Nombre de usuario (si se requiere):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Por favor, introduzca las credenciales necesarias para establecer una conexión con la red inalámbrica "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Autenticación de red necesaria
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Cancel
</Entry>
@ -192,6 +207,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
La última sesión con la configuración activa o la URL de inicio no se ha cerrado correctamente. Por favor, introduzca la contraseña correcta para desbloquear SEB.
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
Permitir temporalmente el estado de las teclas adhesivas. Esto sólo se aplica a la sesión que se está ejecutando en ese momento.
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
Se ha detectado un estado prohibido de las teclas adhesivas. Para desbloquear SEB, seleccione una de las opciones disponibles e introduzca la contraseña de desbloqueo correcta.
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
Finalizar Safe Exam Browser. ADVERTENCIA: No habrá posibilidad de guardar datos o realizar ninguna otra acción, ¡el cierre se iniciará inmediatamente!
</Entry>
<Entry key="LockScreen_Title">
SEB BLOQUEADO
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Koosta
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Parool:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Kasutajanimi:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Kasutajanimi (kui see on nõutav):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Palun sisestage vajalikud volitused, et luua ühendus traadita võrguga "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Võrguautentimine nõutav
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Tühista
</Entry>
@ -192,6 +207,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
Viimast seanssi hetkel aktiivse seadistuse või algus-URL-iga ei suletud korrektselt! Palun sisestage SEB'i avamiseks õige parool.
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
Lubab ajutiselt kleepuvate võtmete oleku. See kehtib ainult hetkel käimasoleva seansi kohta!
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
Avastati kleepuvate võtmete keelatud olek. SEB avamiseks valige üks olemasolevatest võimalustest ja sisestage õige avamisparool.
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
Sulge Safe Exam Browser. HOIATUS: andmete salvestamise ega edasiste toimingute tegemise võimalust ei ole, sulgemine käivitatakse kohe!
</Entry>
<Entry key="LockScreen_Title">
SEB ON LUKUSTATUD
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Mot de passe:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Nom d'utilisateur:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Nom d'utilisateur (si requis):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Veuillez saisir les informations d'identification requises pour établir une connexion au réseau sans fil "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Authentification réseau requise
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Annuler
</Entry>
@ -192,6 +207,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
La dernière session avec la configuration ou l'URL de démarrage actuellement active n'a pas été terminée correctement! Afin de déverrouiller SEB, veuillez entrer le mot de passe de déverrouillage correct.
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
Autoriser temporairement l'état des clés autocollantes. Cela ne s'applique qu'à la session en cours!
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
Un état d'interdiction des clés autocollantes a été détecté. Pour déverrouiller SEB, veuillez sélectionner l'une des options disponibles et saisir le mot de passe de déverrouillage correct.
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
Terminate Safe Exam Browser. WARNING: There will be no possibility to save data or perform any further actions, the shutdown will be initiated immediately!
</Entry>
<Entry key="LockScreen_Title">
SEB VEROUILLE
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Bangun
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Kata sandi:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Nama pengguna:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Nama pengguna (jika diperlukan):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Masukkan kredensial yang diperlukan untuk membuat sambungan ke jaringan nirkabel "%%_NAMA_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Diperlukan Otentikasi Jaringan
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Batal
</Entry>
@ -192,6 +207,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
Sesi terakhir dengan konfigurasi yang sedang aktif atau URL awal tidak diakhiri dengan benar! Silakan masukkan kata sandi yang benar untuk membuka kunci SEB.
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
Mengizinkan status tombol tempel untuk sementara waktu. Ini hanya berlaku untuk sesi yang sedang berjalan!
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
Kondisi kunci tempel yang dilarang telah terdeteksi. Untuk membuka kunci SEB, pilih salah satu opsi yang tersedia dan masukkan kata sandi buka kunci yang benar.
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
Menghentikan Safe Exam Browser. PERINGATAN: Tidak akan ada kemungkinan untuk menyimpan data atau melakukan tindakan lebih lanjut, penghentian akan segera dilakukan!
</Entry>
<Entry key="LockScreen_Title">
SEB TERKUNCI
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Password:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Nome utente:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Nome utente (se richiesto):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Inserire le credenziali richieste per stabilire una connessione alla rete wireless "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
È richiesta l'autenticazione di rete
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Annulla
</Entry>
@ -192,6 +207,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
L'ultima sessione con la configurazione o l'URL di avvio attualmente attiva non è stata terminata correttamente! Per sbloccare SEB, inserisci la password di sblocco corretta.
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
Consente temporaneamente lo stato dei tasti adesivi. Questo vale solo per la sessione in corso!
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
È stato rilevato uno stato non consentito dei tasti adesivi. Per sbloccare SEB, selezionare una delle opzioni disponibili e inserire la password di sblocco corretta.
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
Terminare Safe Exam Browser. ATTENZIONE: Non sarà possibile salvare i dati o eseguire ulteriori azioni, la chiusura verrà avviata immediatamente!
</Entry>
<Entry key="LockScreen_Title">
SEB BLOCCATO
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Wachtwoord:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Gebruikersnaam:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Gebruikersnaam (indien vereist):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Voer de vereiste gegevens in om verbinding te maken met het draadloze netwerk “%%_NAME_%%”.
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Netwerkverificatie vereist
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Annuleren
</Entry>
@ -192,6 +207,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
De laatste sessie met de huidige actieve configuratie of start URL is niet goed afgesloten! Voer het juiste wachtwoord in om SEB te ontgrendelen.
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
Tijdelijk de toestand van de sticky keys toestaan. Dit geldt alleen voor de huidige sessie!
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
Er is een verboden toestand van de sticky keys gedetecteerd. Om SEB te ontgrendelen, selecteert u een van de beschikbare opties en voert u het juiste ontgrendelwachtwoord in.
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
Beëindig de Safe Exam Browser. WAARSCHUWING: er is geen mogelijkheid om gegevens op te slaan of verdere acties uit te voeren, de browser wordt onmiddellijk afgesloten!
</Entry>
<Entry key="LockScreen_Title">
SEB VERGRENDELD
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
Строить
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
Пароль:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
Имя пользователя:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
Имя пользователя (если требуется):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
Введите необходимые учетные данные для установления соединения с беспроводной сетью "%%_NAME_%%".
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
Требуется сетевая аутентификация
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
Отмена
</Entry>
@ -191,6 +206,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
Последний сеанс с текущей активной конфигурацией или URL-адресом запуска не был завершен должным образом! Пожалуйста, введите правильный пароль, чтобы разблокировать SEB.
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
Временно разрешить состояние липких ключей. Это относится только к текущему сеансу!
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
Обнаружено запрещенное состояние липких клавиш. Чтобы разблокировать SEB, пожалуйста, выберите один из доступных вариантов и введите правильный пароль разблокировки.
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
Завершить работу Safe Exam Browser. ВНИМАНИЕ: Сохранить данные или выполнить какие-либо дальнейшие действия будет невозможно, завершение работы будет инициировано немедленно!
</Entry>
<Entry key="LockScreen_Title">
SEB ЗАБЛОКИРОВАН
</Entry>

View file

@ -99,6 +99,21 @@
<Entry key="Build">
生成
</Entry>
<Entry key="CredentialsDialog_PasswordLabel">
密码:
</Entry>
<Entry key="CredentialsDialog_UsernameLabel">
用户名:
</Entry>
<Entry key="CredentialsDialog_UsernameOptionalLabel">
用户名(如需要):
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkMessage">
请输入与无线网络 "%%_NAME_%%" 建立连接所需的凭证。
</Entry>
<Entry key="CredentialsDialog_WirelessNetworkTitle">
需要网络验证
</Entry>
<Entry key="ExamSelectionDialog_Cancel">
取消
</Entry>
@ -178,7 +193,7 @@
检测到禁止的显示配置。 要解锁 SEB请选择可用选项之一并输入正确的解锁密码。
</Entry>
<Entry key="LockScreen_DisplayConfigurationTerminateOption">
终止安全考试浏览器。 警告:将无法保存数据或执行任何进一步操作,将立即启动关机!
终止安全考试浏览器。警告:将无法保存数据或执行任何进一步操作,将立即启动关机!
</Entry>
<Entry key="LockScreen_EaseOfAccessContinueOption">
暂时允许轻松访问的配置。这只适用于当前运行的会话!
@ -192,6 +207,15 @@
<Entry key="LockScreen_SessionIntegrityMessage">
当前激活的配置或启动URL的最后一个会话没有被正确终止! 请输入正确的密码以解锁SEB。
</Entry>
<Entry key="LockScreen_StickyKeysContinueOption">
暂时允许粘性按键的状态。这仅适用于当前运行的会话!
</Entry>
<Entry key="LockScreen_StickyKeysMessage">
已检测到粘滞键处于禁用状态。要解锁 SEB请选择一个可用选项并输入正确的解锁密码。
</Entry>
<Entry key="LockScreen_StickyKeysTerminateOption">
终止安全考试浏览器。警告:将没有可能保存数据或执行任何进一步的行动,关闭将立即启动!
</Entry>
<Entry key="LockScreen_Title">
防作弊考试专用浏览器已锁定
</Entry>

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.SystemComponents.Contracts
namespace SafeExamBrowser.Monitoring.Contracts
{
/// <summary>
/// Provides functionality related to remote session detection.

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.SystemComponents.Contracts
namespace SafeExamBrowser.Monitoring.Contracts
{
/// <summary>
/// Provides functionality related to virtual machine detection.

View file

@ -65,13 +65,17 @@
<Compile Include="Display\IDisplayMonitor.cs" />
<Compile Include="Display\ValidationResult.cs" />
<Compile Include="IClipboard.cs" />
<Compile Include="IRemoteSessionDetector.cs" />
<Compile Include="IVirtualMachineDetector.cs" />
<Compile Include="Keyboard\IKeyboardInterceptor.cs" />
<Compile Include="Mouse\IMouseInterceptor.cs" />
<Compile Include="Applications\InitializationResult.cs" />
<Compile Include="Applications\IApplicationMonitor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="System\Events\SentinelEventArgs.cs" />
<Compile Include="System\Events\SentinelEventHandler.cs" />
<Compile Include="System\Events\SessionChangedEventHandler.cs" />
<Compile Include="System\ISystemMonitor.cs" />
<Compile Include="System\ISystemSentinel.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">

View file

@ -0,0 +1,21 @@
/*
* 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/.
*/
namespace SafeExamBrowser.Monitoring.Contracts.System.Events
{
/// <summary>
/// The event arguments used for the <see cref="SentinelEventHandler"/> fired by the <see cref="ISystemSentinel"/>.
/// </summary>
public class SentinelEventArgs
{
/// <summary>
/// Indicates whether a configuration or state change shall be allowed.
/// </summary>
public bool Allow { get; set; }
}
}

View file

@ -0,0 +1,15 @@
/*
* 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/.
*/
namespace SafeExamBrowser.Monitoring.Contracts.System.Events
{
/// <summary>
/// The default handler for events of the <see cref="ISystemSentinel"/>. Normally indicates that a configuration or state has changed.
/// </summary>
public delegate void SentinelEventHandler(SentinelEventArgs args);
}

View file

@ -1,33 +0,0 @@
/*
* 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.Monitoring.Contracts.System.Events;
namespace SafeExamBrowser.Monitoring.Contracts.System
{
/// <summary>
/// Monitors the operating system, e.g. for user account related events.
/// </summary>
public interface ISystemMonitor
{
/// <summary>
/// Event fired when the active user session has changed.
/// </summary>
event SessionChangedEventHandler SessionChanged;
/// <summary>
/// Starts the monitoring.
/// </summary>
void Start();
/// <summary>
/// Stops the monitoring.
/// </summary>
void Stop();
}
}

View file

@ -0,0 +1,88 @@
/*
* 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.Monitoring.Contracts.System.Events;
namespace SafeExamBrowser.Monitoring.Contracts.System
{
/// <summary>
/// Provides the possibility to suppress and surveil various system components and functionalities.
/// </summary>
public interface ISystemSentinel
{
/// <summary>
/// Event fired when the cursor configuration has changed.
/// </summary>
event SentinelEventHandler CursorChanged;
/// <summary>
/// Event fired when the ease of access configuration has changed.
/// </summary>
event SentinelEventHandler EaseOfAccessChanged;
/// <summary>
/// Event fired when the active user session has changed.
/// </summary>
event SessionChangedEventHandler SessionChanged;
/// <summary>
/// Event fired when the sticky keys state has changed.
/// </summary>
event SentinelEventHandler StickyKeysChanged;
/// <summary>
/// Attempts to disable the sticky keys. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool DisableStickyKeys();
/// <summary>
/// Attempts to enable the sticky keys. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool EnableStickyKeys();
/// <summary>
/// Attempts to revert the sticky keys to their initial state. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool RevertStickyKeys();
/// <summary>
/// Starts monitoring the cursor configuration.
/// </summary>
void StartMonitoringCursors();
/// <summary>
/// Starts monitoring the ease of access configuration.
/// </summary>
void StartMonitoringEaseOfAccess();
/// <summary>
/// Starts monitoring the sticky keys state.
/// </summary>
void StartMonitoringStickyKeys();
/// <summary>
/// Starts monitoring the system events.
/// </summary>
void StartMonitoringSystemEvents();
/// <summary>
/// Stops all monitoring operations.
/// </summary>
void StopMonitoring();
/// <summary>
/// Verifies the cursor configuration. Returns <c>true</c> if permitted, otherwise <c>false</c>.
/// </summary>
bool VerifyCursors();
/// <summary>
/// Verifies the ease of access configuration. Returns <c>true</c> if permitted, otherwise <c>false</c>.
/// </summary>
bool VerifyEaseOfAccess();
}
}

View file

@ -98,7 +98,13 @@ namespace SafeExamBrowser.Monitoring.Applications
if (activeWindow != default && TryGetProcessFor(activeWindow, out var process))
{
application = new ActiveApplication(process, new Window { Handle = activeWindow.Handle, Title = activeWindow.Title });
var window = new Window
{
Handle = activeWindow.Handle,
Title = nativeMethods.GetWindowTitle(activeWindow.Handle)
};
application = new ActiveApplication(process, window);
}
return application != default;
@ -242,7 +248,6 @@ namespace SafeExamBrowser.Monitoring.Applications
{
var isClient = true;
var isRuntime = true;
var isWebView = true;
isClient &= process.Name == "SafeExamBrowser.Client.exe";
isClient &= process.OriginalName == "SafeExamBrowser.Client.exe";
@ -250,16 +255,12 @@ namespace SafeExamBrowser.Monitoring.Applications
isRuntime &= process.Name == "SafeExamBrowser.exe";
isRuntime &= process.OriginalName == "SafeExamBrowser.exe";
isWebView &= process.Name == "msedgewebview2.exe";
isWebView &= process.OriginalName == "msedgewebview2.exe";
#if !DEBUG
isClient &= process.Signature == "2bc82fe8e56a39f96bc6c4b91d6703a0379b76a2";
isRuntime &= process.Signature == "2bc82fe8e56a39f96bc6c4b91d6703a0379b76a2";
isWebView &= process.Signature == "a4baabd12432ab9c7c297385260e95c3dae83bf2";
#endif
return isClient || isRuntime || isWebView;
return isClient || isRuntime;
}
private void Close(Window window)

View file

@ -8,9 +8,9 @@
using System.Windows.Forms;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
namespace SafeExamBrowser.SystemComponents
namespace SafeExamBrowser.Monitoring
{
public class RemoteSessionDetector : IRemoteSessionDetector
{

View file

@ -67,7 +67,13 @@
<Compile Include="Mouse\MouseInterceptor.cs" />
<Compile Include="Applications\ApplicationMonitor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="System\SystemMonitor.cs" />
<Compile Include="RemoteSessionDetector.cs" />
<Compile Include="System\Components\Cursors.cs" />
<Compile Include="System\Components\EaseOfAccess.cs" />
<Compile Include="System\Components\StickyKeys.cs" />
<Compile Include="System\Components\SystemEvents.cs" />
<Compile Include="System\SystemSentinel.cs" />
<Compile Include="VirtualMachineDetector.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">

View file

@ -0,0 +1,152 @@
/*
* 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 System.Linq;
using System.Threading.Tasks;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
namespace SafeExamBrowser.Monitoring.System.Components
{
internal class Cursors
{
private static readonly string SYSTEM_PATH = $@"{Environment.ExpandEnvironmentVariables("%SystemRoot%")}\Cursors\";
private static readonly string USER_PATH = $@"{Environment.ExpandEnvironmentVariables("%LocalAppData%")}\Microsoft\Windows\Cursors\";
private readonly ILogger logger;
private readonly IRegistry registry;
internal event SentinelEventHandler CursorChanged;
internal Cursors(ILogger logger, IRegistry registry)
{
this.logger = logger;
this.registry = registry;
}
internal void StartMonitoring()
{
registry.ValueChanged += Registry_ValueChanged;
if (registry.TryGetNames(RegistryValue.UserHive.Cursors_Key, out var names))
{
foreach (var name in names)
{
registry.StartMonitoring(RegistryValue.UserHive.Cursors_Key, name);
}
logger.Info("Started monitoring cursors.");
}
else
{
logger.Warn("Failed to start monitoring cursor registry values!");
}
}
internal void StopMonitoring()
{
registry.ValueChanged -= Registry_ValueChanged;
if (registry.TryGetNames(RegistryValue.UserHive.Cursors_Key, out var names))
{
foreach (var name in names)
{
registry.StopMonitoring(RegistryValue.UserHive.Cursors_Key, name);
}
logger.Info("Stopped monitoring cursors.");
}
else
{
logger.Warn("Failed to stop monitoring cursor registry values!");
}
}
internal bool Verify()
{
logger.Info($"Starting cursor verification...");
var success = registry.TryGetNames(RegistryValue.UserHive.Cursors_Key, out var cursors);
if (success)
{
foreach (var cursor in cursors.Where(c => !string.IsNullOrWhiteSpace(c)))
{
success &= VerifyCursor(cursor);
}
if (success)
{
logger.Info("Cursor configuration successfully verified.");
}
else
{
logger.Warn("Cursor configuration is compromised!");
}
}
else
{
logger.Error("Failed to verify cursor configuration!");
}
return success;
}
private void Registry_ValueChanged(string key, string name, object oldValue, object newValue)
{
if (key == RegistryValue.UserHive.Cursors_Key)
{
HandleCursorChange(key, name, oldValue, newValue);
}
}
private void HandleCursorChange(string key, string name, object oldValue, object newValue)
{
var args = new SentinelEventArgs();
logger.Warn($@"The cursor registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'!");
Task.Run(() => CursorChanged?.Invoke(args)).ContinueWith((_) =>
{
if (args.Allow)
{
registry.StopMonitoring(key, name);
}
});
}
private bool VerifyCursor(string cursor)
{
var success = true;
success &= registry.TryRead(RegistryValue.UserHive.Cursors_Key, cursor, out var value);
success &= !(value is string) || (value is string path && (string.IsNullOrWhiteSpace(path) || IsValidCursorPath(path)));
if (!success)
{
if (value != default)
{
logger.Warn($"Configuration of cursor '{cursor}' is compromised: '{value}'!");
}
else
{
logger.Warn($"Failed to verify configuration of cursor '{cursor}'!");
}
}
return success;
}
private bool IsValidCursorPath(string path)
{
return path.StartsWith(USER_PATH, StringComparison.OrdinalIgnoreCase) || path.StartsWith(SYSTEM_PATH, StringComparison.OrdinalIgnoreCase);
}
}
}

View file

@ -0,0 +1,95 @@
/*
* 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.Tasks;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
namespace SafeExamBrowser.Monitoring.System.Components
{
internal class EaseOfAccess
{
private readonly ILogger logger;
private readonly IRegistry registry;
internal event SentinelEventHandler EaseOfAccessChanged;
internal EaseOfAccess(ILogger logger, IRegistry registry)
{
this.logger = logger;
this.registry = registry;
}
internal void StartMonitoring()
{
registry.ValueChanged += Registry_ValueChanged;
registry.StartMonitoring(RegistryValue.MachineHive.EaseOfAccess_Key, RegistryValue.MachineHive.EaseOfAccess_Name);
logger.Info("Started monitoring ease of access.");
}
internal void StopMonitoring()
{
registry.ValueChanged -= Registry_ValueChanged;
registry.StopMonitoring(RegistryValue.MachineHive.EaseOfAccess_Key, RegistryValue.MachineHive.EaseOfAccess_Name);
logger.Info("Stopped monitoring ease of access.");
}
internal bool Verify()
{
logger.Info($"Starting ease of access verification...");
var success = registry.TryRead(RegistryValue.MachineHive.EaseOfAccess_Key, RegistryValue.MachineHive.EaseOfAccess_Name, out var value);
if (success)
{
if (value is string s && string.IsNullOrWhiteSpace(s))
{
logger.Info("Ease of access configuration successfully verified.");
}
else
{
logger.Warn($"Ease of access configuration is compromised: '{value}'!");
success = false;
}
}
else
{
success = true;
logger.Info("Ease of access configuration successfully verified (value does not exist).");
}
return success;
}
private void Registry_ValueChanged(string key, string name, object oldValue, object newValue)
{
if (key == RegistryValue.MachineHive.EaseOfAccess_Key)
{
HandleEaseOfAccessChange(key, name, oldValue, newValue);
}
}
private void HandleEaseOfAccessChange(string key, string name, object oldValue, object newValue)
{
var args = new SentinelEventArgs();
logger.Warn($@"The ease of access registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'!");
Task.Run(() => EaseOfAccessChanged?.Invoke(args)).ContinueWith((_) =>
{
if (args.Allow)
{
registry.StopMonitoring(key, name);
}
});
}
}
}

View file

@ -0,0 +1,175 @@
/*
* 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.Tasks;
using System.Timers;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Monitoring.System.Components
{
internal class StickyKeys
{
private readonly ILogger logger;
private readonly INativeMethods nativeMethods;
private readonly Timer timer;
private IStickyKeysState original;
internal event SentinelEventHandler Changed;
internal StickyKeys(ILogger logger, INativeMethods nativeMethods)
{
this.logger = logger;
this.nativeMethods = nativeMethods;
this.timer = new Timer();
}
internal bool Disable()
{
var success = nativeMethods.TryGetStickyKeys(out var state);
if (success)
{
success = nativeMethods.DisableStickyKeys();
if (success)
{
original = state;
logger.Info($"Disabled sticky keys (original state: {ToString(state)}).");
}
else
{
logger.Error($"Failed to disable sticky keys (original state: {ToString(state)})!");
}
}
else
{
logger.Error("Failed to retrieve sticky keys state!");
}
return success;
}
internal bool Enable()
{
var success = nativeMethods.TryGetStickyKeys(out var state);
if (success)
{
success = nativeMethods.EnableStickyKeys();
if (success)
{
original = state;
logger.Info($"Enabled sticky keys (original state: {ToString(state)}).");
}
else
{
logger.Error($"Failed to enable sticky keys (original state: {ToString(state)})!");
}
}
else
{
logger.Error("Failed to retrieve sticky keys state!");
}
return success;
}
internal bool Revert()
{
var success = true;
if (original != default)
{
success = nativeMethods.TrySetStickyKeys(original);
if (success)
{
logger.Info($"Successfully reverted sticky keys (original state: {ToString(original)}).");
}
else
{
logger.Error($"Failed to revert sticky keys (original state: {ToString(original)})!");
}
}
original = default;
return success;
}
internal void StartMonitoring()
{
timer.AutoReset = true;
timer.Elapsed += Timer_Elapsed;
timer.Interval = 1000;
timer.Start();
logger.Info("Started monitoring sticky keys.");
}
internal void StopMonitoring()
{
timer.Elapsed -= Timer_Elapsed;
timer.Stop();
logger.Info("Stopped monitoring sticky keys.");
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (nativeMethods.TryGetStickyKeys(out var state))
{
if (state.IsEnabled || state.IsHotkeyActive)
{
HandleStickyKeysChange(state);
}
}
else
{
logger.Error("Failed to monitor sticky keys state!");
}
}
private void HandleStickyKeysChange(IStickyKeysState state)
{
var args = new SentinelEventArgs();
logger.Warn($"The sticky keys state has changed: {ToString(state)}.");
Task.Run(() => Changed?.Invoke(args)).ContinueWith((_) =>
{
if (args.Allow)
{
StopMonitoring();
}
});
if (nativeMethods.DisableStickyKeys())
{
logger.Info($"Disabled sticky keys.");
}
else
{
logger.Error($"Failed to disable sticky keys!");
}
}
private string ToString(IStickyKeysState state)
{
var availability = state.IsAvailable ? "available" : "unavailable";
var status = state.IsEnabled ? "enabled" : "disabled";
var hotkey = state.IsHotkeyActive ? "active" : "inactive";
return $"functionality {availability} and {status}, hotkey {hotkey}";
}
}
}

View file

@ -10,46 +10,47 @@ using System;
using System.Threading.Tasks;
using Microsoft.Win32;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
namespace SafeExamBrowser.Monitoring.System
namespace SafeExamBrowser.Monitoring.System.Components
{
public class SystemMonitor : ISystemMonitor
internal class SystemEvents
{
private readonly ILogger logger;
public event SessionChangedEventHandler SessionChanged;
internal event SessionChangedEventHandler SessionChanged;
public SystemMonitor(ILogger logger)
internal SystemEvents(ILogger logger)
{
this.logger = logger;
}
public void Start()
internal void StartMonitoring()
{
SystemEvents.EventsThreadShutdown += SystemEvents_EventsThreadShutdown;
SystemEvents.InstalledFontsChanged += SystemEvents_InstalledFontsChanged;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
SystemEvents.SessionEnded += SystemEvents_SessionEnded;
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
SystemEvents.SessionSwitch += SystemEvents_SessionChanged;
SystemEvents.TimeChanged += SystemEvents_TimeChanged;
SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
logger.Info("Started monitoring the operating system.");
Microsoft.Win32.SystemEvents.EventsThreadShutdown += SystemEvents_EventsThreadShutdown;
Microsoft.Win32.SystemEvents.InstalledFontsChanged += SystemEvents_InstalledFontsChanged;
Microsoft.Win32.SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
Microsoft.Win32.SystemEvents.SessionEnded += SystemEvents_SessionEnded;
Microsoft.Win32.SystemEvents.SessionEnding += SystemEvents_SessionEnding;
Microsoft.Win32.SystemEvents.SessionSwitch += SystemEvents_SessionChanged;
Microsoft.Win32.SystemEvents.TimeChanged += SystemEvents_TimeChanged;
Microsoft.Win32.SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
logger.Info("Started monitoring system events.");
}
public void Stop()
internal void StopMonitoring()
{
SystemEvents.EventsThreadShutdown -= SystemEvents_EventsThreadShutdown;
SystemEvents.InstalledFontsChanged -= SystemEvents_InstalledFontsChanged;
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
SystemEvents.SessionEnded -= SystemEvents_SessionEnded;
SystemEvents.SessionEnding -= SystemEvents_SessionEnding;
SystemEvents.SessionSwitch -= SystemEvents_SessionChanged;
SystemEvents.TimeChanged -= SystemEvents_TimeChanged;
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
logger.Info("Stopped monitoring the operating system.");
Microsoft.Win32.SystemEvents.EventsThreadShutdown -= SystemEvents_EventsThreadShutdown;
Microsoft.Win32.SystemEvents.InstalledFontsChanged -= SystemEvents_InstalledFontsChanged;
Microsoft.Win32.SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
Microsoft.Win32.SystemEvents.SessionEnded -= SystemEvents_SessionEnded;
Microsoft.Win32.SystemEvents.SessionEnding -= SystemEvents_SessionEnding;
Microsoft.Win32.SystemEvents.SessionSwitch -= SystemEvents_SessionChanged;
Microsoft.Win32.SystemEvents.TimeChanged -= SystemEvents_TimeChanged;
Microsoft.Win32.SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
logger.Info("Stopped monitoring system events.");
}
private void SystemEvents_EventsThreadShutdown(object sender, EventArgs e)

View file

@ -0,0 +1,95 @@
/*
* 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.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.System;
using SafeExamBrowser.Monitoring.Contracts.System.Events;
using SafeExamBrowser.Monitoring.System.Components;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Monitoring.System
{
public class SystemSentinel : ISystemSentinel
{
private readonly Cursors cursors;
private readonly EaseOfAccess easeOfAccess;
private readonly StickyKeys stickyKeys;
private readonly SystemEvents systemEvents;
public event SentinelEventHandler CursorChanged;
public event SentinelEventHandler EaseOfAccessChanged;
public event SentinelEventHandler StickyKeysChanged;
public event SessionChangedEventHandler SessionChanged;
public SystemSentinel(ILogger logger, INativeMethods nativeMethods, IRegistry registry)
{
cursors = new Cursors(logger, registry);
easeOfAccess = new EaseOfAccess(logger, registry);
stickyKeys = new StickyKeys(logger, nativeMethods);
systemEvents = new SystemEvents(logger);
}
public bool DisableStickyKeys()
{
return stickyKeys.Disable();
}
public bool EnableStickyKeys()
{
return stickyKeys.Enable();
}
public bool RevertStickyKeys()
{
return stickyKeys.Revert();
}
public void StartMonitoringCursors()
{
cursors.CursorChanged += (args) => CursorChanged?.Invoke(args);
cursors.StartMonitoring();
}
public void StartMonitoringEaseOfAccess()
{
easeOfAccess.EaseOfAccessChanged += (args) => EaseOfAccessChanged?.Invoke(args);
easeOfAccess.StartMonitoring();
}
public void StartMonitoringStickyKeys()
{
stickyKeys.Changed += (args) => StickyKeysChanged?.Invoke(args);
stickyKeys.StartMonitoring();
}
public void StartMonitoringSystemEvents()
{
systemEvents.SessionChanged += () => SessionChanged?.Invoke();
systemEvents.StartMonitoring();
}
public void StopMonitoring()
{
cursors.StopMonitoring();
easeOfAccess.StopMonitoring();
stickyKeys.StopMonitoring();
systemEvents.StopMonitoring();
}
public bool VerifyCursors()
{
return cursors.Verify();
}
public bool VerifyEaseOfAccess()
{
return easeOfAccess.Verify();
}
}
}

View file

@ -6,12 +6,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Linq;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
namespace SafeExamBrowser.SystemComponents
namespace SafeExamBrowser.Monitoring
{
public class VirtualMachineDetector : IVirtualMachineDetector
{
@ -105,7 +107,6 @@ namespace SafeExamBrowser.SystemComponents
{
var isVirtualRegistry = false;
isVirtualRegistry |= HasHistoricVirtualMachineHardwareConfiguration();
isVirtualRegistry |= HasLocalVirtualMachineDeviceCache();
return isVirtualRegistry;
@ -134,49 +135,9 @@ namespace SafeExamBrowser.SystemComponents
return isVirtualSystem;
}
private bool HasHistoricVirtualMachineHardwareConfiguration()
{
var hasHistoricConfiguration = false;
if (registry.TryGetSubKeys(RegistryValue.MachineHive.HardwareConfig_Key, out var hardwareConfigSubkeys))
{
foreach (var configId in hardwareConfigSubkeys)
{
var hardwareConfigKey = $@"{RegistryValue.MachineHive.HardwareConfig_Key}\{configId}";
var computerIdsKey = $@"{hardwareConfigKey}\ComputerIds";
var success = true;
success &= registry.TryRead(hardwareConfigKey, "BIOSVendor", out var biosVendor);
success &= registry.TryRead(hardwareConfigKey, "BIOSVersion", out var biosVersion);
success &= registry.TryRead(hardwareConfigKey, "SystemManufacturer", out var systemManufacturer);
success &= registry.TryRead(hardwareConfigKey, "SystemProductName", out var systemProductName);
if (success)
{
var biosInfo = $"{(string) biosVendor} {(string) biosVersion}";
hasHistoricConfiguration |= IsVirtualSystem(biosInfo, (string) systemManufacturer, (string) systemProductName);
if (registry.TryGetNames(computerIdsKey, out var computerIdNames))
{
foreach (var computerIdName in computerIdNames)
{
if (registry.TryRead(computerIdsKey, computerIdName, out var computerSummary))
{
hasHistoricConfiguration |= IsVirtualSystem((string) computerSummary, (string) systemManufacturer, (string) systemProductName);
}
}
}
}
}
}
return hasHistoricConfiguration;
}
private bool HasLocalVirtualMachineDeviceCache()
{
var deviceName = System.Environment.GetEnvironmentVariable("COMPUTERNAME");
var deviceName = Environment.GetEnvironmentVariable("COMPUTERNAME");
var hasDeviceCache = false;
var hasDeviceCacheKeys = registry.TryGetSubKeys(RegistryValue.UserHive.DeviceCache_Key, out var deviceCacheKeys);

View file

@ -1,255 +0,0 @@
/*
* 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 System.IO;
using System.Reflection;
using System.Threading;
using System.Windows;
using Microsoft.Web.WebView2.Wpf;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Contracts.Events.Proctoring;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Proctoring;
namespace SafeExamBrowser.Proctoring.JitsiMeet
{
internal class JitsiMeetImplementation : ProctoringImplementation
{
private readonly AppConfig appConfig;
private readonly IFileSystem fileSystem;
private readonly IModuleLogger logger;
private readonly ProctoringSettings settings;
private readonly IText text;
private readonly IUserInterfaceFactory uiFactory;
private ProctoringControl control;
private string filePath;
private WindowVisibility initialVisibility;
private IProctoringWindow window;
internal override string Name => nameof(JitsiMeet);
internal JitsiMeetImplementation(
AppConfig appConfig,
IFileSystem fileSystem,
IModuleLogger logger,
ProctoringSettings settings,
IText text,
IUserInterfaceFactory uiFactory)
{
this.appConfig = appConfig;
this.fileSystem = fileSystem;
this.logger = logger;
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
}
internal override void Initialize()
{
var start = true;
initialVisibility = settings.WindowVisibility;
settings.JitsiMeet.ServerUrl = Sanitize(settings.JitsiMeet.ServerUrl);
start &= !string.IsNullOrWhiteSpace(settings.JitsiMeet.RoomName);
start &= !string.IsNullOrWhiteSpace(settings.JitsiMeet.ServerUrl);
if (start)
{
logger.Info($"Initialized proctoring: All settings are valid, starting automatically...");
Start();
}
else
{
ShowNotificationInactive();
logger.Info($"Initialized proctoring: Not all settings are valid or a server session is active, not starting automatically.");
}
}
internal override void ProctoringConfigurationReceived(bool allowChat, bool receiveAudio, bool receiveVideo)
{
logger.Info("Proctoring configuration received.");
settings.JitsiMeet.AllowChat = allowChat;
settings.JitsiMeet.ReceiveAudio = receiveAudio;
settings.JitsiMeet.ReceiveVideo = receiveVideo;
if (allowChat || receiveVideo)
{
settings.WindowVisibility = WindowVisibility.AllowToHide;
}
else
{
settings.WindowVisibility = initialVisibility;
}
Stop();
Start();
logger.Info($"Successfully updated configuration: {nameof(allowChat)}={allowChat}, {nameof(receiveAudio)}={receiveAudio}, {nameof(receiveVideo)}={receiveVideo}.");
}
internal override void ProctoringInstructionReceived(InstructionEventArgs args)
{
if (args is JitsiMeetInstruction instruction)
{
logger.Info($"Proctoring instruction received: {instruction.Method}");
if (instruction.Method == InstructionMethod.Join)
{
settings.JitsiMeet.RoomName = instruction.RoomName;
settings.JitsiMeet.ServerUrl = instruction.ServerUrl;
settings.JitsiMeet.Token = instruction.Token;
Stop();
Start();
}
else
{
Stop();
}
logger.Info("Successfully processed instruction.");
}
}
internal override void Start()
{
Application.Current.Dispatcher.Invoke(() =>
{
try
{
var content = LoadContent(settings);
filePath = Path.Combine(appConfig.TemporaryDirectory, $"{Path.GetRandomFileName()}_index.html");
fileSystem.Save(content, filePath);
control = new ProctoringControl(logger.CloneFor(nameof(ProctoringControl)), settings);
control.CreationProperties = new CoreWebView2CreationProperties { UserDataFolder = appConfig.TemporaryDirectory };
control.EnsureCoreWebView2Async().ContinueWith(_ =>
{
control.Dispatcher.Invoke(() =>
{
control.CoreWebView2.Navigate(filePath);
});
});
window = uiFactory.CreateProctoringWindow(control);
window.SetTitle(settings.JitsiMeet.Enabled ? settings.JitsiMeet.Subject : "");
window.Show();
if (settings.WindowVisibility == WindowVisibility.AllowToShow || settings.WindowVisibility == WindowVisibility.Hidden)
{
window.Hide();
}
ShowNotificationActive();
logger.Info("Started proctoring.");
}
catch (Exception e)
{
logger.Error($"Failed to start proctoring! Reason: {e.Message}", e);
}
});
}
internal override void Stop()
{
if (control != default && window != default)
{
control.Dispatcher.Invoke(() =>
{
control.ExecuteScriptAsync("api.executeCommand('hangup'); api.dispose();");
Thread.Sleep(2000);
window.Close();
control = default;
window = default;
fileSystem.Delete(filePath);
ShowNotificationInactive();
logger.Info("Stopped proctoring.");
});
}
}
internal override void Terminate()
{
Stop();
logger.Info("Terminated proctoring.");
}
protected override void ActivateNotification()
{
if (settings.WindowVisibility == WindowVisibility.Visible)
{
window?.BringToForeground();
}
else if (settings.WindowVisibility == WindowVisibility.AllowToHide || settings.WindowVisibility == WindowVisibility.AllowToShow)
{
window?.Toggle();
}
}
private string LoadContent(ProctoringSettings settings)
{
var assembly = Assembly.GetAssembly(typeof(ProctoringController));
var path = $"{typeof(ProctoringController).Namespace}.JitsiMeet.index.html";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
{
var html = reader.ReadToEnd();
html = html.Replace("%%_ALLOW_CHAT_%%", settings.JitsiMeet.AllowChat ? "chat" : "");
html = html.Replace("%%_ALLOW_CLOSED_CAPTIONS_%%", settings.JitsiMeet.AllowClosedCaptions ? "closedcaptions" : "");
html = html.Replace("%%_ALLOW_RAISE_HAND_%%", settings.JitsiMeet.AllowRaiseHand ? "raisehand" : "");
html = html.Replace("%%_ALLOW_RECORDING_%%", settings.JitsiMeet.AllowRecording ? "recording" : "");
html = html.Replace("%%_ALLOW_TILE_VIEW", settings.JitsiMeet.AllowTileView ? "tileview" : "");
html = html.Replace("'%_AUDIO_MUTED_%'", settings.JitsiMeet.AudioMuted && settings.WindowVisibility != WindowVisibility.Hidden ? "true" : "false");
html = html.Replace("'%_AUDIO_ONLY_%'", settings.JitsiMeet.AudioOnly ? "true" : "false");
html = html.Replace("'%_VIDEO_MUTED_%'", settings.JitsiMeet.VideoMuted && settings.WindowVisibility != WindowVisibility.Hidden ? "true" : "false");
return html;
}
}
private string Sanitize(string serverUrl)
{
return serverUrl?.Replace($"{Uri.UriSchemeHttp}{Uri.SchemeDelimiter}", "").Replace($"{Uri.UriSchemeHttps}{Uri.SchemeDelimiter}", "");
}
private void ShowNotificationActive()
{
CanActivate = true;
IconResource = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Proctoring_Active.xaml") };
Tooltip = text.Get(TextKey.Notification_ProctoringActiveTooltip);
InvokeNotificationChanged();
}
private void ShowNotificationInactive()
{
CanActivate = false;
IconResource = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/Proctoring_Inactive.xaml") };
Tooltip = text.Get(TextKey.Notification_ProctoringInactiveTooltip);
InvokeNotificationChanged();
}
}
}

View file

@ -1,69 +0,0 @@
<html>
<head>
<meta charset="utf-8" />
</head>
<body style="margin: 0">
<div id="placeholder" />
<script src='https://meet.jit.si/external_api.js'></script>
<script type="text/javascript">
var api;
function startMeeting(credentials) {
var configOverwrite = {
disableProfile: true,
prejoinPageEnabled: false,
startAudioOnly: '%_AUDIO_ONLY_%',
startWithAudioMuted: '%_AUDIO_MUTED_%',
startWithVideoMuted: '%_VIDEO_MUTED_%'
};
var interfaceOverwrite = {
JITSI_WATERMARK_LINK: '',
SHOW_JITSI_WATERMARK: false,
TOOLBAR_BUTTONS: [
'microphone', 'camera', '%%_ALLOW_CLOSED_CAPTIONS_%%', /*'desktop',*/ 'embedmeeting', 'fullscreen',
'fodeviceselection', /*'hangup',*/ 'profile', '%%_ALLOW_CHAT_%%', '%%_ALLOW_RECORDING_%%',
'livestreaming', 'etherpad', /*'sharedvideo',*/ 'settings', '%%_ALLOW_RAISE_HAND_%%',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
'%%_ALLOW_TILE_VIEW_%%', 'select-background', 'download', 'help', /*'mute-everyone',*/ 'mute-video-everyone', 'security'
]
};
var options = {
configOverwrite: configOverwrite,
height: '100%',
interfaceConfigOverwrite: interfaceOverwrite,
jwt: credentials.token,
parentNode: document.querySelector('#placeholder'),
roomName: credentials.roomName,
width: '100%'
};
api = new JitsiMeetExternalAPI(credentials.domain, options);
api.addListener('audioMuteStatusChanged', args => {
if (args.muted) {
api.executeCommand('toggleAudio');
}
});
api.addListener('videoMuteStatusChanged', args => {
if (args.muted) {
api.executeCommand('toggleVideo');
}
});
api.executeCommand('subject', credentials.subject);
}
function webMessageReceived(args) {
if ('credentials' in args.data) {
startMeeting(args.data.credentials);
}
}
window.addEventListener('unload', () => {
api.executeCommand('hangup');
api.dispose();
});
window.chrome.webview.addEventListener('message', webMessageReceived);
window.chrome.webview.postMessage('credentials');
</script>
</body>
</html>

View file

@ -1,104 +0,0 @@
/*
* 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 Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using Newtonsoft.Json.Linq;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Proctoring;
using SafeExamBrowser.UserInterface.Contracts.Proctoring.Events;
namespace SafeExamBrowser.Proctoring
{
internal class ProctoringControl : WebView2, IProctoringControl
{
private readonly ILogger logger;
private readonly ProctoringSettings settings;
public event FullScreenChangedEventHandler FullScreenChanged;
internal ProctoringControl(ILogger logger, ProctoringSettings settings)
{
this.logger = logger;
this.settings = settings;
CoreWebView2InitializationCompleted += ProctoringControl_CoreWebView2InitializationCompleted;
}
private void ProctoringControl_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
if (e.IsSuccess)
{
CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
CoreWebView2.Settings.AreDevToolsEnabled = false;
CoreWebView2.Settings.IsStatusBarEnabled = false;
CoreWebView2.ContainsFullScreenElementChanged += CoreWebView2_ContainsFullScreenElementChanged;
CoreWebView2.PermissionRequested += CoreWebView2_PermissionRequested;
CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
logger.Info("Successfully initialized.");
}
else
{
logger.Error("Failed to initialize!", e.InitializationException);
}
}
private void CoreWebView2_ContainsFullScreenElementChanged(object sender, object e)
{
FullScreenChanged?.Invoke(CoreWebView2.ContainsFullScreenElement);
logger.Debug($"Full screen {(CoreWebView2.ContainsFullScreenElement ? "activated" : "deactivated")}.");
}
private void CoreWebView2_PermissionRequested(object sender, CoreWebView2PermissionRequestedEventArgs e)
{
if (e.PermissionKind == CoreWebView2PermissionKind.Camera || e.PermissionKind == CoreWebView2PermissionKind.Microphone)
{
e.State = CoreWebView2PermissionState.Allow;
logger.Info($"Granted access to {e.PermissionKind}.");
}
else
{
logger.Info($"Denied access to {e.PermissionKind}.");
}
}
private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
var message = e.TryGetWebMessageAsString();
logger.Debug($"Received web message '{message}'.");
switch (message)
{
case "credentials":
SendCredentials();
break;
}
}
private void SendCredentials()
{
var message = new JObject();
var credentials = new JObject();
if (settings.JitsiMeet.Enabled)
{
credentials.Add(new JProperty("domain", settings.JitsiMeet.ServerUrl));
credentials.Add(new JProperty("roomName", settings.JitsiMeet.RoomName));
credentials.Add(new JProperty("subject", settings.JitsiMeet.ShowMeetingName ? settings.JitsiMeet.Subject : ""));
credentials.Add(new JProperty("token", settings.JitsiMeet.Token));
}
message.Add("credentials", credentials);
logger.Debug("Sending credentials to proctoring client.");
CoreWebView2.PostWebMessageAsJson(message.ToString());
}
}
}

View file

@ -12,7 +12,6 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Proctoring.JitsiMeet;
using SafeExamBrowser.Proctoring.ScreenProctoring;
using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
using SafeExamBrowser.Settings.Proctoring;
@ -57,13 +56,6 @@ namespace SafeExamBrowser.Proctoring
{
var implementations = new List<ProctoringImplementation>();
if (settings.JitsiMeet.Enabled)
{
var logger = this.logger.CloneFor(nameof(JitsiMeet));
implementations.Add(new JitsiMeetImplementation(appConfig, fileSystem, logger, settings, text, uiFactory));
}
if (settings.ScreenProctoring.Enabled)
{
var logger = this.logger.CloneFor(nameof(ScreenProctoring));

View file

@ -62,15 +62,6 @@
<Reference Include="KGySoft.Drawing.Core, Version=8.1.0.0, Culture=neutral, PublicKeyToken=b45eba277439ddfe, processorArchitecture=MSIL">
<HintPath>..\packages\KGySoft.Drawing.Core.8.1.0\lib\net46\KGySoft.Drawing.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.WebView2.Core, Version=1.0.2365.46, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.2365.46\lib\net45\Microsoft.Web.WebView2.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.WebView2.WinForms, Version=1.0.2365.46, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.2365.46\lib\net45\Microsoft.Web.WebView2.WinForms.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.WebView2.Wpf, Version=1.0.2365.46, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.2365.46\lib\net45\Microsoft.Web.WebView2.Wpf.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
@ -87,8 +78,6 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="JitsiMeet\JitsiMeetImplementation.cs" />
<Compile Include="ProctoringControl.cs" />
<Compile Include="ProctoringController.cs" />
<Compile Include="ProctoringFactory.cs" />
<Compile Include="ProctoringImplementation.cs" />
@ -176,19 +165,9 @@
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="JitsiMeet\index.html" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Microsoft.Web.WebView2.1.0.2365.46\build\Microsoft.Web.WebView2.targets" Condition="Exists('..\packages\Microsoft.Web.WebView2.1.0.2365.46\build\Microsoft.Web.WebView2.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.Web.WebView2.1.0.2365.46\build\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Web.WebView2.1.0.2365.46\build\Microsoft.Web.WebView2.targets'))" />
</Target>
</Project>

View file

@ -26,7 +26,6 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Data
private string applicationInfo;
private string browserInfo;
private string browserInfoWithoutUrls;
private TimeSpan elapsed;
private string triggerInfo;
private string urls;
@ -76,7 +75,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Data
CaptureMouseTrigger(mouse);
}
logger.Debug($"Captured metadata: {applicationInfo} / {browserInfoWithoutUrls} / {triggerInfo} / {urlCount} URL(s) / {windowTitle}.");
logger.Debug($"Captured metadata: {applicationInfo} / {browserInfo} / {urlCount} URL(s) / {triggerInfo} / {windowTitle}.");
}
private void CaptureApplicationData()
@ -101,8 +100,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Data
{
var windows = browser.GetWindows();
browserInfo = string.Join(", ", windows.Select(w => $"{(w.IsMainWindow ? "Main" : "Additional")} Window: {w.Title} ({w.Url})"));
browserInfoWithoutUrls = string.Join(", ", windows.Select(w => $"{(w.IsMainWindow ? "Main" : "Additional")} Window: {w.Title}"));
browserInfo = string.Join(", ", windows.Select(w => $"{(w.IsMainWindow ? "Main" : "Additional")} Window: {w.Title}"));
urls = string.Join(", ", windows.Select(w => w.Url));
urlCount = windows.Count();
}
@ -115,10 +113,19 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Data
private void CaptureKeyboardTrigger(KeyboardTrigger keyboard)
{
var flags = Enum.GetValues(typeof(KeyModifier)).OfType<KeyModifier>().Where(m => m != KeyModifier.None && keyboard.Modifier.HasFlag(m));
var flags = Enum.GetValues(typeof(KeyModifier))
.OfType<KeyModifier>()
.Where(m => m != KeyModifier.None && keyboard.Modifier.HasFlag(m) && !keyboard.Key.ToString().Contains(m.ToString()));
var modifiers = flags.Any() ? string.Join(" + ", flags) + " + " : string.Empty;
triggerInfo = $"'{modifiers}{keyboard.Key}' has been {keyboard.State.ToString().ToLower()}.";
if (flags.Any())
{
triggerInfo = $"'{modifiers}{keyboard.Key}' has been {keyboard.State.ToString().ToLower()}.";
}
else
{
triggerInfo = $"A key has been {keyboard.State.ToString().ToLower()}.";
}
}
private void CaptureMouseTrigger(MouseTrigger mouse)
@ -156,7 +163,6 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Data
{
applicationInfo = "-";
browserInfo = "-";
browserInfoWithoutUrls = "-";
triggerInfo = "-";
urls = "-";
windowTitle = "-";

View file

@ -166,7 +166,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
{
logger.Info("Connecting to service...");
var connect = service.Connect(settings.ServiceUrl);
var connect = service.Connect(settings.ClientId, settings.ClientSecret, settings.ServiceUrl);
if (connect.Success)
{
@ -191,11 +191,11 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
}
}
private void UpdateNotification(bool live)
private void UpdateNotification(bool active)
{
CanActivate = false;
if (live)
if (active)
{
IconResource = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/ScreenProctoring_Active.xaml") };
Tooltip = text.Get(TextKey.Notification_ProctoringActiveTooltip);

View file

@ -8,6 +8,7 @@
using System;
using System.Net.Http;
using System.Text;
namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
{
@ -15,7 +16,20 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
{
internal static string ToLogString(this HttpResponseMessage response)
{
return $"{(int?) response?.StatusCode} {response?.StatusCode} {response?.ReasonPhrase}";
return response == default ? "No Response" : $"{(int) response.StatusCode} {response.StatusCode} {response.ReasonPhrase}";
}
internal static string ToSummary(this Exception exception)
{
var trimChars = new[] { '.', '!' };
var summary = new StringBuilder(exception.Message?.TrimEnd(trimChars));
for (var inner = exception.InnerException; inner != default; inner = inner.InnerException)
{
summary.Append($" -> {inner.Message?.TrimEnd(trimChars)}");
}
return summary.ToString();
}
internal static long ToUnixTimestamp(this DateTime date)

View file

@ -17,8 +17,11 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
{
}
internal bool TryExecute(out string message)
internal bool TryExecute(string clientId, string clientSecret, out string message)
{
ClientId = clientId;
ClientSecret = clientSecret;
return TryRetrieveOAuth2Token(out message);
}
}

View file

@ -21,29 +21,21 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
{
private const int ATTEMPTS = 5;
private static string connectionToken;
private static string oauth2Token;
private readonly HttpClient httpClient;
private bool hadException;
protected readonly Api api;
protected readonly ILogger logger;
protected readonly Parser parser;
protected static string ClientId { get; set; }
protected static string ClientSecret { get; set; }
protected (string, string) Authorization => (Header.AUTHORIZATION, $"Bearer {oauth2Token}");
internal static string ConnectionToken
{
get { return connectionToken; }
set { connectionToken = value; }
}
internal static string Oauth2Token
{
get { return oauth2Token; }
set { oauth2Token = value; }
}
protected Request(Api api, HttpClient httpClient, ILogger logger, Parser parser)
{
this.api = api;
@ -84,12 +76,16 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
}
catch (TaskCanceledException)
{
logger.Error($"Request {request.Method} '{request.RequestUri}' did not complete within {httpClient.Timeout}ms!");
logger.Warn($"Request {request.Method} '{request.RequestUri}' did not complete within {httpClient.Timeout}!");
break;
}
catch (Exception e)
{
logger.Error($"Request {request.Method} '{request.RequestUri}' has failed!", e);
if (IsFirstException())
{
logger.Warn($"Request {request.Method} '{request.RequestUri}' has failed: {e.ToSummary()}!");
}
}
}
@ -98,7 +94,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
protected bool TryRetrieveOAuth2Token(out string message)
{
var secret = Convert.ToBase64String(Encoding.UTF8.GetBytes("test:test"));
var secret = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ClientId}:{ClientSecret}"));
var authorization = (Header.AUTHORIZATION, $"Basic {secret}");
var content = "grant_type=client_credentials&scope=read write";
var success = TryExecute(HttpMethod.Post, api.AccessTokenEndpoint, out var response, content, ContentType.URL_ENCODED, authorization);
@ -154,6 +150,15 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
return request;
}
private bool IsFirstException()
{
var isFirst = !hadException;
hadException = true;
return isFirst;
}
private (string name, string value)[] UpdateOAuth2Token((string name, string value)[] headers)
{
var result = new List<(string name, string value)>();

View file

@ -7,6 +7,7 @@
*/
using System;
using System.Net;
using System.Net.Http;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Proctoring.ScreenProctoring.Data;
@ -24,8 +25,8 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service.Requests
internal bool TryExecute(MetaData metaData, ScreenShot screenShot, string sessionId, out int health, out string message)
{
var imageFormat = (Header.IMAGE_FORMAT, ToString(screenShot.Format));
var metdataJson = (Header.METADATA, metaData.ToJson());
var timestamp = (Header.TIMESTAMP, DateTime.Now.ToUnixTimestamp().ToString());
var metdataJson = (Header.METADATA, WebUtility.UrlEncode(metaData.ToJson()));
var timestamp = (Header.TIMESTAMP, screenShot.CaptureTime.ToUnixTimestamp().ToString());
var url = api.ScreenShotEndpoint.Replace(Api.SESSION_ID, sessionId);
var success = TryExecute(HttpMethod.Post, url, out var response, screenShot.Data, ContentType.OCTET_STREAM, Authorization, imageFormat, metdataJson, timestamp);

View file

@ -33,12 +33,16 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service
this.parser = new Parser(logger);
}
internal ServiceResponse Connect(string serviceUrl)
internal ServiceResponse Connect(string clientId, string clientSecret, string serviceUrl)
{
httpClient = new HttpClient { BaseAddress = new Uri(serviceUrl) };
httpClient = new HttpClient
{
BaseAddress = new Uri(serviceUrl),
Timeout = TimeSpan.FromSeconds(10)
};
var request = new OAuth2TokenRequest(api, httpClient, logger, parser);
var success = request.TryExecute(out var message);
var success = request.TryExecute(clientId, clientSecret, out var message);
if (success)
{
@ -81,7 +85,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service
}
else
{
logger.Error("Failed to query health!");
logger.Warn("Failed to query health!");
}
return new ServiceResponse<int>(success, health, message);

View file

@ -62,7 +62,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
var progress = 0;
var total = previous;
while (HasRemainingWork() && service.IsConnected && (!networkIssue || recovering))
while (HasRemainingWork() && service.IsConnected && (!networkIssue || recovering || DateTime.Now < resume))
{
var remaining = buffer.Count + cache.Count;
@ -80,7 +80,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
updateStatus(new RemainingWorkUpdatedEventArgs
{
IsWaiting = recovering,
IsWaiting = recovering || networkIssue,
Next = buffer.TryPeek(out _, out var schedule, out _) ? schedule : default(DateTime?),
Progress = progress,
Resume = resume,
@ -121,6 +121,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
thread.Start();
timer.AutoReset = false;
timer.Elapsed += Timer_Elapsed;
timer.Interval = FIFTEEN_SECONDS;
}
@ -195,19 +196,6 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
private void ExecuteCaching()
{
const int THREE_MINUTES = 180;
if (!recovering)
{
recovering = true;
resume = DateTime.Now.AddSeconds(random.Next(0, THREE_MINUTES));
timer.Elapsed += Timer_Elapsed;
timer.Start();
logger.Warn($"Activating local caching and suspending transmission due to bad service health (value: {health}, resume: {resume:HH:mm:ss}).");
}
CacheFromBuffer();
CacheFromQueue();
}
@ -237,9 +225,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
else
{
timer.Stop();
timer.Elapsed -= Timer_Elapsed;
logger.Info($"Deactivating local caching and resuming transmission due to improved service health (value: {health}).");
logger.Info($"Recovery terminated, deactivating local caching and resuming transmission.");
}
}
@ -278,7 +264,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
}
else
{
buffer.Enqueue(metaData, DateTime.Now, screenShot);
buffer.Enqueue(metaData, CalculateSchedule(metaData), screenShot);
}
}
}
@ -296,10 +282,18 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
var hasItem = buffer.TryPeek(out var metaData, out var schedule, out var screenShot);
var ready = schedule <= DateTime.Now;
if (hasItem && ready && TryTransmit(metaData, screenShot))
if (hasItem && ready)
{
buffer.Dequeue();
screenShot.Dispose();
if (TryTransmit(metaData, screenShot))
{
screenShot.Dispose();
}
else
{
buffer.Enqueue(metaData, CalculateSchedule(metaData), screenShot);
}
}
}
@ -313,7 +307,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
}
else
{
buffer.Enqueue(metaData, DateTime.Now, screenShot);
buffer.Enqueue(metaData, CalculateSchedule(metaData), screenShot);
}
}
}
@ -328,7 +322,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
}
else
{
buffer.Enqueue(metaData, DateTime.Now, screenShot);
buffer.Enqueue(metaData, CalculateSchedule(metaData), screenShot);
}
}
}
@ -354,14 +348,11 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
if (service.IsConnected)
{
var response = service.Send(metaData, screenShot);
var value = response.Success ? response.Value : BAD;
health = UpdateHealth(value);
networkIssue = !response.Success;
success = response.Success;
if (response.Success)
{
health = UpdateHealth(response.Value);
}
}
else
{
@ -376,30 +367,52 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
if (service.IsConnected)
{
var response = service.GetHealth();
var value = response.Success ? response.Value : BAD;
health = UpdateHealth(value);
networkIssue = !response.Success;
if (response.Success)
{
health = UpdateHealth(response.Value);
}
}
else
{
logger.Warn("Cannot query health as service is disconnected!");
}
timer.Start();
if (!timer.Enabled)
{
timer.Start();
}
}
private int UpdateHealth(int value)
{
const int THREE_MINUTES = 180;
var previous = health;
var current = value > BAD ? BAD : (value < GOOD ? GOOD : value);
if (previous != current)
{
logger.Info($"Service health {(previous < current ? "deteriorated" : "improved")} from {previous} to {current}.");
if (current == BAD)
{
recovering = false;
resume = DateTime.Now.AddSeconds(random.Next(0, THREE_MINUTES));
if (!timer.Enabled)
{
timer.Start();
}
logger.Warn($"Activating local caching and suspending transmission due to bad service health (resume: {resume:HH:mm:ss}).");
}
else if (previous == BAD)
{
recovering = true;
resume = DateTime.Now < resume ? resume : DateTime.Now.AddSeconds(random.Next(0, THREE_MINUTES));
logger.Info($"Starting recovery while maintaining local caching (resume: {resume:HH:mm:ss}).");
}
}
return current;

View file

@ -3,6 +3,5 @@
<package id="KGySoft.CoreLibraries" version="8.1.0" targetFramework="net48" />
<package id="KGySoft.Drawing" version="8.1.0" targetFramework="net48" />
<package id="KGySoft.Drawing.Core" version="8.1.0" targetFramework="net48" />
<package id="Microsoft.Web.WebView2" version="1.0.2365.46" targetFramework="net48" />
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
</packages>

View file

@ -42,21 +42,22 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod]
public void Perform_MustShowDisclaimerWhenProctoringEnabled()
{
var disclaimerShown = false;
var count = 0;
settings.Proctoring.ScreenProctoring.Enabled = true;
settings.Proctoring.Enabled = true;
sut.ActionRequired += (args) =>
{
if (args is MessageEventArgs m)
{
disclaimerShown = true;
count++;
m.Result = MessageBoxResult.Ok;
}
};
var result = sut.Perform();
Assert.IsTrue(disclaimerShown);
Assert.AreEqual(1, count);
Assert.AreEqual(OperationResult.Success, result);
}
@ -65,7 +66,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var disclaimerShown = false;
settings.Proctoring.Enabled = true;
settings.Proctoring.ScreenProctoring.Enabled = true;
sut.ActionRequired += (args) =>
{
if (args is MessageEventArgs m)
@ -75,7 +77,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
}
};
var result = sut.Perform();
var result = sut.Repeat();
Assert.IsTrue(disclaimerShown);
Assert.AreEqual(OperationResult.Aborted, result);
@ -86,7 +88,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var disclaimerShown = false;
settings.Proctoring.Enabled = false;
sut.ActionRequired += (args) =>
{
if (args is MessageEventArgs m)
@ -105,21 +106,22 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod]
public void Repeat_MustShowDisclaimerWhenProctoringEnabled()
{
var disclaimerShown = false;
var count = 0;
settings.Proctoring.ScreenProctoring.Enabled = true;
settings.Proctoring.Enabled = true;
sut.ActionRequired += (args) =>
{
if (args is MessageEventArgs m)
{
disclaimerShown = true;
count++;
m.Result = MessageBoxResult.Ok;
}
};
var result = sut.Repeat();
var result = sut.Perform();
Assert.IsTrue(disclaimerShown);
Assert.AreEqual(1, count);
Assert.AreEqual(OperationResult.Success, result);
}
@ -128,7 +130,8 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var disclaimerShown = false;
settings.Proctoring.Enabled = true;
settings.Proctoring.ScreenProctoring.Enabled = true;
sut.ActionRequired += (args) =>
{
if (args is MessageEventArgs m)
@ -149,7 +152,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var disclaimerShown = false;
settings.Proctoring.Enabled = false;
sut.ActionRequired += (args) =>
{
if (args is MessageEventArgs m)
@ -170,7 +172,6 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
var disclaimerShown = false;
settings.Proctoring.Enabled = true;
sut.ActionRequired += (args) =>
{
if (args is MessageEventArgs m)

View file

@ -1,100 +0,0 @@
/*
* 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 Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Runtime.Operations;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Security;
namespace SafeExamBrowser.Runtime.UnitTests.Operations
{
[TestClass]
public class ProctoringWorkaroundOperationTests
{
private SessionContext context;
private Mock<ILogger> logger;
private AppSettings settings;
private ProctoringWorkaroundOperation sut;
[TestInitialize]
public void Initialize()
{
context = new SessionContext();
logger = new Mock<ILogger>();
settings = new AppSettings();
context.Next = new SessionConfiguration();
context.Next.Settings = settings;
sut = new ProctoringWorkaroundOperation(logger.Object, context);
}
[TestMethod]
public void Perform_MustSwitchToDisableExplorerShellIfProctoringActive()
{
settings.Proctoring.Enabled = true;
settings.Security.KioskMode = KioskMode.CreateNewDesktop;
var result = sut.Perform();
Assert.AreEqual(KioskMode.DisableExplorerShell, settings.Security.KioskMode);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Perform_MustDoNothingIfProctoringNotActive()
{
settings.Proctoring.Enabled = false;
settings.Security.KioskMode = KioskMode.None;
var result = sut.Perform();
Assert.AreEqual(KioskMode.None, settings.Security.KioskMode);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Repeat_MustSwitchToDisableExplorerShellIfProctoringActive()
{
settings.Proctoring.Enabled = true;
settings.Security.KioskMode = KioskMode.CreateNewDesktop;
var result = sut.Repeat();
Assert.AreEqual(KioskMode.DisableExplorerShell, settings.Security.KioskMode);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Repeat_MustDoNothingIfProctoringNotActive()
{
settings.Proctoring.Enabled = false;
settings.Security.KioskMode = KioskMode.None;
var result = sut.Repeat();
Assert.AreEqual(KioskMode.None, settings.Security.KioskMode);
Assert.AreEqual(OperationResult.Success, result);
}
[TestMethod]
public void Revert_MustDoNothing()
{
settings.Proctoring.Enabled = true;
settings.Security.KioskMode = KioskMode.None;
var result = sut.Revert();
Assert.AreEqual(KioskMode.None, settings.Security.KioskMode);
Assert.AreEqual(OperationResult.Success, result);
}
}
}

View file

@ -11,10 +11,10 @@ using Moq;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
using SafeExamBrowser.Runtime.Operations;
using SafeExamBrowser.Runtime.Operations.Events;
using SafeExamBrowser.Settings;
using SafeExamBrowser.SystemComponents.Contracts;
namespace SafeExamBrowser.Runtime.UnitTests.Operations
{

View file

@ -11,10 +11,10 @@ using Moq;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
using SafeExamBrowser.Runtime.Operations;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Security;
using SafeExamBrowser.SystemComponents.Contracts;
namespace SafeExamBrowser.Runtime.UnitTests.Operations
{

View file

@ -145,7 +145,6 @@
<Compile Include="Operations\DisclaimerOperationTests.cs" />
<Compile Include="Operations\DisplayMonitorOperationTests.cs" />
<Compile Include="Operations\KioskModeOperationTests.cs" />
<Compile Include="Operations\ProctoringWorkaroundOperationTests.cs" />
<Compile Include="Operations\RemoteSessionOperationTests.cs" />
<Compile Include="Operations\ServerOperationTests.cs" />
<Compile Include="Operations\ServiceOperationTests.cs" />

View file

@ -25,7 +25,9 @@ using SafeExamBrowser.I18n;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring;
using SafeExamBrowser.Monitoring.Display;
using SafeExamBrowser.Monitoring.System;
using SafeExamBrowser.Runtime.Communication;
using SafeExamBrowser.Runtime.Operations;
using SafeExamBrowser.Server;
@ -82,6 +84,7 @@ namespace SafeExamBrowser.Runtime
var remoteSessionDetector = new RemoteSessionDetector(ModuleLogger(nameof(RemoteSessionDetector)));
var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS);
var runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig);
var sentinel = new SystemSentinel(ModuleLogger(nameof(SystemSentinel)), nativeMethods, registry);
var server = new ServerProxy(appConfig, keyGenerator, ModuleLogger(nameof(ServerProxy)), systemInfo, userInfo);
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
var sessionContext = new SessionContext();
@ -101,12 +104,11 @@ namespace SafeExamBrowser.Runtime
sessionOperations.Enqueue(new VersionRestrictionOperation(logger, sessionContext, text));
sessionOperations.Enqueue(new DisclaimerOperation(logger, sessionContext));
sessionOperations.Enqueue(new RemoteSessionOperation(remoteSessionDetector, logger, sessionContext));
sessionOperations.Enqueue(new SessionIntegrityOperation(logger, registry, sessionContext));
sessionOperations.Enqueue(new SessionIntegrityOperation(logger, sentinel, sessionContext));
sessionOperations.Enqueue(new VirtualMachineOperation(vmDetector, logger, sessionContext));
sessionOperations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, sessionContext, text));
sessionOperations.Enqueue(new ServiceOperation(logger, runtimeHost, serviceProxy, sessionContext, THIRTY_SECONDS, userInfo));
sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
sessionOperations.Enqueue(new ProctoringWorkaroundOperation(logger, sessionContext));
sessionOperations.Enqueue(new KioskModeOperation(desktopFactory, desktopMonitor, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new ClientOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext));

View file

@ -103,7 +103,7 @@ namespace SafeExamBrowser.Runtime.Operations
var logFilePath = $"{'"' + Convert.ToBase64String(Encoding.UTF8.GetBytes(Context.Next.AppConfig.ClientLogFilePath)) + '"'}";
var logLevel = Context.Next.Settings.LogLevel.ToString();
var runtimeHostUri = Context.Next.AppConfig.RuntimeAddress;
var uiMode = Context.Next.Settings.UserInterfaceMode.ToString();
var uiMode = Context.Next.Settings.UserInterface.Mode.ToString();
var clientReady = false;
var clientReadyEvent = new AutoResetEvent(false);

View file

@ -32,13 +32,13 @@ namespace SafeExamBrowser.Runtime.Operations
protected abstract void InvokeActionRequired(ActionRequiredEventArgs args);
protected LoadStatus? TryLoadSettings(Uri uri, UriSource source, out PasswordParameters passwordParams, out AppSettings settings, string currentPassword = default(string))
protected LoadStatus? TryLoadSettings(Uri uri, UriSource source, out PasswordParameters passwordParams, out AppSettings settings, string currentPassword = default)
{
passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true };
var status = configuration.TryLoadSettings(uri, out settings, passwordParams);
if (status == LoadStatus.PasswordNeeded && currentPassword != default(string))
if (status == LoadStatus.PasswordNeeded && currentPassword != default)
{
passwordParams.Password = currentPassword;
passwordParams.IsHash = true;
@ -46,7 +46,7 @@ namespace SafeExamBrowser.Runtime.Operations
status = configuration.TryLoadSettings(uri, out settings, passwordParams);
}
for (int attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
for (var attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
{
var isLocalConfig = source == UriSource.AppData || source == UriSource.ProgramData;
var purpose = isLocalConfig ? PasswordRequestPurpose.LocalSettings : PasswordRequestPurpose.Settings;

View file

@ -164,13 +164,18 @@ namespace SafeExamBrowser.Runtime.Operations
result = OperationResult.Aborted;
}
if (result == OperationResult.Success && Context.Current.IsBrowserResource)
{
HandleReconfigurationByBrowserResource();
}
fileSystem.Delete(uri.LocalPath);
logger.Info($"Deleted temporary configuration file '{uri}'.");
return result;
}
private OperationResult DetermineLoadResult(Uri uri, UriSource source, AppSettings settings, LoadStatus status, PasswordParameters passwordParams, string currentPassword = default(string))
private OperationResult DetermineLoadResult(Uri uri, UriSource source, AppSettings settings, LoadStatus status, PasswordParameters passwordParams, string currentPassword = default)
{
var result = OperationResult.Failed;
@ -205,6 +210,7 @@ namespace SafeExamBrowser.Runtime.Operations
private OperationResult HandleBrowserResource(Uri uri)
{
Context.Next.IsBrowserResource = true;
Context.Next.Settings.Applications.Blacklist.Clear();
Context.Next.Settings.Applications.Whitelist.Clear();
Context.Next.Settings.Display.AllowedDisplays = 10;
@ -222,7 +228,7 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success;
}
private OperationResult HandleClientConfiguration(Uri uri, PasswordParameters passwordParams, string currentPassword = default(string))
private OperationResult HandleClientConfiguration(Uri uri, PasswordParameters passwordParams, string currentPassword = default)
{
var isFirstSession = Context.Current == null;
var success = TryConfigureClient(uri, passwordParams, currentPassword);
@ -240,6 +246,12 @@ namespace SafeExamBrowser.Runtime.Operations
return result;
}
private void HandleReconfigurationByBrowserResource()
{
Context.Next.Settings.Browser.DeleteCookiesOnStartup = false;
logger.Info("Some browser settings were overridden in order to retain a potential LMS / web application session.");
}
private void HandleStartUrlQuery(Uri uri, UriSource source)
{
if (source == UriSource.Reconfiguration && Uri.TryCreate(Context.ReconfigurationUrl, UriKind.Absolute, out var reconfigurationUri))
@ -247,13 +259,13 @@ namespace SafeExamBrowser.Runtime.Operations
uri = reconfigurationUri;
}
if (uri != default(Uri) && uri.Query.LastIndexOf('?') > 0)
if (uri != default && uri.Query.LastIndexOf('?') > 0)
{
Context.Next.Settings.Browser.StartUrlQuery = uri.Query.Substring(uri.Query.LastIndexOf('?'));
}
}
private bool? TryConfigureClient(Uri uri, PasswordParameters passwordParams, string currentPassword = default(string))
private bool? TryConfigureClient(Uri uri, PasswordParameters passwordParams, string currentPassword = default)
{
var mustAuthenticate = IsRequiredToAuthenticateForClientConfiguration(passwordParams, currentPassword);
@ -304,9 +316,9 @@ namespace SafeExamBrowser.Runtime.Operations
return success;
}
private bool IsRequiredToAuthenticateForClientConfiguration(PasswordParameters passwordParams, string currentPassword = default(string))
private bool IsRequiredToAuthenticateForClientConfiguration(PasswordParameters passwordParams, string currentPassword = default)
{
var mustAuthenticate = currentPassword != default(string);
var mustAuthenticate = currentPassword != default;
if (mustAuthenticate)
{
@ -334,7 +346,7 @@ namespace SafeExamBrowser.Runtime.Operations
{
var authenticated = false;
for (int attempts = 0; attempts < 5 && !authenticated; attempts++)
for (var attempts = 0; attempts < 5 && !authenticated; attempts++)
{
var success = TryGetPassword(PasswordRequestPurpose.LocalAdministrator, out var password);
@ -384,8 +396,8 @@ namespace SafeExamBrowser.Runtime.Operations
{
var isValidUri = false;
uri = null;
source = default(UriSource);
uri = default;
source = default;
if (commandLineArgs?.Length > 1)
{

View file

@ -31,21 +31,11 @@ namespace SafeExamBrowser.Runtime.Operations
{
var result = OperationResult.Success;
if (Context.Next.Settings.Proctoring.JitsiMeet.Enabled)
{
result = ShowVideoProctoringDisclaimer();
}
if (result == OperationResult.Success && Context.Next.Settings.Proctoring.ScreenProctoring.Enabled)
if (Context.Next.Settings.Proctoring.ScreenProctoring.Enabled)
{
result = ShowScreenProctoringDisclaimer();
}
if (result == OperationResult.Success && Context.Next.Settings.Proctoring.Zoom.Enabled)
{
result = ShowZoomError();
}
return result;
}
@ -53,21 +43,11 @@ namespace SafeExamBrowser.Runtime.Operations
{
var result = OperationResult.Success;
if (Context.Next.Settings.Proctoring.JitsiMeet.Enabled)
{
result = ShowVideoProctoringDisclaimer();
}
if (result == OperationResult.Success && Context.Next.Settings.Proctoring.ScreenProctoring.Enabled)
if (Context.Next.Settings.Proctoring.ScreenProctoring.Enabled)
{
result = ShowScreenProctoringDisclaimer();
}
if (result == OperationResult.Success && Context.Next.Settings.Proctoring.Zoom.Enabled)
{
result = ShowZoomError();
}
return result;
}
@ -102,50 +82,5 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Aborted;
}
}
private OperationResult ShowVideoProctoringDisclaimer()
{
var args = new MessageEventArgs
{
Action = MessageBoxAction.OkCancel,
Icon = MessageBoxIcon.Information,
Message = TextKey.MessageBox_VideoProctoringDisclaimer,
Title = TextKey.MessageBox_VideoProctoringDisclaimerTitle
};
StatusChanged?.Invoke(TextKey.OperationStatus_WaitDisclaimerConfirmation);
ActionRequired?.Invoke(args);
if (args.Result == MessageBoxResult.Ok)
{
logger.Info("The user confirmed the video proctoring disclaimer.");
return OperationResult.Success;
}
else
{
logger.Warn("The user did not confirm the video proctoring disclaimer! Aborting session initialization...");
return OperationResult.Aborted;
}
}
private OperationResult ShowZoomError()
{
var args = new MessageEventArgs
{
Action = MessageBoxAction.Ok,
Icon = MessageBoxIcon.Error,
Message = TextKey.MessageBox_ZoomNotSupported,
Title = TextKey.MessageBox_ZoomNotSupportedTitle
};
logger.Error("Zoom proctoring is enabled but not supported! Aborting session initialization...");
StatusChanged?.Invoke(TextKey.OperationStatus_WaitErrorConfirmation);
ActionRequired?.Invoke(args);
return OperationResult.Aborted;
}
}
}

View file

@ -1,49 +0,0 @@
/*
* 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.Logging.Contracts;
using SafeExamBrowser.Settings.Security;
namespace SafeExamBrowser.Runtime.Operations
{
internal class ProctoringWorkaroundOperation : SessionOperation
{
private readonly ILogger logger;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged { add { } remove { } }
public ProctoringWorkaroundOperation(ILogger logger, SessionContext context) : base(context)
{
this.logger = logger;
}
public override OperationResult Perform()
{
if (Context.Next.Settings.Proctoring.JitsiMeet.Enabled && Context.Next.Settings.Security.KioskMode == KioskMode.CreateNewDesktop)
{
Context.Next.Settings.Security.KioskMode = KioskMode.DisableExplorerShell;
logger.Info("Switched kiosk mode to Disable Explorer Shell due to remote proctoring being enabled.");
}
return OperationResult.Success;
}
public override OperationResult Repeat()
{
return Perform();
}
public override OperationResult Revert()
{
return OperationResult.Success;
}
}
}

View file

@ -10,8 +10,8 @@ using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
using SafeExamBrowser.Runtime.Operations.Events;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
namespace SafeExamBrowser.Runtime.Operations

View file

@ -169,6 +169,7 @@ namespace SafeExamBrowser.Runtime.Operations
if (status == LoadStatus.Success)
{
var browserSettings = Context.Next.Settings.Browser;
var serverSettings = Context.Next.Settings.Server;
Context.Next.AppConfig.ServerApi = info.Api;
@ -178,6 +179,7 @@ namespace SafeExamBrowser.Runtime.Operations
Context.Next.Settings = settings;
Context.Next.Settings.Browser.StartUrl = exam.Url;
Context.Next.Settings.Browser.StartUrlQuery = browserSettings.StartUrlQuery;
Context.Next.Settings.Server = serverSettings;
Context.Next.Settings.SessionMode = SessionMode.Server;

View file

@ -6,31 +6,26 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Linq;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.Monitoring.Contracts.System;
namespace SafeExamBrowser.Runtime.Operations
{
internal class SessionIntegrityOperation : SessionOperation
{
private static readonly string USER_PATH = $@"{Environment.ExpandEnvironmentVariables("%LocalAppData%")}\Microsoft\Windows\Cursors\";
private static readonly string SYSTEM_PATH = $@"{Environment.ExpandEnvironmentVariables("%SystemRoot%")}\Cursors\";
private readonly ILogger logger;
private readonly IRegistry registry;
private readonly ISystemSentinel sentinel;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public SessionIntegrityOperation(ILogger logger, IRegistry registry, SessionContext context) : base(context)
public SessionIntegrityOperation(ILogger logger, ISystemSentinel sentinel, SessionContext context) : base(context)
{
this.logger = logger;
this.registry = registry;
this.sentinel = sentinel;
}
public override OperationResult Perform()
@ -39,8 +34,11 @@ namespace SafeExamBrowser.Runtime.Operations
StatusChanged?.Invoke(TextKey.OperationStatus_VerifySessionIntegrity);
success &= VerifyCursorConfiguration();
success &= VerifyEaseOfAccessConfiguration();
success &= InitializeStickyKeys();
success &= VerifyCursors();
success &= VerifyEaseOfAccess();
LogResult(success);
return success ? OperationResult.Success : OperationResult.Failed;
}
@ -51,43 +49,60 @@ namespace SafeExamBrowser.Runtime.Operations
StatusChanged?.Invoke(TextKey.OperationStatus_VerifySessionIntegrity);
success &= VerifyCursorConfiguration();
success &= VerifyEaseOfAccessConfiguration();
success &= InitializeStickyKeys();
success &= VerifyCursors();
success &= VerifyEaseOfAccess();
LogResult(success);
return success ? OperationResult.Success : OperationResult.Failed;
}
public override OperationResult Revert()
{
FinalizeStickyKeys();
return OperationResult.Success;
}
private bool VerifyCursorConfiguration()
private void FinalizeStickyKeys()
{
sentinel.RevertStickyKeys();
}
private bool InitializeStickyKeys()
{
var success = true;
sentinel.RevertStickyKeys();
if (!Context.Next.Settings.Security.AllowStickyKeys)
{
success = sentinel.DisableStickyKeys();
}
return success;
}
private void LogResult(bool success)
{
if (success)
{
logger.Info("Successfully ensured session integrity.");
}
else
{
logger.Error("Failed to ensure session integrity! Aborting session initialization...");
}
}
private bool VerifyCursors()
{
var success = true;
if (Context.Next.Settings.Security.VerifyCursorConfiguration)
{
logger.Info($"Attempting to verify cursor configuration...");
success = registry.TryGetNames(RegistryValue.UserHive.Cursors_Key, out var cursors);
if (success)
{
foreach (var cursor in cursors.Where(c => !string.IsNullOrWhiteSpace(c)))
{
success &= VerifyCursor(cursor);
}
}
if (success)
{
logger.Info("Cursor configuration successfully verified.");
}
else
{
logger.Warn("Failed to verify cursor configuration or configuration is compromised! Aborting session initialization...");
}
success = sentinel.VerifyCursors();
}
else
{
@ -97,65 +112,22 @@ namespace SafeExamBrowser.Runtime.Operations
return success;
}
private bool VerifyCursor(string cursor)
private bool VerifyEaseOfAccess()
{
var success = true;
success &= registry.TryRead(RegistryValue.UserHive.Cursors_Key, cursor, out var value);
success &= !(value is string) || (value is string path && (string.IsNullOrWhiteSpace(path) || IsValidCursorPath(path)));
var success = sentinel.VerifyEaseOfAccess();
if (!success)
{
if (value != default)
{
logger.Warn($"Configuration of cursor '{cursor}' is compromised: '{value}'!");
}
else
{
logger.Warn($"Failed to verify configuration of cursor '{cursor}'!");
}
}
return success;
}
private bool IsValidCursorPath(string path)
{
return path.StartsWith(USER_PATH, StringComparison.OrdinalIgnoreCase) || path.StartsWith(SYSTEM_PATH, StringComparison.OrdinalIgnoreCase);
}
private bool VerifyEaseOfAccessConfiguration()
{
var success = false;
logger.Info($"Attempting to verify ease of access configuration...");
if (registry.TryRead(RegistryValue.MachineHive.EaseOfAccess_Key, RegistryValue.MachineHive.EaseOfAccess_Name, out var value))
{
if (value is string s && string.IsNullOrWhiteSpace(s))
if (Context.Current?.Settings.Service.IgnoreService == false)
{
logger.Info($"Ease of access configuration is compromised but service was active in the current session.");
success = true;
logger.Info("Ease of access configuration successfully verified.");
}
else if (!Context.Next.Settings.Service.IgnoreService)
{
logger.Info($"Ease of access configuration is compromised but service will be active in the next session.");
success = true;
logger.Info($"Ease of access configuration is compromised ('{value}'), but service will be active in the next session.");
}
else if (Context.Current?.Settings.Service.IgnoreService == false)
{
success = true;
logger.Info($"Ease of access configuration is set ('{value}'), but service was active in the current session.");
}
else
{
logger.Warn($"Ease of access configuration is compromised: '{value}'! Aborting session initialization...");
}
}
else
{
success = true;
logger.Info("Ease of access configuration successfully verified (value does not exist).");
}
return success;

View file

@ -10,17 +10,17 @@ using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
using SafeExamBrowser.Runtime.Operations.Events;
using SafeExamBrowser.Settings.Security;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
namespace SafeExamBrowser.Runtime.Operations
{
internal class VirtualMachineOperation : SessionOperation
{
private IVirtualMachineDetector detector;
private ILogger logger;
private readonly IVirtualMachineDetector detector;
private readonly ILogger logger;
public override event ActionRequiredEventHandler ActionRequired;
public override event StatusChangedEventHandler StatusChanged;

Some files were not shown because too many files have changed in this diff Show more