25 Commits

Author SHA1 Message Date
213ab539cf v1.7
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
2025-09-16 16:51:14 +02:00
9a9b753049 Sign files
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
2025-09-16 16:43:02 +02:00
5148abede7 Remove leftovers
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
2025-09-16 16:36:09 +02:00
dd82d45ed8 Update Safe Exam Browser Patch to 3.10.0.826 2025-09-16 16:32:31 +02:00
4827ae1afc Update README.md 2025-09-16 08:45:41 +00:00
f79146d6fb Update README.md
Some checks reported errors
continuous-integration/appveyor/branch AppVeyor build cancelled
2025-09-08 09:33:55 +00:00
59747140c7 Upload files to "/"
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
2025-09-08 09:26:54 +00:00
ae5dc61021 Delete appveyor.yml 2025-09-07 15:37:09 +00:00
626b351dff Update appveyor.yml
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
2025-09-07 15:33:59 +00:00
1a350b6c13 Upload files to "/"
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
2025-09-07 15:20:38 +00:00
Vichingo455
9ed5135f69 Bug and UI fixes
Some checks failed
continuous-integration/appveyor/branch AppVeyor build failed
2025-06-24 18:07:56 +02:00
25495595ba Update README.md 2025-06-24 12:47:53 +00:00
8a09160d31 Update .github/ISSUE_TEMPLATE/config.yml 2025-06-24 12:44:53 +00:00
354f731a6f Update .github/ISSUE_TEMPLATE/bug-report.md 2025-06-24 12:43:48 +00:00
9b2367a4a3 Update README.md 2025-06-24 12:12:07 +00:00
4948d5946f Update README.md 2025-06-23 15:36:59 +00:00
84b26a2295 v1.6 2025-06-23 17:31:39 +02:00
811aea6bd2 Reduce UI size 2025-06-23 17:13:30 +02:00
b71e8bd82f v1.6 2025-06-23 17:07:12 +02:00
a6b60d72bd Almost done first beta 2025-06-23 16:03:48 +02:00
2d36fecb45 Missing files + start working on offline patcher 2025-06-23 13:42:14 +02:00
058d48196a Downgrade patcher to .NET 4.5 from .NET 4.7 2025-06-22 22:39:41 +02:00
332c6460c7 Add resource files which got ignored by Git 2025-06-22 22:39:05 +02:00
fc6a2f7509 Update README.md 2025-06-01 13:04:37 +00:00
00707825b4 Restore SEBPatch 2025-06-01 11:56:28 +02:00
1133 changed files with 22922 additions and 10218 deletions

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Wiki
url: https://github.com/school-cheating/SEBPatch/wiki
about: Before opening an issue, check out the wiki to see if your issue is listed there.

