Restore SEBPatch
This commit is contained in:
@@ -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
|
||||
|
@@ -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,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Proctoring")]
|
||||
[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
|
||||
|
@@ -53,14 +53,17 @@
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="KGySoft.CoreLibraries, Version=8.1.0.0, Culture=neutral, PublicKeyToken=b45eba277439ddfe, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\KGySoft.CoreLibraries.8.1.0\lib\net472\KGySoft.CoreLibraries.dll</HintPath>
|
||||
<Reference Include="BouncyCastle.Cryptography, Version=2.0.0.0, Culture=neutral, PublicKeyToken=072edcf4a5328938, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\BouncyCastle.Cryptography.2.5.0\lib\net461\BouncyCastle.Cryptography.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="KGySoft.Drawing, Version=8.1.0.0, Culture=neutral, PublicKeyToken=b45eba277439ddfe, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\KGySoft.Drawing.8.1.0\lib\net46\KGySoft.Drawing.dll</HintPath>
|
||||
<Reference Include="KGySoft.CoreLibraries, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b45eba277439ddfe, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\KGySoft.CoreLibraries.9.0.0\lib\net472\KGySoft.CoreLibraries.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="KGySoft.Drawing.Core, Version=8.1.0.0, Culture=neutral, PublicKeyToken=b45eba277439ddfe, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\KGySoft.Drawing.Core.8.1.0\lib\net46\KGySoft.Drawing.Core.dll</HintPath>
|
||||
<Reference Include="KGySoft.Drawing, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b45eba277439ddfe, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\KGySoft.Drawing.9.0.0\lib\net46\KGySoft.Drawing.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="KGySoft.Drawing.Core, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b45eba277439ddfe, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\KGySoft.Drawing.Core.9.0.0\lib\net46\KGySoft.Drawing.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
@@ -88,6 +91,7 @@
|
||||
<Compile Include="ScreenProctoring\Data\KeyboardTrigger.cs" />
|
||||
<Compile Include="ScreenProctoring\Data\MetaData.cs" />
|
||||
<Compile Include="ScreenProctoring\Data\MouseTrigger.cs" />
|
||||
<Compile Include="ScreenProctoring\Encryptor.cs" />
|
||||
<Compile Include="ScreenProctoring\Events\DataCollectedEventHandler.cs" />
|
||||
<Compile Include="ScreenProctoring\Imaging\Extensions.cs" />
|
||||
<Compile Include="ScreenProctoring\Data\MetaDataAggregator.cs" />
|
||||
@@ -107,6 +111,7 @@
|
||||
<Compile Include="ScreenProctoring\Service\Requests\OAuth2TokenRequest.cs" />
|
||||
<Compile Include="ScreenProctoring\Service\Requests\Request.cs" />
|
||||
<Compile Include="ScreenProctoring\Service\Requests\ScreenShotRequest.cs" />
|
||||
<Compile Include="ScreenProctoring\Service\Sanitizer.cs" />
|
||||
<Compile Include="ScreenProctoring\Service\ServiceProxy.cs" />
|
||||
<Compile Include="ScreenProctoring\Service\ServiceResponse.cs" />
|
||||
<Compile Include="ScreenProctoring\TransmissionSpooler.cs" />
|
||||
@@ -166,6 +171,7 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
|
@@ -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
|
||||
@@ -47,6 +47,15 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
}
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
list.Clear();
|
||||
logger.Debug("Cleared all data.");
|
||||
}
|
||||
}
|
||||
|
||||
internal void Dequeue()
|
||||
{
|
||||
lock (@lock)
|
||||
|
@@ -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,19 +23,27 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
{
|
||||
internal class Cache
|
||||
{
|
||||
private const string DATA_FILE_EXTENSION = "xml";
|
||||
private const string DATA_FILE_EXTENSION = "spsdat";
|
||||
private const string IMAGE_FILE_EXTENSION = "spsimg";
|
||||
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly Lazy<string> directory;
|
||||
private readonly Encryptor encryptor;
|
||||
private readonly ILogger logger;
|
||||
private readonly ScreenProctoringSettings settings;
|
||||
private readonly ConcurrentQueue<(string fileName, int checksum, string hash)> queue;
|
||||
|
||||
internal int Count => queue.Count;
|
||||
internal string Directory { get; private set; }
|
||||
internal string Directory => directory.Value;
|
||||
internal long Size { get; private set; }
|
||||
|
||||
public Cache(AppConfig appConfig, ILogger logger)
|
||||
public Cache(AppConfig appConfig, ILogger logger, ScreenProctoringSettings settings)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.directory = new Lazy<string>(InitializeDirectory);
|
||||
this.encryptor = new Encryptor(settings);
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
this.queue = new ConcurrentQueue<(string, int, string)>();
|
||||
}
|
||||
|
||||
@@ -44,6 +52,24 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
return queue.Any();
|
||||
}
|
||||
|
||||
internal void Clear()
|
||||
{
|
||||
while (queue.Any())
|
||||
{
|
||||
if (queue.TryDequeue(out var item))
|
||||
{
|
||||
var (dataPath, imagePath) = BuildPathsFor(item.fileName);
|
||||
|
||||
File.Delete(dataPath);
|
||||
File.Delete(imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
Size = 0;
|
||||
|
||||
logger.Debug("Cleared all data.");
|
||||
}
|
||||
|
||||
internal bool TryEnqueue(MetaData metaData, ScreenShot screenShot)
|
||||
{
|
||||
var fileName = $"{screenShot.CaptureTime:yyyy-MM-dd HH\\hmm\\mss\\sfff\\m\\s}";
|
||||
@@ -51,14 +77,22 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
|
||||
try
|
||||
{
|
||||
InitializeDirectory();
|
||||
SaveData(fileName, metaData, screenShot);
|
||||
SaveImage(fileName, screenShot);
|
||||
Enqueue(fileName, metaData, screenShot);
|
||||
var (dataPath, imagePath) = BuildPathsFor(fileName);
|
||||
|
||||
if (Size <= settings.CacheSize * 1000000)
|
||||
{
|
||||
SaveData(dataPath, metaData, screenShot);
|
||||
SaveImage(imagePath, screenShot);
|
||||
Enqueue(fileName, metaData, screenShot);
|
||||
|
||||
logger.Debug($"Cached data for '{fileName}', now holding {Count} item(s) counting {Size / 1000000.0:N1} MB.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"The maximum cache size of {settings.CacheSize:N1} MB has been reached, dropping data for '{fileName}'!");
|
||||
}
|
||||
|
||||
success = true;
|
||||
|
||||
logger.Debug($"Cached data for '{fileName}', now holding {Count} item(s).");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -79,13 +113,15 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadData(item.fileName, out metaData, out screenShot);
|
||||
LoadImage(item.fileName, screenShot);
|
||||
Dequeue(item.fileName, item.checksum, item.hash, metaData, screenShot);
|
||||
var (dataPath, imagePath) = BuildPathsFor(item.fileName);
|
||||
|
||||
LoadData(dataPath, out metaData, out screenShot);
|
||||
LoadImage(imagePath, screenShot);
|
||||
Dequeue(item.checksum, dataPath, item.fileName, item.hash, imagePath, metaData, screenShot);
|
||||
|
||||
success = true;
|
||||
|
||||
logger.Debug($"Removed data for '{item.fileName}', {Count} item(s) remaining.");
|
||||
logger.Debug($"Removed data for '{item.fileName}', {Count} item(s) counting {Size / 1000000.0:N1} MB remaining.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -96,12 +132,16 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
return success;
|
||||
}
|
||||
|
||||
private void Dequeue(string fileName, int checksum, string hash, MetaData metaData, ScreenShot screenShot)
|
||||
private (string dataPath, string imagePath) BuildPathsFor(string fileName)
|
||||
{
|
||||
var dataPath = Path.Combine(Directory, $"{fileName}.{DATA_FILE_EXTENSION}");
|
||||
var extension = screenShot.Format.ToString().ToLower();
|
||||
var imagePath = Path.Combine(Directory, $"{fileName}.{extension}");
|
||||
var imagePath = Path.Combine(Directory, $"{fileName}.{IMAGE_FILE_EXTENSION}");
|
||||
|
||||
return (dataPath, imagePath);
|
||||
}
|
||||
|
||||
private void Dequeue(int checksum, string dataPath, string fileName, string hash, string imagePath, MetaData metaData, ScreenShot screenShot)
|
||||
{
|
||||
if (checksum != GenerateChecksum(screenShot))
|
||||
{
|
||||
logger.Warn($"The checksum for '{fileName}' does not match, the image data may be manipulated!");
|
||||
@@ -157,24 +197,25 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
return hash;
|
||||
}
|
||||
|
||||
private void InitializeDirectory()
|
||||
private string InitializeDirectory()
|
||||
{
|
||||
if (Directory == default)
|
||||
var path = Path.Combine(appConfig.TemporaryDirectory, nameof(ScreenProctoring));
|
||||
|
||||
if (!System.IO.Directory.Exists(path))
|
||||
{
|
||||
Directory = Path.Combine(appConfig.TemporaryDirectory, nameof(ScreenProctoring));
|
||||
System.IO.Directory.CreateDirectory(path);
|
||||
logger.Debug($"Created caching directory '{path}'.");
|
||||
}
|
||||
|
||||
if (!System.IO.Directory.Exists(Directory))
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(Directory);
|
||||
logger.Debug($"Created caching directory '{Directory}'.");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private void LoadData(string fileName, out MetaData metaData, out ScreenShot screenShot)
|
||||
private void LoadData(string filePath, out MetaData metaData, out ScreenShot screenShot)
|
||||
{
|
||||
var dataPath = Path.Combine(Directory, $"{fileName}.{DATA_FILE_EXTENSION}");
|
||||
var document = XDocument.Load(dataPath);
|
||||
var encrypted = File.ReadAllBytes(filePath);
|
||||
var data = encryptor.Decrypt(encrypted);
|
||||
var text = Encoding.UTF8.GetString(data);
|
||||
var document = XDocument.Parse(text);
|
||||
var xml = document.Descendants(nameof(MetaData)).First();
|
||||
|
||||
metaData = new MetaData();
|
||||
@@ -193,22 +234,24 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
screenShot.Format = (ImageFormat) Enum.Parse(typeof(ImageFormat), xml.Descendants(nameof(ScreenShot.Format)).First().Value);
|
||||
screenShot.Height = int.Parse(xml.Descendants(nameof(ScreenShot.Height)).First().Value);
|
||||
screenShot.Width = int.Parse(xml.Descendants(nameof(ScreenShot.Width)).First().Value);
|
||||
|
||||
Size -= encrypted.Length;
|
||||
}
|
||||
|
||||
private void LoadImage(string fileName, ScreenShot screenShot)
|
||||
private void LoadImage(string filePath, ScreenShot screenShot)
|
||||
{
|
||||
var extension = screenShot.Format.ToString().ToLower();
|
||||
var imagePath = Path.Combine(Directory, $"{fileName}.{extension}");
|
||||
var encrypted = File.ReadAllBytes(filePath);
|
||||
var image = encryptor.Decrypt(encrypted);
|
||||
|
||||
screenShot.Data = File.ReadAllBytes(imagePath);
|
||||
screenShot.Data = image;
|
||||
Size -= encrypted.Length;
|
||||
}
|
||||
|
||||
private void SaveData(string fileName, MetaData metaData, ScreenShot screenShot)
|
||||
private void SaveData(string filePath, MetaData metaData, ScreenShot screenShot)
|
||||
{
|
||||
var data = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
|
||||
var dataPath = Path.Combine(Directory, $"{fileName}.{DATA_FILE_EXTENSION}");
|
||||
var xml = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
|
||||
|
||||
data.Add(
|
||||
xml.Add(
|
||||
new XElement("Data",
|
||||
new XElement(nameof(MetaData),
|
||||
new XElement(nameof(MetaData.ApplicationInfo), metaData.ApplicationInfo),
|
||||
@@ -227,15 +270,25 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
)
|
||||
);
|
||||
|
||||
data.Save(dataPath);
|
||||
using (var stream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(stream, new UTF8Encoding(false)))
|
||||
{
|
||||
xml.Save(writer);
|
||||
|
||||
var data = stream.ToArray();
|
||||
var encrypted = encryptor.Encrypt(data);
|
||||
|
||||
File.WriteAllBytes(filePath, encrypted);
|
||||
Size += encrypted.Length;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveImage(string fileName, ScreenShot screenShot)
|
||||
private void SaveImage(string filePath, ScreenShot screenShot)
|
||||
{
|
||||
var extension = screenShot.Format.ToString().ToLower();
|
||||
var imagePath = Path.Combine(Directory, $"{fileName}.{extension}");
|
||||
var encrypted = encryptor.Encrypt(screenShot.Data);
|
||||
|
||||
File.WriteAllBytes(imagePath, screenShot.Data);
|
||||
File.WriteAllBytes(filePath, encrypted);
|
||||
Size += encrypted.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
@@ -66,8 +66,8 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
mouseHookId = nativeMethods.RegisterMouseHook(MouseHookCallback);
|
||||
|
||||
timer.AutoReset = false;
|
||||
timer.Elapsed += MaxIntervalElapsed;
|
||||
timer.Interval = settings.MaxInterval;
|
||||
timer.Elapsed += IntervalMaximumElapsed;
|
||||
timer.Interval = settings.IntervalMaximum;
|
||||
timer.Start();
|
||||
|
||||
logger.Debug("Started.");
|
||||
@@ -90,7 +90,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
keyboardHookId = default;
|
||||
mouseHookId = default;
|
||||
|
||||
timer.Elapsed -= MaxIntervalElapsed;
|
||||
timer.Elapsed -= IntervalMaximumElapsed;
|
||||
timer.Stop();
|
||||
|
||||
logger.Debug("Stopped.");
|
||||
@@ -110,11 +110,11 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
return false;
|
||||
}
|
||||
|
||||
private void MaxIntervalElapsed(object sender, ElapsedEventArgs args)
|
||||
private void IntervalMaximumElapsed(object sender, ElapsedEventArgs args)
|
||||
{
|
||||
var trigger = new IntervalTrigger
|
||||
{
|
||||
ConfigurationValue = settings.MaxInterval,
|
||||
ConfigurationValue = settings.IntervalMaximum,
|
||||
};
|
||||
|
||||
TryCollect(interval: trigger);
|
||||
@@ -136,7 +136,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
|
||||
private void TryCollect(IntervalTrigger interval = default, KeyboardTrigger keyboard = default, MouseTrigger mouse = default)
|
||||
{
|
||||
if (MinIntervalElapsed() && Monitor.TryEnter(@lock))
|
||||
if (HasIntervalMinimumElapsed() && Monitor.TryEnter(@lock))
|
||||
{
|
||||
var elapsed = DateTime.Now.Subtract(last);
|
||||
|
||||
@@ -169,9 +169,9 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
}
|
||||
}
|
||||
|
||||
private bool MinIntervalElapsed()
|
||||
private bool HasIntervalMinimumElapsed()
|
||||
{
|
||||
return DateTime.Now.Subtract(last) >= new TimeSpan(0, 0, 0, 0, settings.MinInterval);
|
||||
return DateTime.Now.Subtract(last) >= new TimeSpan(0, 0, 0, 0, settings.IntervalMinimum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
@@ -47,7 +47,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
this.logger = logger;
|
||||
this.service = service;
|
||||
this.settings = settings.ScreenProctoring;
|
||||
this.spooler = new TransmissionSpooler(appConfig, logger.CloneFor(nameof(TransmissionSpooler)), service);
|
||||
this.spooler = new TransmissionSpooler(appConfig, logger.CloneFor(nameof(TransmissionSpooler)), service, settings.ScreenProctoring);
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
{
|
||||
settings.ClientId = instruction.ClientId;
|
||||
settings.ClientSecret = instruction.ClientSecret;
|
||||
settings.EncryptionSecret = instruction.EncryptionSecret;
|
||||
settings.GroupId = instruction.GroupId;
|
||||
settings.ServiceUrl = instruction.ServiceUrl;
|
||||
|
||||
|
@@ -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,10 +19,10 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service
|
||||
|
||||
internal Api()
|
||||
{
|
||||
AccessTokenEndpoint = "/oauth/token";
|
||||
HealthEndpoint = "/health";
|
||||
ScreenShotEndpoint = $"/seb-api/v1/session/{SESSION_ID}/screenshot";
|
||||
SessionEndpoint = "/seb-api/v1/session";
|
||||
AccessTokenEndpoint = "oauth/token";
|
||||
HealthEndpoint = "health";
|
||||
ScreenShotEndpoint = $"seb-api/v1/session/{SESSION_ID}/screenshot";
|
||||
SessionEndpoint = "seb-api/v1/session";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
@@ -20,6 +20,7 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service
|
||||
private readonly Api api;
|
||||
private readonly ILogger logger;
|
||||
private readonly Parser parser;
|
||||
private readonly Sanitizer sanitizer;
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
@@ -31,16 +32,19 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring.Service
|
||||
this.api = new Api();
|
||||
this.logger = logger;
|
||||
this.parser = new Parser(logger);
|
||||
this.sanitizer = new Sanitizer();
|
||||
}
|
||||
|
||||
internal ServiceResponse Connect(string clientId, string clientSecret, string serviceUrl)
|
||||
{
|
||||
httpClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(serviceUrl),
|
||||
BaseAddress = sanitizer.Sanitize(serviceUrl),
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
|
||||
sanitizer.Sanitize(api);
|
||||
|
||||
var request = new OAuth2TokenRequest(api, httpClient, logger, parser);
|
||||
var success = request.TryExecute(clientId, clientSecret, out var message);
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
@@ -16,6 +16,7 @@ using SafeExamBrowser.Proctoring.Contracts.Events;
|
||||
using SafeExamBrowser.Proctoring.ScreenProctoring.Data;
|
||||
using SafeExamBrowser.Proctoring.ScreenProctoring.Imaging;
|
||||
using SafeExamBrowser.Proctoring.ScreenProctoring.Service;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
@@ -40,10 +41,10 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
private Thread thread;
|
||||
private CancellationTokenSource token;
|
||||
|
||||
internal TransmissionSpooler(AppConfig appConfig, IModuleLogger logger, ServiceProxy service)
|
||||
internal TransmissionSpooler(AppConfig appConfig, IModuleLogger logger, ServiceProxy service, ScreenProctoringSettings settings)
|
||||
{
|
||||
this.buffer = new Buffer(logger.CloneFor(nameof(Buffer)));
|
||||
this.cache = new Cache(appConfig, logger.CloneFor(nameof(Cache)));
|
||||
this.cache = new Cache(appConfig, logger.CloneFor(nameof(Cache)), settings);
|
||||
this.logger = logger;
|
||||
this.queue = new ConcurrentQueue<(MetaData, ScreenShot)>();
|
||||
this.random = new Random();
|
||||
@@ -56,13 +57,14 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
queue.Enqueue((metaData, screenShot));
|
||||
}
|
||||
|
||||
internal void ExecuteRemainingWork(Action<RemainingWorkUpdatedEventArgs> updateStatus)
|
||||
internal void ExecuteRemainingWork(Action<RemainingWorkUpdatedEventArgs> handler)
|
||||
{
|
||||
var previous = buffer.Count + cache.Count;
|
||||
var progress = 0;
|
||||
var start = DateTime.Now;
|
||||
var total = previous;
|
||||
|
||||
while (HasRemainingWork() && service.IsConnected && (!networkIssue || recovering || DateTime.Now < resume))
|
||||
while (HasRemainingWork() && service.IsConnected)
|
||||
{
|
||||
var remaining = buffer.Count + cache.Count;
|
||||
|
||||
@@ -78,26 +80,19 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
previous = remaining;
|
||||
progress = total - remaining;
|
||||
|
||||
updateStatus(new RemainingWorkUpdatedEventArgs
|
||||
var args = UpdateStatus(handler, progress, start, total);
|
||||
|
||||
if (args.CancellationRequested)
|
||||
{
|
||||
IsWaiting = recovering || networkIssue,
|
||||
Next = buffer.TryPeek(out _, out var schedule, out _) ? schedule : default(DateTime?),
|
||||
Progress = progress,
|
||||
Resume = resume,
|
||||
Total = total
|
||||
});
|
||||
logger.Warn($"The execution of the remaining work has been cancelled and {remaining} item(s) will not be transmitted!");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
if (networkIssue)
|
||||
{
|
||||
updateStatus(new RemainingWorkUpdatedEventArgs { HasFailed = true, CachePath = cache.Directory });
|
||||
}
|
||||
else
|
||||
{
|
||||
updateStatus(new RemainingWorkUpdatedEventArgs { IsFinished = true });
|
||||
}
|
||||
UpdateStatus(handler);
|
||||
}
|
||||
|
||||
internal bool HasRemainingWork()
|
||||
@@ -163,6 +158,9 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
thread = default;
|
||||
token = default;
|
||||
}
|
||||
|
||||
buffer.Clear();
|
||||
cache.Clear();
|
||||
}
|
||||
|
||||
private void Execute()
|
||||
@@ -417,5 +415,34 @@ namespace SafeExamBrowser.Proctoring.ScreenProctoring
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private RemainingWorkUpdatedEventArgs UpdateStatus(Action<RemainingWorkUpdatedEventArgs> handler, int progress, DateTime start, int total)
|
||||
{
|
||||
var args = new RemainingWorkUpdatedEventArgs
|
||||
{
|
||||
AllowCancellation = start.Add(new TimeSpan(0, 1, 15)) < DateTime.Now,
|
||||
IsWaiting = health == BAD || networkIssue || recovering || DateTime.Now < resume,
|
||||
Next = buffer.TryPeek(out _, out var schedule, out _) ? schedule : default(DateTime?),
|
||||
Progress = progress,
|
||||
Resume = DateTime.Now < resume ? resume : default(DateTime?),
|
||||
Total = total
|
||||
};
|
||||
|
||||
handler.Invoke(args);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
private void UpdateStatus(Action<RemainingWorkUpdatedEventArgs> handler)
|
||||
{
|
||||
if (HasRemainingWork())
|
||||
{
|
||||
handler.Invoke(new RemainingWorkUpdatedEventArgs { HasFailed = true, CachePath = cache.Directory });
|
||||
}
|
||||
else
|
||||
{
|
||||
handler.Invoke(new RemainingWorkUpdatedEventArgs { IsFinished = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="KGySoft.CoreLibraries" version="8.1.0" targetFramework="net48" />
|
||||
<package id="KGySoft.Drawing" version="8.1.0" targetFramework="net48" />
|
||||
<package id="KGySoft.Drawing.Core" version="8.1.0" targetFramework="net48" />
|
||||
<package id="BouncyCastle.Cryptography" version="2.5.0" targetFramework="net48" />
|
||||
<package id="KGySoft.CoreLibraries" version="9.0.0" targetFramework="net48" />
|
||||
<package id="KGySoft.Drawing" version="9.0.0" targetFramework="net48" />
|
||||
<package id="KGySoft.Drawing.Core" version="9.0.0" targetFramework="net48" />
|
||||
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||
</packages>
|
Reference in New Issue
Block a user