54
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
name: "CodeQL"
on:
push:
branches: [ "master", "*" ]
pull_request:
branches: [ "master", "*" ]
schedule:
- cron: '0 0 * * 1'
jobs:
analyze:
name: Analyze
runs-on: "windows-latest"
timeout-minutes: 360
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp', 'javascript-typescript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@@ -1,10 +1,12 @@
# Safe Exam Browser Patch
[![Build status](https://ci.vichingo455.freeddns.org/api/projects/status/qcwguer3ng9ox4a0?svg=true)](https://ci.vichingo455.freeddns.org/project/Vichingo455/sebpatch)
A patch to bypass Safe Exam Browser restrictions.
- Patch for version 3.8.0.742 has been deprecated and will not receive updates in the future, please upgrade to Safe Exam Browser 3.9.0.787.
- Currently supported SEB version: 3.10.0.826
## How to use
Check out the [Wiki](https://github.com/school-cheating/SEBPatch/wiki)
## Downloading and installing
You can get the latest executable from [releases](https://git.vichingo455.freeddns.org/school-cheating/SEBPatch/releases/latest). If you need help about the installation, check the [wiki](https://git.vichingo455.freeddns.org/school-cheating/SEBPatch/wiki).
## Mirrors
In case you can't download from the latest release, here is a list of mirrors (will be updated eventually):

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -18,6 +18,11 @@ namespace SafeExamBrowser.Applications.Contracts
/// </summary>
Error,
/// <summary>
/// The application has been found but is invalid (e.g. because it is not the correct version or has been manipulated).
/// </summary>
Invalid,
/// <summary>
/// The application could not be found on the system.
/// </summary>

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications.Contracts")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2025 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2025 ETH Zürich, IT Services")]
[assembly: ComVisible(false)]

View File

@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" />
<Import Project="..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props" Condition="Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" />
<Import Project="..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.props')" />
<Import Project="..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props" Condition="Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -62,78 +63,82 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.5.1.1\lib\net462\Castle.Core.dll</HintPath>
<HintPath>..\packages\Castle.Core.5.2.1\lib\net462\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ApplicationInsights, Version=2.22.0.997, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.22.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
<Reference Include="Microsoft.ApplicationInsights, Version=2.23.0.29, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.23.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Testing.Extensions.Telemetry, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.dll</HintPath>
<Reference Include="Microsoft.Testing.Extensions.MSBuild, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\lib\netstandard2.0\Microsoft.Testing.Extensions.MSBuild.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.TrxReport.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.TrxReport.Abstractions.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.TrxReport.Abstractions.dll</HintPath>
<Reference Include="Microsoft.Testing.Extensions.Telemetry, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\lib\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.VSTestBridge, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.VSTestBridge.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.VSTestBridge.dll</HintPath>
<Reference Include="Microsoft.Testing.Extensions.TrxReport.Abstractions, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.TrxReport.Abstractions.1.8.3\lib\netstandard2.0\Microsoft.Testing.Extensions.TrxReport.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.dll</HintPath>
<Reference Include="Microsoft.Testing.Extensions.VSTestBridge, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.VSTestBridge.1.8.3\lib\netstandard2.0\Microsoft.Testing.Extensions.VSTestBridge.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform.MSBuild, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.MSBuild.dll</HintPath>
<Reference Include="Microsoft.Testing.Platform, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.1.8.3\lib\netstandard2.0\Microsoft.Testing.Platform.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.AdapterUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.AdapterUtilities.17.14.1\lib\net462\Microsoft.TestPlatform.AdapterUtilities.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.14.1\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.PlatformAbstractions, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.14.1\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.ObjectModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.14.1\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
<HintPath>..\packages\MSTest.TestFramework.3.10.3\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
<HintPath>..\packages\MSTest.TestFramework.3.10.3\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.20.70.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.20.70\lib\net462\Moq.dll</HintPath>
<Reference Include="Moq, Version=4.20.72.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.20.72\lib\net462\Moq.dll</HintPath>
</Reference>
<Reference Include="NuGet.Frameworks, Version=6.9.1.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\NuGet.Frameworks.6.9.1\lib\net472\NuGet.Frameworks.dll</HintPath>
<Reference Include="NuGet.Frameworks, Version=6.14.0.116, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\NuGet.Frameworks.6.14.0\lib\net472\NuGet.Frameworks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll</HintPath>
<Reference Include="System.Collections.Immutable, Version=9.0.0.8, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.9.0.8\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.0\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
<Reference Include="System.Diagnostics.DiagnosticSource, Version=9.0.0.8, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.9.0.8\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
<Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
<Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.8.0.0\lib\net462\System.Reflection.Metadata.dll</HintPath>
<Reference Include="System.Reflection.Metadata, Version=9.0.0.8, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.9.0.8\lib\net462\System.Reflection.Metadata.dll</HintPath>
</Reference>
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
</ItemGroup>
@@ -182,18 +187,27 @@
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\MSTest.Analyzers.3.10.3\analyzers\dotnet\cs\MSTest.Analyzers.CodeFixes.dll" />
<Analyzer Include="..\packages\MSTest.Analyzers.3.10.3\analyzers\dotnet\cs\MSTest.Analyzers.dll" />
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.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.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.targets'))" />
<Error Condition="!Exists('..\packages\MSTest.TestFramework.3.10.3\build\net462\MSTest.TestFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestFramework.3.10.3\build\net462\MSTest.TestFramework.targets'))" />
</Target>
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" />
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" />
<Import Project="..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.targets')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.targets')" />
<Import Project="..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.targets')" />
<Import Project="..\packages\MSTest.TestFramework.3.10.3\build\net462\MSTest.TestFramework.targets" Condition="Exists('..\packages\MSTest.TestFramework.3.10.3\build\net462\MSTest.TestFramework.targets')" />
</Project>

View File

@@ -4,7 +4,7 @@
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NuGet.Frameworks" publicKeyToken="31bf3856ad364e35" culture="neutral" />
@@ -12,23 +12,31 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.8" newVersion="9.0.0.8" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.ApplicationInsights" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.22.0.997" newVersion="2.22.0.997" />
<bindingRedirect oldVersion="0.0.0.0-2.23.0.29" newVersion="2.23.0.29" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.8" newVersion="9.0.0.8" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.8" newVersion="9.0.0.8" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.4.0" newVersion="4.2.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>

View File

@@ -1,23 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="5.1.1" targetFramework="net48" />
<package id="Microsoft.ApplicationInsights" version="2.22.0" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.Telemetry" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.TrxReport.Abstractions" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.VSTestBridge" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform.MSBuild" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.TestPlatform.ObjectModel" version="17.9.0" targetFramework="net48" />
<package id="Moq" version="4.20.70" targetFramework="net48" />
<package id="MSTest.TestAdapter" version="3.2.2" targetFramework="net48" />
<package id="MSTest.TestFramework" version="3.2.2" targetFramework="net48" />
<package id="NuGet.Frameworks" version="6.9.1" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Collections.Immutable" version="8.0.0" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="8.0.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.5" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.Reflection.Metadata" version="8.0.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
<package id="Castle.Core" version="5.2.1" targetFramework="net48" />
<package id="Microsoft.ApplicationInsights" version="2.23.0" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.Telemetry" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.TrxReport.Abstractions" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.VSTestBridge" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.Testing.Platform" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.Testing.Platform.MSBuild" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.TestPlatform.AdapterUtilities" version="17.14.1" targetFramework="net48" />
<package id="Microsoft.TestPlatform.ObjectModel" version="17.14.1" targetFramework="net48" />
<package id="Moq" version="4.20.72" targetFramework="net48" />
<package id="MSTest.Analyzers" version="3.10.3" targetFramework="net48" developmentDependency="true" />
<package id="MSTest.TestAdapter" version="3.10.3" targetFramework="net48" />
<package id="MSTest.TestFramework" version="3.10.3" targetFramework="net48" />
<package id="NuGet.Frameworks" version="6.14.0" targetFramework="net48" />
<package id="System.Buffers" version="4.6.1" targetFramework="net48" />
<package id="System.Collections.Immutable" version="9.0.8" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="9.0.8" targetFramework="net48" />
<package id="System.Memory" version="4.6.3" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net48" />
<package id="System.Reflection.Metadata" version="9.0.8" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net48" />
</packages>

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -8,7 +8,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
@@ -43,42 +45,62 @@ namespace SafeExamBrowser.Applications
public FactoryResult TryCreate(WhitelistApplication settings, out IApplication<IApplicationWindow> application)
{
var name = $"'{settings.DisplayName}' ({settings.ExecutableName})";
var result = FactoryResult.Error;
application = default;
try
{
var success = TryFindApplication(settings, out var executablePath);
var found = TryFindApplication(settings, out var executablePath);
var valid = found && VerifyApplication(executablePath, name, settings);
if (success)
if (found && valid)
{
application = BuildApplication(executablePath, settings);
application.Initialize();
logger.Debug($"Successfully initialized application {name}.");
return FactoryResult.Success;
application = InitializeApplication(executablePath, settings);
}
logger.Error($"Could not find application {name}!");
return FactoryResult.NotFound;
result = DetermineResult(name, found, valid);
}
catch (Exception e)
{
logger.Error($"Unexpected error while trying to initialize application {name}!", e);
}
return FactoryResult.Error;
return result;
}
private IApplication<IApplicationWindow> BuildApplication(string executablePath, WhitelistApplication settings)
private FactoryResult DetermineResult(string name, bool found, bool valid)
{
var result = default(FactoryResult);
if (!found)
{
result = FactoryResult.NotFound;
logger.Error($"Could not find application {name}!");
}
else if (!valid)
{
result = FactoryResult.Invalid;
logger.Error($"The application {name} is not valid or has been manipulated!");
}
else
{
result = FactoryResult.Success;
logger.Debug($"Successfully initialized application {name}.");
}
return result;
}
private IApplication<IApplicationWindow> InitializeApplication(string executablePath, WhitelistApplication settings)
{
const int ONE_SECOND = 1000;
var applicationLogger = logger.CloneFor(settings.DisplayName);
var application = new ExternalApplication(applicationMonitor, executablePath, applicationLogger, nativeMethods, processFactory, settings, ONE_SECOND);
application.Initialize();
return application;
}
@@ -143,5 +165,99 @@ namespace SafeExamBrowser.Applications
return default;
}
private bool VerifyApplication(string executablePath, string name, WhitelistApplication settings)
{
var valid = true;
valid &= VerifyName(executablePath, name, settings);
valid &= VerifyOriginalName(executablePath, name, settings);
valid &= VerifySignature(executablePath, name, settings);
return valid;
}
private bool VerifyName(string executablePath, string name, WhitelistApplication settings)
{
var valid = Path.GetFileName(executablePath).Equals(settings.ExecutableName, StringComparison.OrdinalIgnoreCase);
if (!valid)
{
logger.Warn($"The executable name of application {name} at '{executablePath}' does not match the configured value!");
}
return valid;
}
private bool VerifyOriginalName(string executablePath, string name, WhitelistApplication settings)
{
var ignoreOriginalName = string.IsNullOrWhiteSpace(settings.OriginalName);
var valid = ignoreOriginalName;
if (!ignoreOriginalName && TryLoadOriginalName(executablePath, out var originalName))
{
valid = originalName.Equals(settings.OriginalName, StringComparison.OrdinalIgnoreCase);
}
if (!valid)
{
logger.Warn($"The original name of application {name} at '{executablePath}' does not match the configured value!");
}
return valid;
}
private bool VerifySignature(string executablePath, string name, WhitelistApplication settings)
{
var ignoreSignature = string.IsNullOrWhiteSpace(settings.Signature);
var valid = ignoreSignature;
if (!ignoreSignature && TryLoadSignature(executablePath, out var signature))
{
valid = signature.Equals(settings.Signature, StringComparison.OrdinalIgnoreCase);
}
if (!valid)
{
logger.Warn($"The signature of application {name} at '{executablePath}' does not match the configured value!");
}
return valid;
}
private bool TryLoadOriginalName(string path, out string originalName)
{
originalName = default;
try
{
originalName = FileVersionInfo.GetVersionInfo(path).OriginalFilename;
}
catch (Exception e)
{
logger.Error($"Failed to load original name for '{path}'!", e);
}
return originalName != default;
}
private bool TryLoadSignature(string path, out string signature)
{
signature = default;
try
{
using (var certificate = X509Certificate.CreateFromSignedFile(path))
{
signature = certificate.GetCertHashString();
}
}
catch (Exception e)
{
logger.Error($"Failed to load signature for '{path}'!", e);
}
return signature != default;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Applications")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2025 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Browser.Contracts")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2025 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla internal
* License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -102,13 +102,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters
}
[TestMethod]
[ExpectedException(typeof(NotImplementedException))]
public void MustNotAllowUnsupportedResult()
{
var rule = new Mock<IRule>();
rule.SetupGet(r => r.Result).Returns((FilterResult) (-1));
sut.Load(rule.Object);
Assert.ThrowsExactly<NotImplementedException>(() => sut.Load(rule.Object));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -33,10 +33,9 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters
}
[TestMethod]
[ExpectedException(typeof(NotImplementedException))]
public void MustNotAllowUnsupportedFilterType()
{
sut.CreateRule((FilterRuleType) (-1));
Assert.ThrowsExactly<NotImplementedException>(() => sut.CreateRule((FilterRuleType) (-1)));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -51,17 +51,15 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MustNotAllowUndefinedExpression()
{
sut.Initialize(new FilterRuleSettings());
Assert.ThrowsExactly<ArgumentNullException>(() => sut.Initialize(new FilterRuleSettings()));
}
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void MustValidateExpression()
{
sut.Initialize(new FilterRuleSettings { Expression = "ç+\"}%&*/(+)=?{=*+¦]@#°§]`?´^¨'°[¬|¢" });
Assert.ThrowsExactly<ArgumentException>(() => sut.Initialize(new FilterRuleSettings { Expression = "ç+\"}%&*/(+)=?{=*+¦]@#°§]`?´^¨'°[¬|¢" }));
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -51,10 +51,9 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void MustNotAllowUndefinedExpression()
{
sut.Initialize(new FilterRuleSettings());
Assert.ThrowsExactly<ArgumentNullException>(() => sut.Initialize(new FilterRuleSettings()));
}
[TestMethod]
@@ -73,7 +72,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Filters.Rules
foreach (var expression in invalid)
{
Assert.ThrowsException<ArgumentException>(() => sut.Initialize(new FilterRuleSettings { Expression = expression }));
Assert.ThrowsExactly<ArgumentException>(() => sut.Initialize(new FilterRuleSettings { Expression = expression }));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -80,7 +80,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
threadId = Thread.CurrentThread.ManagedThreadId;
};
var status = sut.OnFileDialog(default, default, mode, title, initialPath, default, callback.Object);
var status = sut.OnFileDialog(default, default, mode, title, initialPath, default, default, default, callback.Object);
sync.WaitOne();

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -108,7 +108,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
};
sut.DownloadUpdated += (state) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sut.OnBeforeDownload(default, default, item, callback.Object);
sync.WaitOne();
callback.VerifyNoOtherCalls();
@@ -145,7 +145,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
sut.ConfigurationDownloadRequested += (f, a) => failed = true;
sut.DownloadUpdated += (state) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sut.OnBeforeDownload(default, default, item, callback.Object);
sync.WaitOne();
callback.Verify(c => c.Continue(It.Is<string>(p => p.Equals(downloadPath)), false), Times.Once);
@@ -180,7 +180,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
sut.ConfigurationDownloadRequested += (f, a) => failed = true;
sut.DownloadUpdated += (state) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sut.OnBeforeDownload(default, default, item, callback.Object);
sync.WaitOne();
var downloadPath = Path.Combine(Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory), item.SuggestedFileName);
@@ -202,12 +202,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
Url = "https://somehost.org/somefile.abc"
};
appConfig.ConfigurationFileMimeType = "application/seb";
settings.AllowDownloads = false;
settings.AllowConfigurationDownloads = false;
sut.ConfigurationDownloadRequested += (file, args) => fail = true;
sut.DownloadUpdated += (state) => fail = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sut.OnBeforeDownload(default, default, item, callback.Object);
callback.VerifyNoOtherCalls();
Assert.IsFalse(fail);
@@ -233,7 +234,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
settings.AllowConfigurationDownloads = false;
sut.ConfigurationDownloadRequested += (f, a) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sut.OnBeforeDownload(default, default, item, callback.Object);
sync.WaitOne();
Assert.IsFalse(failed);
@@ -246,7 +247,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
};
item.PercentComplete = 10;
sut.OnDownloadUpdated(default(IWebBrowser), default(IBrowser), item, default(IDownloadItemCallback));
sut.OnDownloadUpdated(default, default, item, default);
sync.WaitOne();
Assert.IsFalse(state.IsCancelled);
@@ -255,7 +256,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
Assert.AreNotEqual(Thread.CurrentThread.ManagedThreadId, threadId);
item.PercentComplete = 20;
sut.OnDownloadUpdated(default(IWebBrowser), default(IBrowser), item, default(IDownloadItemCallback));
sut.OnDownloadUpdated(default, default, item, default);
sync.WaitOne();
Assert.IsFalse(state.IsCancelled);
@@ -265,7 +266,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
item.PercentComplete = 50;
item.IsCancelled = true;
sut.OnDownloadUpdated(default(IWebBrowser), default(IBrowser), item, default(IDownloadItemCallback));
sut.OnDownloadUpdated(default, default, item, default);
sync.WaitOne();
Assert.IsFalse(failed);
@@ -297,7 +298,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
};
sut.DownloadUpdated += (state) => failed = true;
sut.OnBeforeDownload(default(IWebBrowser), default(IBrowser), item, callback.Object);
sut.OnBeforeDownload(default, default, item, callback.Object);
sync.WaitOne();
callback.Verify(c => c.Continue(It.Is<string>(p => p.Equals(args.DownloadPath)), false), Times.Once);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -12,6 +12,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Browser.Integrations;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
@@ -41,6 +42,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
[TestInitialize]
public void Initialize()
{
var integrations = new Integration[]
{
new GenericIntegration(new Mock<ILogger>().Object),
new EdxIntegration(new Mock<ILogger>().Object),
new MoodleIntegration(new Mock<ILogger>().Object)
};
appConfig = new AppConfig();
filter = new Mock<IRequestFilter>();
keyGenerator = new Mock<IKeyGenerator>();
@@ -48,7 +56,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
settings = new BrowserSettings();
windowSettings = new WindowSettings();
text = new Mock<IText>();
resourceHandler = new ResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, default, settings, windowSettings, text.Object);
resourceHandler = new ResourceHandler(appConfig, filter.Object, integrations, keyGenerator.Object, logger.Object, default, settings, windowSettings, text.Object);
sut = new TestableRequestHandler(appConfig, filter.Object, logger.Object, resourceHandler, settings, windowSettings);
}
@@ -285,7 +293,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
private class TestableRequestHandler : RequestHandler
{
internal TestableRequestHandler(AppConfig appConfig, IRequestFilter filter, ILogger logger, ResourceHandler resourceHandler, BrowserSettings settings, WindowSettings windowSettings) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings)
internal TestableRequestHandler(
AppConfig appConfig,
IRequestFilter filter,
ILogger logger,
ResourceHandler resourceHandler,
BrowserSettings settings,
WindowSettings windowSettings) : base(appConfig, filter, logger, resourceHandler, settings, windowSettings)
{
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -7,6 +7,7 @@
*/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net.Mime;
using System.Threading;
@@ -14,6 +15,7 @@ using CefSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Integrations;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
@@ -42,6 +44,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
[TestInitialize]
public void Initialize()
{
var integrations = new Integration[]
{
new GenericIntegration(new Mock<ILogger>().Object),
new EdxIntegration(new Mock<ILogger>().Object),
new MoodleIntegration(new Mock<ILogger>().Object)
};
appConfig = new AppConfig();
filter = new Mock<IRequestFilter>();
keyGenerator = new Mock<IKeyGenerator>();
@@ -50,7 +59,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
windowSettings = new WindowSettings();
text = new Mock<IText>();
sut = new TestableResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, SessionMode.Server, settings, windowSettings, text.Object);
sut = new TestableResourceHandler(appConfig, filter.Object, integrations, keyGenerator.Object, logger.Object, SessionMode.Server, settings, windowSettings, text.Object);
}
[TestMethod]
@@ -325,12 +334,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
internal TestableResourceHandler(
AppConfig appConfig,
IRequestFilter filter,
IEnumerable<Integration> integrations,
IKeyGenerator keyGenerator,
ILogger logger,
SessionMode sessionMode,
BrowserSettings settings,
WindowSettings windowSettings,
IText text) : base(appConfig, filter, keyGenerator, logger, sessionMode, settings, windowSettings, text)
IText text) : base(appConfig, filter, integrations, keyGenerator, logger, sessionMode, settings, windowSettings, text)
{
}

View File

@@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Browser.UnitTests")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2025 ETH Zürich, IT Services")]
[assembly: ComVisible(false)]

View File

@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" />
<Import Project="..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props" Condition="Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" />
<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\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.props')" />
<Import Project="..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props" Condition="Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.props')" />
<Import Project="..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.props" Condition="Exists('..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.props')" />
<Import Project="..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.props" Condition="Exists('..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.props')" />
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x86.139.0.28\build\chromiumembeddedframework.runtime.win-x86.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x86.139.0.28\build\chromiumembeddedframework.runtime.win-x86.props')" />
<Import Project="..\packages\chromiumembeddedframework.runtime.win-x64.139.0.28\build\chromiumembeddedframework.runtime.win-x64.props" Condition="Exists('..\packages\chromiumembeddedframework.runtime.win-x64.139.0.28\build\chromiumembeddedframework.runtime.win-x64.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -65,84 +66,88 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.5.1.1\lib\net462\Castle.Core.dll</HintPath>
<HintPath>..\packages\Castle.Core.5.2.1\lib\net462\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="CefSharp, Version=121.3.130.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
<HintPath>..\packages\CefSharp.Common.121.3.130\lib\net462\CefSharp.dll</HintPath>
<Reference Include="CefSharp, Version=139.0.280.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
<HintPath>..\packages\CefSharp.Common.139.0.280\lib\net462\CefSharp.dll</HintPath>
</Reference>
<Reference Include="CefSharp.Core, Version=121.3.130.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
<HintPath>..\packages\CefSharp.Common.121.3.130\lib\net462\CefSharp.Core.dll</HintPath>
<Reference Include="CefSharp.Core, Version=139.0.280.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL">
<HintPath>..\packages\CefSharp.Common.139.0.280\lib\net462\CefSharp.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ApplicationInsights, Version=2.22.0.997, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.22.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
<Reference Include="Microsoft.ApplicationInsights, Version=2.23.0.29, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.23.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Testing.Extensions.Telemetry, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.dll</HintPath>
<Reference Include="Microsoft.Testing.Extensions.MSBuild, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\lib\netstandard2.0\Microsoft.Testing.Extensions.MSBuild.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.TrxReport.Abstractions, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.TrxReport.Abstractions.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.TrxReport.Abstractions.dll</HintPath>
<Reference Include="Microsoft.Testing.Extensions.Telemetry, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\lib\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Extensions.VSTestBridge, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.VSTestBridge.1.0.2\lib\netstandard2.0\Microsoft.Testing.Extensions.VSTestBridge.dll</HintPath>
<Reference Include="Microsoft.Testing.Extensions.TrxReport.Abstractions, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.TrxReport.Abstractions.1.8.3\lib\netstandard2.0\Microsoft.Testing.Extensions.TrxReport.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.dll</HintPath>
<Reference Include="Microsoft.Testing.Extensions.VSTestBridge, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Extensions.VSTestBridge.1.8.3\lib\netstandard2.0\Microsoft.Testing.Extensions.VSTestBridge.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Testing.Platform.MSBuild, Version=1.0.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\lib\netstandard2.0\Microsoft.Testing.Platform.MSBuild.dll</HintPath>
<Reference Include="Microsoft.Testing.Platform, Version=1.8.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Testing.Platform.1.8.3\lib\netstandard2.0\Microsoft.Testing.Platform.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.AdapterUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.AdapterUtilities.17.14.1\lib\net462\Microsoft.TestPlatform.AdapterUtilities.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.TestPlatform.CoreUtilities, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.14.1\lib\net462\Microsoft.TestPlatform.CoreUtilities.dll</HintPath>
</Reference>
<Reference Include="Microsoft.TestPlatform.PlatformAbstractions, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.14.1\lib\net462\Microsoft.TestPlatform.PlatformAbstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.ObjectModel, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.9.0\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
<HintPath>..\packages\Microsoft.TestPlatform.ObjectModel.17.14.1\lib\net462\Microsoft.VisualStudio.TestPlatform.ObjectModel.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
<HintPath>..\packages\MSTest.TestFramework.3.10.3\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.3.2.2\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
<HintPath>..\packages\MSTest.TestFramework.3.10.3\lib\net462\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.20.70.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.20.70\lib\net462\Moq.dll</HintPath>
<Reference Include="Moq, Version=4.20.72.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.20.72\lib\net462\Moq.dll</HintPath>
</Reference>
<Reference Include="NuGet.Frameworks, Version=6.9.1.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\NuGet.Frameworks.6.9.1\lib\net472\NuGet.Frameworks.dll</HintPath>
<Reference Include="NuGet.Frameworks, Version=6.14.0.116, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\NuGet.Frameworks.6.14.0\lib\net472\NuGet.Frameworks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll</HintPath>
<Reference Include="System.Collections.Immutable, Version=9.0.0.8, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.9.0.8\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.0\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
<Reference Include="System.Diagnostics.DiagnosticSource, Version=9.0.0.8, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.9.0.8\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
<Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
<Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Reflection.Metadata, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.8.0.0\lib\net462\System.Reflection.Metadata.dll</HintPath>
<Reference Include="System.Reflection.Metadata, Version=9.0.0.8, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Reflection.Metadata.9.0.8\lib\net462\System.Reflection.Metadata.dll</HintPath>
</Reference>
<Reference Include="System.Runtime" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
@@ -198,23 +203,32 @@
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\MSTest.Analyzers.3.10.3\analyzers\dotnet\cs\MSTest.Analyzers.CodeFixes.dll" />
<Analyzer Include="..\packages\MSTest.Analyzers.3.10.3\analyzers\dotnet\cs\MSTest.Analyzers.dll" />
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.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\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'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Extensions.Telemetry.1.0.2\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets'))" />
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x64.139.0.28\build\chromiumembeddedframework.runtime.win-x64.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x64.139.0.28\build\chromiumembeddedframework.runtime.win-x64.props'))" />
<Error Condition="!Exists('..\packages\chromiumembeddedframework.runtime.win-x86.139.0.28\build\chromiumembeddedframework.runtime.win-x86.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\chromiumembeddedframework.runtime.win-x86.139.0.28\build\chromiumembeddedframework.runtime.win-x86.props'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Testing.Extensions.Telemetry.1.8.3\build\netstandard2.0\Microsoft.Testing.Extensions.Telemetry.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.targets'))" />
<Error Condition="!Exists('..\packages\MSTest.TestFramework.3.10.3\build\net462\MSTest.TestFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestFramework.3.10.3\build\net462\MSTest.TestFramework.targets'))" />
</Target>
<Import Project="..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets" Condition="Exists('..\packages\CefSharp.Common.121.3.130\build\CefSharp.Common.targets')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.0.2\build\netstandard2.0\Microsoft.Testing.Platform.MSBuild.targets')" />
<Import Project="..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.3.2.2\build\net462\MSTest.TestAdapter.targets')" />
<Import Project="..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.targets" Condition="Exists('..\packages\CefSharp.Common.139.0.280\build\CefSharp.Common.targets')" />
<Import Project="..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.1.8.3\build\netstandard2.0\Microsoft.Testing.Platform.targets')" />
<Import Project="..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.targets" Condition="Exists('..\packages\Microsoft.Testing.Platform.MSBuild.1.8.3\build\Microsoft.Testing.Platform.MSBuild.targets')" />
<Import Project="..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.3.10.3\build\net462\MSTest.TestAdapter.targets')" />
<Import Project="..\packages\MSTest.TestFramework.3.10.3\build\net462\MSTest.TestFramework.targets" Condition="Exists('..\packages\MSTest.TestFramework.3.10.3\build\net462\MSTest.TestFramework.targets')" />
</Project>

View File

@@ -4,11 +4,11 @@
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
<bindingRedirect oldVersion="0.0.0.0-4.2.4.0" newVersion="4.2.4.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Security.Principal.Windows" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
@@ -16,11 +16,11 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="CefSharp" publicKeyToken="40c4b6fc221f4138" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-118.6.80.0" newVersion="118.6.80.0" />
<bindingRedirect oldVersion="0.0.0.0-138.0.170.0" newVersion="138.0.170.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="CefSharp.Core" publicKeyToken="40c4b6fc221f4138" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-118.6.80.0" newVersion="118.6.80.0" />
<bindingRedirect oldVersion="0.0.0.0-138.0.170.0" newVersion="138.0.170.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NuGet.Frameworks" publicKeyToken="31bf3856ad364e35" culture="neutral" />
@@ -28,23 +28,27 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.8" newVersion="9.0.0.8" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.ApplicationInsights" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.22.0.997" newVersion="2.22.0.997" />
<bindingRedirect oldVersion="0.0.0.0-2.23.0.29" newVersion="2.23.0.29" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.8" newVersion="9.0.0.8" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.8" newVersion="9.0.0.8" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>

View File

@@ -1,26 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Castle.Core" version="5.1.1" 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" />
<package id="Microsoft.ApplicationInsights" version="2.22.0" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.Telemetry" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.TrxReport.Abstractions" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.VSTestBridge" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.Testing.Platform.MSBuild" version="1.0.2" targetFramework="net48" />
<package id="Microsoft.TestPlatform.ObjectModel" version="17.9.0" targetFramework="net48" />
<package id="Moq" version="4.20.70" targetFramework="net48" />
<package id="MSTest.TestAdapter" version="3.2.2" targetFramework="net48" />
<package id="MSTest.TestFramework" version="3.2.2" targetFramework="net48" />
<package id="NuGet.Frameworks" version="6.9.1" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Collections.Immutable" version="8.0.0" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="8.0.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.5" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.Reflection.Metadata" version="8.0.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
<package id="Castle.Core" version="5.2.1" targetFramework="net48" />
<package id="CefSharp.Common" version="139.0.280" targetFramework="net48" />
<package id="chromiumembeddedframework.runtime.win-x64" version="139.0.28" targetFramework="net48" />
<package id="chromiumembeddedframework.runtime.win-x86" version="139.0.28" targetFramework="net48" />
<package id="Microsoft.ApplicationInsights" version="2.23.0" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.Telemetry" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.TrxReport.Abstractions" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.Testing.Extensions.VSTestBridge" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.Testing.Platform" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.Testing.Platform.MSBuild" version="1.8.3" targetFramework="net48" />
<package id="Microsoft.TestPlatform.AdapterUtilities" version="17.14.1" targetFramework="net48" />
<package id="Microsoft.TestPlatform.ObjectModel" version="17.14.1" targetFramework="net48" />
<package id="Moq" version="4.20.72" targetFramework="net48" />
<package id="MSTest.Analyzers" version="3.10.3" targetFramework="net48" developmentDependency="true" />
<package id="MSTest.TestAdapter" version="3.10.3" targetFramework="net48" />
<package id="MSTest.TestFramework" version="3.10.3" targetFramework="net48" />
<package id="NuGet.Frameworks" version="6.14.0" targetFramework="net48" />
<package id="System.Buffers" version="4.6.1" targetFramework="net48" />
<package id="System.Collections.Immutable" version="9.0.8" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="9.0.8" targetFramework="net48" />
<package id="System.Memory" version="4.6.3" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net48" />
<package id="System.Reflection.Metadata" version="9.0.8" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net48" />
</packages>

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -18,6 +18,7 @@ using SafeExamBrowser.Applications.Contracts.Events;
using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Integrations;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
@@ -116,17 +117,10 @@ namespace SafeExamBrowser.Browser
if (success)
{
InitializeCookies();
InitializeDownAndUploadDirectory();
InitializeIntegrityKeys();
if (settings.DeleteCookiesOnStartup)
{
DeleteCookies();
}
if (settings.UseTemporaryDownAndUploadDirectory)
{
CreateTemporaryDownAndUploadDirectory();
}
InitializePreferences();
logger.Info("Initialized browser.");
}
@@ -144,36 +138,23 @@ namespace SafeExamBrowser.Browser
public void Terminate()
{
logger.Info("Initiating termination...");
AwaitReady();
foreach (var window in windows)
{
window.Closed -= Window_Closed;
window.Close();
logger.Info($"Closed browser window #{window.Id}.");
}
if (settings.UseTemporaryDownAndUploadDirectory)
{
DeleteTemporaryDownAndUploadDirectory();
}
if (settings.DeleteCookiesOnShutdown)
{
DeleteCookies();
}
FinalizeCookies();
FinalizeDownAndUploadDirectory();
Cef.Shutdown();
logger.Info("Terminated browser.");
FinalizeCache();
if (settings.DeleteCacheOnShutdown && settings.DeleteCookiesOnShutdown)
{
DeleteCache();
}
else
{
logger.Info("Retained browser cache.");
}
logger.Info("Terminated browser.");
}
private void AwaitReady()
@@ -188,6 +169,12 @@ namespace SafeExamBrowser.Browser
private void CreateNewWindow(PopupRequestedEventArgs args = default)
{
var id = ++windowIdCounter;
var integrations = new Integration[]
{
new GenericIntegration(logger.CloneFor($"{nameof(GenericIntegration)} #{id}")),
new EdxIntegration(logger.CloneFor($"{nameof(EdxIntegration)} #{id}")),
new MoodleIntegration(logger.CloneFor($"{nameof(MoodleIntegration)} #{id}"))
};
var isMainWindow = windows.Count == 0;
var startUrl = GenerateStartUrl();
var windowLogger = logger.CloneFor($"Browser Window #{id}");
@@ -197,6 +184,7 @@ namespace SafeExamBrowser.Browser
fileSystemDialog,
hashAlgorithm,
id,
integrations,
isMainWindow,
keyGenerator,
windowLogger,
@@ -218,7 +206,7 @@ namespace SafeExamBrowser.Browser
window.InitializeControl();
windows.Add(window);
if (args != default(PopupRequestedEventArgs))
if (args != default)
{
args.Window = window;
}
@@ -231,49 +219,10 @@ namespace SafeExamBrowser.Browser
WindowsChanged?.Invoke();
}
private void CreateTemporaryDownAndUploadDirectory()
{
try
{
settings.DownAndUploadDirectory = Path.Combine(appConfig.TemporaryDirectory, Path.GetRandomFileName());
Directory.CreateDirectory(settings.DownAndUploadDirectory);
logger.Info($"Created temporary down- and upload directory.");
}
catch (Exception e)
{
logger.Error("Failed to create temporary down- and upload directory!", e);
}
}
private void DeleteTemporaryDownAndUploadDirectory()
{
try
{
Directory.Delete(settings.DownAndUploadDirectory, true);
logger.Info("Deleted temporary down- and upload directory.");
}
catch (Exception e)
{
logger.Error("Failed to delete temporary down- and upload directory!", e);
}
}
private void DeleteCache()
{
try
{
Directory.Delete(appConfig.BrowserCachePath, true);
logger.Info("Deleted browser cache.");
}
catch (Exception e)
{
logger.Error("Failed to delete browser cache!", e);
}
}
private void DeleteCookies()
{
var callback = new TaskDeleteCookiesCallback();
var cookieManager = Cef.GetGlobalCookieManager();
callback.Task.ContinueWith(task =>
{
@@ -287,7 +236,7 @@ namespace SafeExamBrowser.Browser
}
});
if (Cef.GetGlobalCookieManager().DeleteCookies(callback: callback))
if (cookieManager != default && cookieManager.DeleteCookies(callback: callback))
{
logger.Debug("Successfully initiated cookie deletion.");
}
@@ -297,6 +246,50 @@ namespace SafeExamBrowser.Browser
}
}
private void FinalizeCache()
{
if (settings.DeleteCacheOnShutdown && settings.DeleteCookiesOnShutdown)
{
try
{
Directory.Delete(appConfig.BrowserCachePath, true);
logger.Info("Deleted browser cache.");
}
catch (Exception e)
{
logger.Error("Failed to delete browser cache!", e);
}
}
else
{
logger.Info("Retained browser cache.");
}
}
private void FinalizeCookies()
{
if (settings.DeleteCookiesOnShutdown)
{
DeleteCookies();
}
}
private void FinalizeDownAndUploadDirectory()
{
if (settings.UseTemporaryDownAndUploadDirectory)
{
try
{
Directory.Delete(settings.DownAndUploadDirectory, true);
logger.Info("Deleted temporary down- and upload directory.");
}
catch (Exception e)
{
logger.Error("Failed to delete temporary down- and upload directory!", e);
}
}
}
private string GenerateStartUrl()
{
var url = settings.StartUrl;
@@ -333,7 +326,6 @@ namespace SafeExamBrowser.Browser
cefSettings.AcceptLanguageList = CultureInfo.CurrentUICulture.Name;
cefSettings.CachePath = appConfig.BrowserCachePath;
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
cefSettings.LogFile = appConfig.BrowserLogFilePath;
cefSettings.LogSeverity = error ? LogSeverity.Error : (warning ? LogSeverity.Warning : LogSeverity.Info);
cefSettings.PersistSessionCookies = !settings.DeleteCookiesOnStartup || !settings.DeleteCookiesOnShutdown;
@@ -356,6 +348,7 @@ namespace SafeExamBrowser.Browser
cefSettings.CefCommandLineArgs.Add("enable-media-stream");
cefSettings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");
cefSettings.CefCommandLineArgs.Add("touch-events", "enabled");
cefSettings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");
InitializeProxySettings(cefSettings);
@@ -371,6 +364,53 @@ namespace SafeExamBrowser.Browser
return cefSettings;
}
private void InitializeCookies()
{
if (settings.DeleteCookiesOnStartup)
{
DeleteCookies();
}
}
private void InitializeDownAndUploadDirectory()
{
if (settings.UseTemporaryDownAndUploadDirectory)
{
InitializeTemporaryDownAndUploadDirectory();
}
else if (!string.IsNullOrEmpty(settings.DownAndUploadDirectory))
{
InitializeCustomDownAndUploadDirectory();
}
}
private void InitializeCustomDownAndUploadDirectory()
{
if (!Directory.Exists(Environment.ExpandEnvironmentVariables(settings.DownAndUploadDirectory)))
{
logger.Warn("The configured down- and upload directory does not exist! Falling back to the default directory...");
settings.DownAndUploadDirectory = default;
}
else
{
logger.Debug("Using custom down- and upload directory as defined in the active configuration.");
}
}
private void InitializeTemporaryDownAndUploadDirectory()
{
try
{
settings.DownAndUploadDirectory = Path.Combine(appConfig.TemporaryDirectory, Path.GetRandomFileName());
Directory.CreateDirectory(settings.DownAndUploadDirectory);
logger.Info($"Created temporary down- and upload directory.");
}
catch (Exception e)
{
logger.Error("Failed to create temporary down- and upload directory!", e);
}
}
private void InitializeIntegrityKeys()
{
logger.Debug($"Browser Exam Key (BEK) transmission is {(settings.SendBrowserExamKey ? "enabled" : "disabled")}.");
@@ -387,6 +427,18 @@ namespace SafeExamBrowser.Browser
}
}
private void InitializePreferences()
{
Cef.UIThreadTaskFactory.StartNew(() =>
{
using (var requestContext = Cef.GetGlobalRequestContext())
{
requestContext.SetPreference("autofill.credit_card_enabled", false, out _);
requestContext.SetPreference("autofill.profile_enabled", false, out _);
}
});
}
private void InitializeProxySettings(CefSettings cefSettings)
{
if (settings.Proxy.Policy == ProxyPolicy.Custom)
@@ -476,6 +528,7 @@ namespace SafeExamBrowser.Browser
private void Window_ResetRequested()
{
logger.Info("Attempting to reset browser...");
AwaitReady();
foreach (var window in windows)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -23,9 +23,13 @@ namespace SafeExamBrowser.Browser
{
private readonly Clipboard clipboard;
private readonly ICefSharpControl control;
private readonly IContextMenuHandler contextMenuHandler;
private readonly IDialogHandler dialogHandler;
private readonly IDisplayHandler displayHandler;
private readonly IDownloadHandler downloadHandler;
private readonly IDragHandler dragHandler;
private readonly IFocusHandler focusHandler;
private readonly IJsDialogHandler javaScriptDialogHandler;
private readonly IKeyboardHandler keyboardHandler;
private readonly ILogger logger;
private readonly IRenderProcessMessageHandler renderProcessMessageHandler;
@@ -44,19 +48,27 @@ namespace SafeExamBrowser.Browser
public BrowserControl(
Clipboard clipboard,
ICefSharpControl control,
IContextMenuHandler contextMenuHandler,
IDialogHandler dialogHandler,
IDisplayHandler displayHandler,
IDownloadHandler downloadHandler,
IDragHandler dragHandler,
IFocusHandler focusHandler,
IJsDialogHandler javaScriptDialogHandler,
IKeyboardHandler keyboardHandler,
ILogger logger,
IRenderProcessMessageHandler renderProcessMessageHandler,
IRequestHandler requestHandler)
{
this.control = control;
this.clipboard = clipboard;
this.control = control;
this.contextMenuHandler = contextMenuHandler;
this.dialogHandler = dialogHandler;
this.displayHandler = displayHandler;
this.downloadHandler = downloadHandler;
this.dragHandler = dragHandler;
this.focusHandler = focusHandler;
this.javaScriptDialogHandler = javaScriptDialogHandler;
this.keyboardHandler = keyboardHandler;
this.logger = logger;
this.renderProcessMessageHandler = renderProcessMessageHandler;
@@ -67,6 +79,7 @@ namespace SafeExamBrowser.Browser
{
if (!control.IsDisposed)
{
control.CloseDevTools();
control.Dispose(true);
}
}
@@ -79,31 +92,20 @@ namespace SafeExamBrowser.Browser
{
control.BrowserCore.EvaluateScriptAsync(code).ContinueWith(t =>
{
callback?.Invoke(new JavaScriptResult
{
Message = t.Result.Message,
Result = t.Result.Result,
Success = t.Result.Success
});
callback?.Invoke(new JavaScriptResult { Message = t.Result.Message, Result = t.Result.Result, Success = t.Result.Success });
});
}
else
{
Task.Run(() => callback?.Invoke(new JavaScriptResult
{
Message = "JavaScript can't be executed in main frame!",
Success = false
}));
Task.Run(() => callback?.Invoke(new JavaScriptResult { Message = "Could not execute JavaScript in main frame!", Success = false }));
}
}
catch (Exception e)
{
logger.Error($"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'!", e);
Task.Run(() => callback?.Invoke(new JavaScriptResult
{
Message = $"Failed to execute JavaScript '{(code.Length > 50 ? code.Take(50) : code)}'! Reason: {e.Message}",
Success = false
}));
var message = "Failed to execute JavaScript in main frame!";
logger.Error(message, e);
Task.Run(() => callback?.Invoke(new JavaScriptResult { Message = $"{message} Reason: {e.Message}", Success = false }));
}
}
@@ -119,22 +121,35 @@ namespace SafeExamBrowser.Browser
control.AddressChanged += (o, e) => AddressChanged?.Invoke(e.Address);
control.AuthCredentialsRequired += (w, b, o, i, h, p, r, s, c, a) => a.Value = requestHandler.GetAuthCredentials(w, b, o, i, h, p, r, s, c);
control.BeforeBrowse += (w, b, f, r, u, i, a) => a.Value = requestHandler.OnBeforeBrowse(w, b, f, r, u, i);
control.BeforeDownload += (w, b, d, c) => downloadHandler.OnBeforeDownload(w, b, d, c);
control.BeforeContextMenu += (w, b, f, p, m) => contextMenuHandler.OnBeforeContextMenu(w, b, f, p, m);
control.BeforeDownload += (w, b, d, c, a) => a.Value = a.Value = downloadHandler.OnBeforeDownload(w, b, d, c);
control.BeforeUnloadDialog += (w, b, m, r, c, a) => a.Value = javaScriptDialogHandler.OnBeforeUnloadDialog(w, b, m, r, c);
control.CanDownload += (w, b, u, r, a) => a.Value = downloadHandler.CanDownload(w, b, u, r);
control.ContextCreated += (w, b, f) => renderProcessMessageHandler.OnContextCreated(w, b, f);
control.ContextMenuCommand += (w, b, f, p, c, e, a) => a.Value = contextMenuHandler.OnContextMenuCommand(w, b, f, p, c, e);
control.ContextMenuDismissed += (w, b, f) => contextMenuHandler.OnContextMenuDismissed(w, b, f);
control.ContextReleased += (w, b, f) => renderProcessMessageHandler.OnContextReleased(w, b, f);
control.DialogClosed += (w, b) => javaScriptDialogHandler.OnDialogClosed(w, b);
control.DownloadUpdated += (w, b, d, c) => downloadHandler.OnDownloadUpdated(w, b, d, c);
control.DragEnterCefSharp += (w, b, d, m, a) => a.Value = dragHandler.OnDragEnter(w, b, d, m);
control.DraggableRegionsChanged += (w, b, f, r) => dragHandler.OnDraggableRegionsChanged(w, b, f, r);
control.FaviconUrlChanged += (w, b, u) => displayHandler.OnFaviconUrlChange(w, b, u);
control.FileDialogRequested += (w, b, m, t, d, f, c) => dialogHandler.OnFileDialog(w, b, m, t, d, f, c);
control.FileDialogRequested += (w, b, m, t, p, f, e, d, c) => dialogHandler.OnFileDialog(w, b, m, t, p, f, e, d, c);
control.FocusedNodeChanged += (w, b, f, n) => renderProcessMessageHandler.OnFocusedNodeChanged(w, b, f, n);
control.GotFocusCefSharp += (w, b) => focusHandler.OnGotFocus(w, b);
control.IsBrowserInitializedChanged += Control_IsBrowserInitializedChanged;
control.JavaScriptDialog += (IWebBrowser w, IBrowser b, string u, CefJsDialogType t, string m, string p, IJsDialogCallback c, ref bool s, GenericEventArgs a) => a.Value = javaScriptDialogHandler.OnJSDialog(w, b, u, t, m, p, c, ref s);
control.KeyEvent += (w, b, t, k, n, m, s) => keyboardHandler.OnKeyEvent(w, b, t, k, n, m, s);
control.LoadError += (o, e) => LoadFailed?.Invoke((int) e.ErrorCode, e.ErrorText, e.Frame.IsMain, e.FailedUrl);
control.LoadingProgressChanged += (w, b, p) => displayHandler.OnLoadingProgressChange(w, b, p);
control.LoadingStateChanged += (o, e) => LoadingStateChanged?.Invoke(e.IsLoading);
control.OpenUrlFromTab += (w, b, f, u, t, g, a) => a.Value = requestHandler.OnOpenUrlFromTab(w, b, f, u, t, g);
control.PreKeyEvent += (IWebBrowser w, IBrowser b, KeyType t, int k, int n, CefEventFlags m, bool i, ref bool s, GenericEventArgs a) => a.Value = keyboardHandler.OnPreKeyEvent(w, b, t, k, n, m, i, ref s);
control.ResetDialogState += (w, b) => javaScriptDialogHandler.OnResetDialogState(w, b);
control.ResourceRequestHandlerRequired += (IWebBrowser w, IBrowser b, IFrame f, IRequest r, bool n, bool d, string i, ref bool h, ResourceRequestEventArgs a) => a.Handler = requestHandler.GetResourceRequestHandler(w, b, f, r, n, d, i, ref h);
control.RunContextMenu += (w, b, f, p, m, c, a) => a.Value = contextMenuHandler.RunContextMenu(w, b, f, p, m, c);
control.SetFocus += (w, b, s, a) => a.Value = focusHandler.OnSetFocus(w, b, s);
control.TakeFocus += (w, b, n) => focusHandler.OnTakeFocus(w, b, n);
control.TitleChanged += (o, e) => TitleChanged?.Invoke(e.Title);
control.UncaughtExceptionEvent += (w, b, f, e) => renderProcessMessageHandler.OnUncaughtException(w, b, f, e);
@@ -174,9 +189,21 @@ namespace SafeExamBrowser.Browser
control.BrowserCore.SetZoomLevel(level);
}
private void Clipboard_Changed(long id)
private void Clipboard_Changed(string id)
{
ExecuteJavaScript($"SafeExamBrowser.clipboard.update({id}, '{clipboard.Content}');");
try
{
var script = $"SafeExamBrowser.clipboard.update('{id}', '{clipboard.Content}');";
foreach (var frame in control.BrowserCore?.GetAllFrames() ?? Enumerable.Empty<IFrame>())
{
frame.EvaluateScriptAsync(script);
}
}
catch (Exception e)
{
logger.Error($"Failed to update JavaScript clipboard!", e);
}
}
private void Control_IsBrowserInitializedChanged(object sender, EventArgs e)
@@ -189,7 +216,7 @@ namespace SafeExamBrowser.Browser
private void WebBrowser_JavascriptMessageReceived(object sender, JavascriptMessageReceivedEventArgs e)
{
clipboard.Process(e);
clipboard.Update(e);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -19,6 +19,7 @@ using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Events;
using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Browser.Integrations;
using SafeExamBrowser.Browser.Wrapper;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
@@ -52,6 +53,7 @@ namespace SafeExamBrowser.Browser
private readonly IFileSystemDialog fileSystemDialog;
private readonly IHashAlgorithm hashAlgorithm;
private readonly HttpClient httpClient;
private readonly IEnumerable<Integration> integrations;
private readonly IKeyGenerator keyGenerator;
private readonly IModuleLogger logger;
private readonly IMessageBox messageBox;
@@ -97,6 +99,7 @@ namespace SafeExamBrowser.Browser
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
int id,
IEnumerable<Integration> integrations,
bool isMainWindow,
IKeyGenerator keyGenerator,
IModuleLogger logger,
@@ -113,6 +116,7 @@ namespace SafeExamBrowser.Browser
this.hashAlgorithm = hashAlgorithm;
this.httpClient = new HttpClient();
this.Id = id;
this.integrations = integrations;
this.IsMainWindow = isMainWindow;
this.keyGenerator = keyGenerator;
this.logger = logger;
@@ -153,15 +157,19 @@ namespace SafeExamBrowser.Browser
{
var cefSharpControl = default(ICefSharpControl);
var controlLogger = logger.CloneFor($"{nameof(BrowserControl)} #{Id}");
var contextMenuHandler = new ContextMenuHandler();
var dialogHandler = new DialogHandler();
var displayHandler = new DisplayHandler();
var downloadLogger = logger.CloneFor($"{nameof(DownloadHandler)} #{Id}");
var downloadHandler = new DownloadHandler(appConfig, downloadLogger, settings, WindowSettings);
var dragHandler = new DragHandler();
var focusHandler = new FocusHandler();
var javaScriptDialogHandler = new JavaScriptDialogHandler();
var keyboardHandler = new KeyboardHandler();
var renderHandler = new RenderProcessMessageHandler(appConfig, clipboard, keyGenerator, settings, text);
var requestFilter = new RequestFilter();
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
var resourceHandler = new ResourceHandler(appConfig, requestFilter, keyGenerator, logger, sessionMode, settings, WindowSettings, text);
var resourceHandler = new ResourceHandler(appConfig, requestFilter, integrations, keyGenerator, logger, sessionMode, settings, WindowSettings, text);
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings);
Icon = new BrowserIconResource();
@@ -181,6 +189,7 @@ namespace SafeExamBrowser.Browser
downloadHandler.ConfigurationDownloadRequested += DownloadHandler_ConfigurationDownloadRequested;
downloadHandler.DownloadAborted += DownloadHandler_DownloadAborted;
downloadHandler.DownloadUpdated += DownloadHandler_DownloadUpdated;
javaScriptDialogHandler.DialogRequested += JavaScriptDialogHandler_DialogRequested;
keyboardHandler.FindRequested += KeyboardHandler_FindRequested;
keyboardHandler.FocusAddressBarRequested += KeyboardHandler_FocusAddressBarRequested;
keyboardHandler.HomeNavigationRequested += HomeNavigationRequested;
@@ -195,19 +204,33 @@ namespace SafeExamBrowser.Browser
InitializeRequestFilter(requestFilter);
Control = new BrowserControl(clipboard, cefSharpControl, dialogHandler, displayHandler, downloadHandler, keyboardHandler, controlLogger, renderHandler, requestHandler);
Control = new BrowserControl(
clipboard,
cefSharpControl,
contextMenuHandler,
dialogHandler,
displayHandler,
downloadHandler,
dragHandler,
focusHandler,
javaScriptDialogHandler,
keyboardHandler,
controlLogger,
renderHandler,
requestHandler);
Control.AddressChanged += Control_AddressChanged;
Control.LoadFailed += Control_LoadFailed;
Control.LoadingStateChanged += Control_LoadingStateChanged;
Control.TitleChanged += Control_TitleChanged;
Control.Initialize();
logger.Debug("Initialized browser control.");
}
internal void InitializeWindow()
{
window = uiFactory.CreateBrowserWindow(Control, settings, IsMainWindow, this.logger);
window = uiFactory.CreateBrowserWindow(Control, settings, IsMainWindow, logger);
window.AddressChanged += Window_AddressChanged;
window.BackwardNavigationRequested += Window_BackwardNavigationRequested;
window.Closed += Window_Closed;
@@ -226,6 +249,7 @@ namespace SafeExamBrowser.Browser
window.BringToForeground();
Handle = window.Handle;
InitiateCookieTraversal();
logger.Debug("Initialized browser window.");
}
@@ -240,6 +264,22 @@ namespace SafeExamBrowser.Browser
.Build();
}
private void InitiateCookieTraversal()
{
var visitor = new CookieVisitor(integrations);
visitor.UserIdentifierDetected += (id) => UserIdentifierDetected?.Invoke(id);
if (Cef.GetGlobalCookieManager().VisitAllCookies(visitor))
{
logger.Debug("Successfully initiated cookie traversal.");
}
else
{
logger.Warn("Failed to initiate cookie traversal!");
}
}
private void InitializeRequestFilter(IRequestFilter requestFilter)
{
if (settings.Filter.ProcessContentRequests || settings.Filter.ProcessMainRequests)
@@ -281,8 +321,6 @@ namespace SafeExamBrowser.Browser
window.UpdateTitle(address);
TitleChanged?.Invoke(address);
}
AutoFind();
}
private void Control_LoadFailed(int errorCode, string errorText, bool isMainRequest, string url)
@@ -488,6 +526,20 @@ namespace SafeExamBrowser.Browser
}
}
private void JavaScriptDialogHandler_DialogRequested(JavaScriptDialogRequestedEventArgs args)
{
logger.Debug($"A JavaScript dialog of type '{args.Type}' has been requested...");
if (args.Type == JavaScriptDialogType.LeavePage)
{
args.Success = RequestPageLeave();
}
else
{
args.Success = RequestPageReload();
}
}
private void KeyboardHandler_FindRequested()
{
if (settings.AllowFind)
@@ -642,29 +694,61 @@ namespace SafeExamBrowser.Browser
private void ReloadRequested()
{
logger.Debug("A reload of the current page has been requested...");
if (RequestPageReload())
{
Control.Reload();
}
}
private bool RequestPageLeave()
{
var allow = false;
var result = messageBox.Show(TextKey.MessageBox_PageLeaveConfirmation, TextKey.MessageBox_PageLeaveConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
if (result == MessageBoxResult.Yes)
{
allow = true;
logger.Debug("The page leave has been granted by the user.");
}
else
{
logger.Debug("The page leave has been aborted by the user.");
}
return allow;
}
private bool RequestPageReload()
{
var allow = false;
if (WindowSettings.AllowReloading && WindowSettings.ShowReloadWarning)
{
var result = messageBox.Show(TextKey.MessageBox_ReloadConfirmation, TextKey.MessageBox_ReloadConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
var result = messageBox.Show(TextKey.MessageBox_PageReloadConfirmation, TextKey.MessageBox_PageReloadConfirmationTitle, MessageBoxAction.YesNo, MessageBoxIcon.Question, window);
if (result == MessageBoxResult.Yes)
{
logger.Debug("The user confirmed reloading the current page...");
Control.Reload();
allow = true;
logger.Debug("The page reload has been granted by the user.");
}
else
{
logger.Debug("The user aborted reloading the current page.");
logger.Debug("The page reload has been aborted by the user.");
}
}
else if (WindowSettings.AllowReloading)
{
logger.Debug("Reloading current page...");
Control.Reload();
allow = true;
logger.Debug("The page reload has been automatically granted.");
}
else
{
logger.Debug("Blocked reload attempt, as the user is not allowed to reload web pages.");
logger.Debug("The page reload has been blocked, as the user is not allowed to reload web pages.");
}
return allow;
}
private void ShowDownUploadNotAllowedMessage(bool isDownload = true)
@@ -772,14 +856,6 @@ namespace SafeExamBrowser.Browser
}
}
private void AutoFind()
{
if (settings.AllowFind && !string.IsNullOrEmpty(findParameters.term) && !CLEAR_FIND_TERM.Equals(findParameters.term, StringComparison.OrdinalIgnoreCase))
{
Control.Find(findParameters.term, findParameters.isInitial, findParameters.caseSensitive, findParameters.forward);
}
}
private double CalculateZoomPercentage()
{
return (zoomLevel * 25.0) + 100.0;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -30,9 +30,24 @@ namespace SafeExamBrowser.Browser
this.settings = settings;
}
internal void Process(JavascriptMessageReceivedEventArgs message)
internal void Update(JavascriptMessageReceivedEventArgs message)
{
if (settings.UseIsolatedClipboard)
{
try
{
var data = message.ConvertMessageTo<Data>();
if (data != default && data.Type == "Clipboard" && TrySetContent(data.Content))
{
Task.Run(() => Changed?.Invoke(data.Id));
}
}
catch (Exception e)
{
logger.Error($"Failed to process browser message '{message}'!", e);
}
}
}
private bool TrySetContent(object value)
@@ -50,7 +65,7 @@ namespace SafeExamBrowser.Browser
private class Data
{
public string Content { get; set; }
public long Id { get; set; }
public string Id { get; set; }
public string Type { get; set; }
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,195 +1,211 @@
/*
* Copyright (c) 2024 ETH Z<>rich, IT Services
* Copyright (c) 2025 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/.
*
* Original code taken and slightly adapted from https://github.com/eqsoft/seb2/blob/master/browser/app/modules/SebBrowser.jsm#L1215.
* Original code taken and adapted from https://github.com/eqsoft/seb2/blob/master/browser/app/modules/SebBrowser.jsm#L1215.
*/
SafeExamBrowser.clipboard = {
id: Math.round((Date.now() + Math.random()) * 1000),
ranges: [],
text: "",
clear: function () {
this.ranges = [];
this.text = "";
},
getContentEncoded: function () {
var bytes = new TextEncoder().encode(this.text);
var base64 = btoa(String.fromCodePoint(...bytes));
return base64;
},
update: function (id, base64) {
if (this.id != id) {
var bytes = Uint8Array.from(atob(base64), (m) => m.codePointAt(0));
var content = new TextDecoder().decode(bytes);
if (typeof SafeExamBrowser.clipboard === 'undefined') {
SafeExamBrowser.clipboard = {
id: crypto.randomUUID(),
ranges: [],
text: "",
clear: function () {
this.ranges = [];
this.text = content;
}
}
}
this.text = "";
},
function copySelectedData(e) {
if (e.target.contentEditable && e.target.setRangeText) {
SafeExamBrowser.clipboard.text = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd);
SafeExamBrowser.clipboard.ranges = [];
} else {
var selection = e.target.ownerDocument.defaultView.getSelection();
var text = "";
getContentEncoded: function () {
var bytes = new TextEncoder().encode(this.text);
var base64 = btoa(String.fromCodePoint(...bytes));
for (var i = 0; i < selection.rangeCount; i++) {
SafeExamBrowser.clipboard.ranges[i] = selection.getRangeAt(i).cloneContents();
text += SafeExamBrowser.clipboard.ranges[i].textContent;
}
return base64;
},
SafeExamBrowser.clipboard.text = text;
}
}
update: function (id, base64) {
if (this.id != id) {
var bytes = Uint8Array.from(atob(base64), (m) => m.codePointAt(0));
var content = new TextDecoder().decode(bytes);
function cutSelectedData(e) {
if (e.target.contentEditable && e.target.setRangeText) {
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
} else {
var designMode = e.target.ownerDocument.designMode;
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
var selection = e.target.ownerDocument.defaultView.getSelection();
for (var i = 0; i < selection.rangeCount; i++) {
var range = selection.getRangeAt(i);
if (designMode === 'on') {
range.deleteContents();
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
if (node.contains(range.commonAncestorContainer)) {
range.deleteContents();
}
});
}
this.ranges = [];
this.text = content;
}
}
}
}
function pasteSelectedData(e) {
if (e.target.contentEditable && e.target.setRangeText) {
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
e.target.setRangeText(SafeExamBrowser.clipboard.text, e.target.selectionStart, e.target.selectionStart + SafeExamBrowser.clipboard.text.length, 'end');
} else {
var w = e.target.ownerDocument.defaultView;
var designMode = e.target.ownerDocument.designMode;
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
var selection = w.getSelection();
for (var i = 0; i < selection.rangeCount; i++) {
var r = selection.getRangeAt(i);
if (designMode === 'on') {
r.deleteContents();
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
if (node.contains(r.commonAncestorContainer)) {
r.deleteContents();
}
});
}
}
}
if (designMode === 'on') {
var range = w.getSelection().getRangeAt(0);
if (SafeExamBrowser.clipboard.ranges.length > 0) {
SafeExamBrowser.clipboard.ranges.map(r => {
range = w.getSelection().getRangeAt(0);
range.collapse();
const newNode = r.cloneNode(true);
range.insertNode(newNode);
range.collapse();
});
} else {
range.collapse();
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
range.collapse();
}
if (typeof copySelection === 'undefined') {
function copySelection(e) {
if (e.target.contentEditable && e.target.setRangeText) {
SafeExamBrowser.clipboard.text = e.target.value.substring(e.target.selectionStart, e.target.selectionEnd);
SafeExamBrowser.clipboard.ranges = [];
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
var range = w.getSelection().getRangeAt(0);
var selection = e.target.ownerDocument.defaultView.getSelection();
var text = "";
if (node.contains(range.commonAncestorContainer)) {
if (SafeExamBrowser.clipboard.ranges.length > 0) {
SafeExamBrowser.clipboard.ranges.map(r => {
range = w.getSelection().getRangeAt(0);
range.collapse();
const newNode = r.cloneNode(true);
range.insertNode(newNode);
range.collapse();
});
} else {
range = w.getSelection().getRangeAt(0);
range.collapse();
range.insertNode(w.document.createTextNode(SafeExamBrowser.clipboard.text));
range.collapse();
}
for (var i = 0; i < selection.rangeCount; i++) {
SafeExamBrowser.clipboard.ranges[i] = selection.getRangeAt(i).cloneContents();
text += SafeExamBrowser.clipboard.ranges[i].textContent;
}
SafeExamBrowser.clipboard.text = text;
}
}
}
if (typeof cutSelection === 'undefined') {
function cutSelection(e) {
if (e.target.contentEditable && e.target.setRangeText) {
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
} else {
var designMode = e.target.ownerDocument.designMode;
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
var selection = e.target.ownerDocument.defaultView.getSelection();
for (var i = 0; i < selection.rangeCount; i++) {
var range = selection.getRangeAt(i);
if (designMode === 'on') {
range.deleteContents();
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
if (node.contains(range.commonAncestorContainer)) {
range.deleteContents();
}
});
}
});
}
}
}
}
}
function onCopy(e) {
SafeExamBrowser.clipboard.clear();
if (typeof pasteContent === 'undefined') {
function pasteContent(e) {
if (e.target.contentEditable && e.target.setRangeText) {
e.target.setRangeText("", e.target.selectionStart, e.target.selectionEnd, 'select');
e.target.setRangeText(SafeExamBrowser.clipboard.text, e.target.selectionStart, e.target.selectionStart + SafeExamBrowser.clipboard.text.length, 'end');
} else {
var targetWindow = e.target.ownerDocument.defaultView;
var designMode = e.target.ownerDocument.designMode;
var contentEditables = e.target.ownerDocument.querySelectorAll('*[contenteditable]');
var selection = targetWindow.getSelection();
try {
copySelectedData(e);
for (var i = 0; i < selection.rangeCount; i++) {
var r = selection.getRangeAt(i);
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
} finally {
e.preventDefault();
e.returnValue = false;
if (designMode === 'on') {
r.deleteContents();
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
if (node.contains(r.commonAncestorContainer)) {
r.deleteContents();
}
});
}
}
}
if (designMode === 'on') {
var range = targetWindow.getSelection().getRangeAt(0);
if (SafeExamBrowser.clipboard.ranges.length > 0) {
SafeExamBrowser.clipboard.ranges.map(r => {
range = targetWindow.getSelection().getRangeAt(0);
range.collapse();
const newNode = r.cloneNode(true);
range.insertNode(newNode);
range.collapse();
});
} else {
range.collapse();
range.insertNode(targetWindow.document.createTextNode(SafeExamBrowser.clipboard.text));
range.collapse();
}
} else {
if (contentEditables.length) {
contentEditables.forEach(node => {
var range = targetWindow.getSelection().getRangeAt(0);
if (node.contains(range.commonAncestorContainer)) {
if (SafeExamBrowser.clipboard.ranges.length > 0) {
SafeExamBrowser.clipboard.ranges.map(r => {
range = targetWindow.getSelection().getRangeAt(0);
range.collapse();
const newNode = r.cloneNode(true);
range.insertNode(newNode);
range.collapse();
});
} else {
range = targetWindow.getSelection().getRangeAt(0);
range.collapse();
range.insertNode(targetWindow.document.createTextNode(SafeExamBrowser.clipboard.text));
range.collapse();
}
}
});
}
}
}
}
return false;
}
function onCut(e) {
SafeExamBrowser.clipboard.clear();
if (typeof onCopy === 'undefined') {
function onCopy(e) {
try {
SafeExamBrowser.clipboard.clear();
try {
copySelectedData(e);
cutSelectedData(e);
copySelection(e);
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
} finally {
e.preventDefault();
e.returnValue = false;
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
} finally {
e.preventDefault();
}
return false;
}
return false;
window.document.addEventListener("copy", onCopy, true);
}
function onPaste(e) {
try {
pasteSelectedData(e);
} finally {
e.preventDefault();
e.returnValue = false;
if (typeof onCut === 'undefined') {
function onCut(e) {
try {
SafeExamBrowser.clipboard.clear();
copySelection(e);
cutSelection(e);
CefSharp.PostMessage({ Type: "Clipboard", Id: SafeExamBrowser.clipboard.id, Content: SafeExamBrowser.clipboard.getContentEncoded() });
} finally {
e.preventDefault();
}
return false;
}
return false;
window.document.addEventListener("cut", onCut, true);
}
window.document.addEventListener("copy", onCopy, true);
window.document.addEventListener("cut", onCut, true);
window.document.addEventListener("paste", onPaste, true);
if (typeof onPaste === 'undefined') {
function onPaste(e) {
try {
pasteContent(e);
} finally {
e.preventDefault();
}
return false;
}
window.document.addEventListener("paste", onPaste, true);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Z<>rich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -8,5 +8,5 @@
namespace SafeExamBrowser.Browser.Events
{
internal delegate void ClipboardChangedEventHandler(long id);
internal delegate void ClipboardChangedEventHandler(string id);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 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.Browser.Events
{
internal class JavaScriptDialogRequestedEventArgs
{
internal bool Success { get; set; }
internal JavaScriptDialogType Type { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
/*
* Copyright (c) 2025 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.Browser.Events
{
internal delegate void JavaScriptDialogRequestedEventHandler(JavaScriptDialogRequestedEventArgs args);
}

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 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.Browser.Events
{
internal enum JavaScriptDialogType
{
LeavePage,
Reload
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -12,21 +12,21 @@ namespace SafeExamBrowser.Browser.Handlers
{
internal class ContextMenuHandler : IContextMenuHandler
{
public void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
public void OnBeforeContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
{
model.Clear();
}
public bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags)
public bool OnContextMenuCommand(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags)
{
return false;
}
public void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame)
public void OnContextMenuDismissed(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
{
}
public bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback)
public bool RunContextMenu(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback)
{
return false;
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (c) 2025 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Collections.Generic;
using System.Threading.Tasks;
using CefSharp;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Integrations;
namespace SafeExamBrowser.Browser.Handlers
{
internal class CookieVisitor : ICookieVisitor
{
private readonly IEnumerable<Integration> integrations;
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
internal CookieVisitor(IEnumerable<Integration> integrations)
{
this.integrations = integrations;
}
public void Dispose()
{
}
public bool Visit(Cookie cookie, int count, int total, ref bool deleteCookie)
{
foreach (var integration in integrations)
{
var success = integration.TrySearchUserIdentifier(cookie, out var userIdentifier);
if (success)
{
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
break;
}
}
return true;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -18,7 +18,16 @@ namespace SafeExamBrowser.Browser.Handlers
{
internal event DialogRequestedEventHandler DialogRequested;
public bool OnFileDialog(IWebBrowser webBrowser, IBrowser browser, CefFileDialogMode mode, string title, string defaultFilePath, List<string> acceptFilters, IFileDialogCallback callback)
public bool OnFileDialog(
IWebBrowser webBrowser,
IBrowser browser,
CefFileDialogMode mode,
string title,
string defaultFilePath,
IReadOnlyCollection<string> acceptFilters,
IReadOnlyCollection<string> acceptExtensions,
IReadOnlyCollection<string> acceptDescriptions,
IFileDialogCallback callback)
{
var args = new DialogRequestedEventArgs
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -50,7 +50,7 @@ namespace SafeExamBrowser.Browser.Handlers
return true;
}
public void OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
public bool OnBeforeDownload(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
{
var fileExtension = Path.GetExtension(downloadItem.SuggestedFileName);
var isConfigurationFile = false;
@@ -86,6 +86,8 @@ namespace SafeExamBrowser.Browser.Handlers
logger.Info($"Aborted download request{(windowSettings.UrlPolicy.CanLog() ? $" for '{url}'" : "")}, as downloading is not allowed.");
Task.Run(() => DownloadAborted?.Invoke());
}
return true;
}
public void OnDownloadUpdated(IWebBrowser webBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
@@ -100,6 +102,8 @@ namespace SafeExamBrowser.Browser.Handlers
FullPath = downloadItem.FullPath,
IsCancelled = downloadItem.IsCancelled,
IsComplete = downloadItem.IsComplete,
IsIndeterminate = downloadItem.PercentComplete < 0,
Size = downloadItem.ReceivedBytes,
Url = downloadItem.Url
};

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Collections.Generic;
using CefSharp;
using CefSharp.Enums;
namespace SafeExamBrowser.Browser.Handlers
{
internal class DragHandler : IDragHandler
{
public bool OnDragEnter(IWebBrowser chromiumWebBrowser, IBrowser browser, IDragData dragData, DragOperationsMask mask)
{
return !(dragData.IsFragment && mask.HasFlag(DragOperationsMask.Move));
}
public void OnDraggableRegionsChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IList<DraggableRegion> regions)
{
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2025 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 CefSharp;
namespace SafeExamBrowser.Browser.Handlers
{
internal class FocusHandler : IFocusHandler
{
internal FocusHandler()
{
}
public void OnGotFocus(IWebBrowser webBrowser, IBrowser browser)
{
}
public bool OnSetFocus(IWebBrowser webBrowser, IBrowser browser, CefFocusSource source)
{
return false;
}
public void OnTakeFocus(IWebBrowser webBrowser, IBrowser browser, bool next)
{
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2025 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 CefSharp;
using SafeExamBrowser.Browser.Events;
namespace SafeExamBrowser.Browser.Handlers
{
internal class JavaScriptDialogHandler : IJsDialogHandler
{
internal event JavaScriptDialogRequestedEventHandler DialogRequested;
public bool OnBeforeUnloadDialog(IWebBrowser webBrowser, IBrowser browser, string message, bool isReload, IJsDialogCallback callback)
{
var args = new JavaScriptDialogRequestedEventArgs
{
Type = isReload ? JavaScriptDialogType.Reload : JavaScriptDialogType.LeavePage
};
Task.Run(() =>
{
DialogRequested?.Invoke(args);
using (callback)
{
callback.Continue(args.Success);
}
});
return true;
}
public void OnDialogClosed(IWebBrowser webBrowser, IBrowser browser)
{
}
public bool OnJSDialog(IWebBrowser webBrowser, IBrowser browser, string originUrl, CefJsDialogType type, string message, string promptText, IJsDialogCallback callback, ref bool suppress)
{
return false;
}
public void OnResetDialogState(IWebBrowser webBrowser, IBrowser browser)
{
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
* Copyright (c) 2025 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
@@ -7,18 +7,16 @@
*/
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Threading.Tasks;
using CefSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SafeExamBrowser.Browser.Content;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Integrations;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
@@ -36,6 +34,7 @@ namespace SafeExamBrowser.Browser.Handlers
private readonly AppConfig appConfig;
private readonly ContentLoader contentLoader;
private readonly IRequestFilter filter;
private readonly IEnumerable<Integration> integrations;
private readonly IKeyGenerator keyGenerator;
private readonly ILogger logger;
private readonly SessionMode sessionMode;
@@ -44,13 +43,13 @@ namespace SafeExamBrowser.Browser.Handlers
private IResourceHandler contentHandler;
private IResourceHandler pageHandler;
private string userIdentifier;
internal event UserIdentifierDetectedEventHandler UserIdentifierDetected;
internal ResourceHandler(
AppConfig appConfig,
IRequestFilter filter,
IEnumerable<Integration> integrations,
IKeyGenerator keyGenerator,
ILogger logger,
SessionMode sessionMode,
@@ -60,6 +59,7 @@ namespace SafeExamBrowser.Browser.Handlers
{
this.appConfig = appConfig;
this.filter = filter;
this.integrations = integrations;
this.contentLoader = new ContentLoader(text);
this.keyGenerator = keyGenerator;
this.logger = logger;
@@ -235,217 +235,17 @@ namespace SafeExamBrowser.Browser.Handlers
private void SearchUserIdentifier(IRequest request, IResponse response)
{
var success = TrySearchGenericUserIdentifier(response);
if (!success)
foreach (var integration in integrations)
{
success = TrySearchEdxUserIdentifier(response);
}
var success = integration.TrySearchUserIdentifier(request, response, out var userIdentifier);
if (!success)
{
TrySearchMoodleUserIdentifier(request, response);
}
}
private bool TrySearchGenericUserIdentifier(IResponse response)
{
var ids = response.Headers.GetValues("X-LMS-USER-ID");
var success = false;
if (ids != default(string[]))
{
var userId = ids.FirstOrDefault();
if (userId != default && userIdentifier != userId)
if (success)
{
userIdentifier = userId;
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
logger.Info("Generic LMS user identifier detected.");
success = true;
break;
}
}
return success;
}
private bool TrySearchEdxUserIdentifier(IResponse response)
{
var cookies = response.Headers.GetValues("Set-Cookie");
var success = false;
if (cookies != default(string[]))
{
try
{
var userInfo = cookies.FirstOrDefault(c => c.Contains("edx-user-info"));
if (userInfo != default)
{
var start = userInfo.IndexOf("=") + 1;
var end = userInfo.IndexOf("; expires");
var cookie = userInfo.Substring(start, end - start);
var sanitized = cookie.Replace("\\\"", "\"").Replace("\\054", ",").Trim('"');
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
var userName = json["username"].Value<string>();
if (userIdentifier != userName)
{
userIdentifier = userName;
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
logger.Info("EdX user identifier detected.");
success = true;
}
}
}
catch (Exception e)
{
logger.Error("Failed to parse edX user identifier!", e);
}
}
return success;
}
private bool TrySearchMoodleUserIdentifier(IRequest request, IResponse response)
{
var success = TrySearchMoodleUserIdentifierByLocation(response);
if (!success)
{
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Plugin, request, response);
}
if (!success)
{
success = TrySearchMoodleUserIdentifierByRequest(MoodleRequestType.Theme, request, response);
}
return success;
}
private bool TrySearchMoodleUserIdentifierByLocation(IResponse response)
{
var locations = response.Headers.GetValues("Location");
if (locations != default(string[]))
{
try
{
var location = locations.FirstOrDefault(l => l.Contains("/login/index.php?testsession"));
if (location != default)
{
var userId = location.Substring(location.IndexOf("=") + 1);
if (userIdentifier != userId)
{
userIdentifier = userId;
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
logger.Info("Moodle user identifier detected by location.");
}
return true;
}
}
catch (Exception e)
{
logger.Error("Failed to parse Moodle user identifier by location!", e);
}
}
return false;
}
private bool TrySearchMoodleUserIdentifierByRequest(MoodleRequestType type, IRequest request, IResponse response)
{
var cookies = response.Headers.GetValues("Set-Cookie");
var success = false;
if (cookies != default(string[]))
{
var session = cookies.FirstOrDefault(c => c.Contains("MoodleSession"));
if (session != default)
{
var userId = ExecuteMoodleUserIdentifierRequest(request.Url, session, type);
if (int.TryParse(userId, out var id) && id > 0 && userIdentifier != userId)
{
userIdentifier = userId;
Task.Run(() => UserIdentifierDetected?.Invoke(userIdentifier));
logger.Info($"Moodle user identifier detected by request ({type}).");
success = true;
}
}
}
return success;
}
private string ExecuteMoodleUserIdentifierRequest(string requestUrl, string session, MoodleRequestType type)
{
var userId = default(string);
try
{
Task.Run(async () =>
{
try
{
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);
if (type == MoodleRequestType.Plugin)
{
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/mod/quiz/accessrule/sebserver/classes/external/user.php";
}
else
{
endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}/theme/boost_ethz/sebuser.php";
}
var message = new HttpRequestMessage(HttpMethod.Get, endpointUrl);
using (var handler = new HttpClientHandler { UseCookies = false })
using (var client = new HttpClient(handler))
{
message.Headers.Add("Cookie", $"{name}={value}");
var result = await client.SendAsync(message);
if (result.IsSuccessStatusCode)
{
userId = await result.Content.ReadAsStringAsync();
}
else if (result.StatusCode != HttpStatusCode.NotFound)
{
logger.Error($"Failed to retrieve Moodle user identifier by request ({type})! Response: {(int) result.StatusCode} {result.ReasonPhrase}");
}
}
}
catch (Exception e)
{
logger.Error($"Failed to parse Moodle user identifier by request ({type})!", e);
}
}).GetAwaiter().GetResult();
}
catch (Exception e)
{
logger.Error($"Failed to execute Moodle user identifier request ({type})!", e);
}
return userId;
}
private enum MoodleRequestType
{
Plugin,
Theme
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2025 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 CefSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Browser.Integrations
{
internal class EdxIntegration : Integration
{
private readonly ILogger logger;
public EdxIntegration(ILogger logger)
{
this.logger = logger;
}
internal override bool TrySearchUserIdentifier(Cookie cookie, out string userIdentifier)
{
userIdentifier = default;
return false;
}
internal override bool TrySearchUserIdentifier(IRequest request, IResponse response, out string userIdentifier)
{
var cookies = response.Headers.GetValues("Set-Cookie");
var userInfo = cookies?.FirstOrDefault(c => c.Contains("edx-user-info"));
userIdentifier = default;
if (TryParseCookie(userInfo, out var id) && HasChanged(id))
{
userIdentifier = id;
logger.Info($"User identifier '{id}' detected by session cookie on response.");
}
return userIdentifier != default;
}
private bool TryParseCookie(string userInfo, out string userIdentifier)
{
userIdentifier = default;
try
{
if (userInfo != default)
{
var start = userInfo.IndexOf("=") + 1;
var end = userInfo.IndexOf("; expires");
var cookie = userInfo.Substring(start, end - start);
var sanitized = cookie.Replace("\\\"", "\"").Replace("\\054", ",").Trim('"');
var json = JsonConvert.DeserializeObject(sanitized) as JObject;
userIdentifier = json["username"].Value<string>();
}
}
catch (Exception e)
{
logger.Error("Failed to parse user identifier!", e);
}
return userIdentifier != default;
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2025 ETH Zürich, IT Services
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Linq;
using CefSharp;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Browser.Integrations
{
internal class GenericIntegration : Integration
{
private readonly ILogger logger;
public GenericIntegration(ILogger logger)
{
this.logger = logger;
}
internal override bool TrySearchUserIdentifier(Cookie cookie, out string userIdentifier)
{
userIdentifier = default;
return false;
}
internal override bool TrySearchUserIdentifier(IRequest request, IResponse response, out string userIdentifier)
{
var ids = response.Headers.GetValues("X-LMS-USER-ID");
var id = ids?.FirstOrDefault();
userIdentifier = default;
if (HasChanged(id))
{
userIdentifier = id;
logger.Info($"User identifier '{id}' detected by header of response.");
}
return userIdentifier != default;
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2025 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 CefSharp;
namespace SafeExamBrowser.Browser.Integrations
{
internal abstract class Integration
{
private static string activeUserIdentifier;
internal abstract bool TrySearchUserIdentifier(Cookie cookie, out string userIdentifier);
internal abstract bool TrySearchUserIdentifier(IRequest request, IResponse response, out string userIdentifier);
protected bool HasChanged(string userIdentifier)
{
var current = activeUserIdentifier;
if (userIdentifier != default && activeUserIdentifier != userIdentifier)
{
activeUserIdentifier = userIdentifier;
}
return activeUserIdentifier != current;
}
}
}

View File

@@ -0,0 +1,226 @@
/*
* Copyright (c) 2025 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.Net;
using System.Net.Http;
using CefSharp;
using SafeExamBrowser.Logging.Contracts;
using Cookie = CefSharp.Cookie;
namespace SafeExamBrowser.Browser.Integrations
{
internal class MoodleIntegration : Integration
{
private const string PLUGIN_PATH = "/mod/quiz/accessrule/sebserver/classes/external/user.php";
private const string SESSION_COOKIE_NAME = "MoodleSession";
private const string THEME_PATH = "/theme/boost_ethz/sebuser.php";
private readonly ILogger logger;
public MoodleIntegration(ILogger logger)
{
this.logger = logger;
}
internal override bool TrySearchUserIdentifier(Cookie cookie, out string userIdentifier)
{
return TrySearchByCookie(cookie, out userIdentifier);
}
internal override bool TrySearchUserIdentifier(IRequest request, IResponse response, out string userIdentifier)
{
var success = TrySearchByLocation(response, out userIdentifier);
if (!success)
{
success = TrySearchByRequests(request, response, out userIdentifier);
}
return success;
}
private bool TrySearchByCookie(Cookie cookie, out string userIdentifier)
{
var id = default(string);
var type = default(RequestType);
var isSession = cookie.Name.Contains(SESSION_COOKIE_NAME);
var url = $"{(cookie.Secure ? Uri.UriSchemeHttps : Uri.UriSchemeHttp)}{Uri.SchemeDelimiter}{cookie.Domain}{cookie.Path}";
var hasId = isSession && TryExecuteRequests(url, (cookie.Name, cookie.Value), out type, out id);
userIdentifier = default;
if (hasId && HasChanged(id))
{
userIdentifier = id;
logger.Info($"User identifier '{id}' detected by request on cookie traversal ({type}).");
}
return userIdentifier != default;
}
private bool TrySearchByLocation(IResponse response, out string userIdentifier)
{
var locations = response.Headers.GetValues("Location");
var location = locations?.FirstOrDefault(l => l.Contains("/login/index.php?testsession"));
userIdentifier = default;
if (TryParseLocation(location, out var id) && HasChanged(id))
{
userIdentifier = id;
logger.Info($"User identifier '{id}' detected by location header of response.");
}
return userIdentifier != default;
}
private bool TrySearchByRequests(IRequest request, IResponse response, out string userIdentifier)
{
var id = default(string);
var type = default(RequestType);
var cookies = response.Headers.GetValues("Set-Cookie");
var session = cookies?.FirstOrDefault(c => c.Contains(SESSION_COOKIE_NAME));
var hasCookie = TryParseCookie(session, out var cookie);
var hasId = hasCookie && TryExecuteRequests(request.Url, cookie, out type, out id);
userIdentifier = default;
if (hasId && HasChanged(id))
{
userIdentifier = id;
logger.Info($"User identifier '{id}' detected by request on response ({type}).");
}
return userIdentifier != default;
}
private bool TryExecuteRequests(string originUrl, (string name, string value) session, out RequestType requestType, out string userId)
{
var order = new[] { RequestType.Plugin, RequestType.Theme };
requestType = default;
userId = default;
foreach (var type in order)
{
try
{
var url = BuildUrl(originUrl, type);
using (var response = ExecuteRequest(url, session))
{
if (TryParseResponse(response, type, out var id))
{
requestType = type;
userId = id;
break;
}
}
}
catch (Exception e)
{
logger.Error($"Failed to execute user identifier request ({type})!", e);
}
}
return userId != default;
}
private string BuildUrl(string originUrl, RequestType type)
{
var uri = new Uri(originUrl);
var endpointUrl = $"{uri.Scheme}{Uri.SchemeDelimiter}{uri.Host}{(type == RequestType.Plugin ? PLUGIN_PATH : THEME_PATH)}";
return endpointUrl;
}
private HttpResponseMessage ExecuteRequest(string url, (string name, string value) session)
{
using (var message = new HttpRequestMessage(HttpMethod.Get, url))
using (var handler = new HttpClientHandler { UseCookies = false })
using (var client = new HttpClient(handler))
{
message.Headers.Add("Cookie", $"{session.name}={session.value}");
return client.SendAsync(message).GetAwaiter().GetResult();
}
}
private bool TryParseCookie(string session, out (string name, string value) cookie)
{
cookie = default;
try
{
if (session != default)
{
var start = session.IndexOf("=") + 1;
var end = session.IndexOf(";");
cookie.name = session.Substring(0, start - 1);
cookie.value = session.Substring(start, end - start);
}
}
catch (Exception e)
{
logger.Error("Failed to parse session cookie!", e);
}
return cookie.name != default && cookie.value != default;
}
private bool TryParseLocation(string location, out string userId)
{
userId = default;
try
{
if (location != default)
{
userId = location.Substring(location.IndexOf("=") + 1);
}
}
catch (Exception e)
{
logger.Error("Failed to parse location!", e);
}
return userId != default;
}
private bool TryParseResponse(HttpResponseMessage response, RequestType type, out string userId)
{
userId = default;
if (response.IsSuccessStatusCode)
{
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
if (int.TryParse(content, out var id) && id > 0)
{
userId = content;
}
}
else if (response.StatusCode != HttpStatusCode.NotFound)
{
logger.Error($"Failed to retrieve user identifier by request ({type})! Response: {(int) response.StatusCode} {response.ReasonPhrase}");
}
return userId != default;
}
private enum RequestType
{
Plugin,
Theme
}
}
}

View File

@@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Browser")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
[assembly: AssemblyCopyright("Copyright © 2025 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from

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