SEBWIN-714, #606: Implemented basic cursor functionality. Minor refactoring of registry and file system dialog classes.

This commit is contained in:
Damian Büchel 2023-09-01 12:28:03 +02:00
parent fa16710bdb
commit 722d84978c
22 changed files with 392 additions and 202 deletions

View file

@ -335,36 +335,50 @@ namespace SafeExamBrowser.Client
var timer = new System.Timers.Timer();
timer.AutoReset = false;
timer.Elapsed += (o, args) =>
{
logger.Info($"Attempting to verify application integrity...");
if (IntegrityModule.TryVerifyCodeSignature(out var isValid))
{
if (isValid)
{
logger.Info("Application integrity successfully verified.");
}
else
{
logger.Warn("Application integrity is compromised!");
ShowLockScreen(text.Get(TextKey.LockScreen_ApplicationIntegrityMessage), text.Get(TextKey.LockScreen_Title), Enumerable.Empty<LockScreenOption>());
}
}
else
{
logger.Warn("Failed to verify application integrity!");
}
};
timer.Elapsed += (o, args) => VerifyApplicationIntegrity();
timer.Interval = TEN_MINUTES + (new Random().NextDouble() * FIVE_MINUTES);
timer.Start();
if (registry.TryGetNames(RegistryValue.UserHive.Cursors_Key, out var names))
{
foreach (var name in names)
{
registry.StartMonitoring(RegistryValue.UserHive.Cursors_Key, name);
}
}
else
{
logger.Warn("Failed to start monitoring cursor registry values!");
}
if (Settings.Service.IgnoreService)
{
registry.StartMonitoring(RegistryValue.MachineHive.EaseOfAccess_Key, RegistryValue.MachineHive.EaseOfAccess_Name);
}
}
private void VerifyApplicationIntegrity()
{
logger.Info($"Attempting to verify application integrity...");
if (IntegrityModule.TryVerifyCodeSignature(out var isValid))
{
if (isValid)
{
logger.Info("Application integrity successfully verified.");
}
else
{
logger.Warn("Application integrity is compromised!");
ShowLockScreen(text.Get(TextKey.LockScreen_ApplicationIntegrityMessage), text.Get(TextKey.LockScreen_Title), Enumerable.Empty<LockScreenOption>());
}
}
else
{
logger.Warn("Failed to verify application integrity!");
}
}
private void VerifySessionIntegrity()
{
var hasQuitPassword = !string.IsNullOrEmpty(Settings.Security.QuitPasswordHash);
@ -701,9 +715,55 @@ namespace SafeExamBrowser.Client
splashScreen.UpdateStatus(status, true);
}
private void Registry_ValueChanged(object oldValue, object newValue)
private void Registry_ValueChanged(string key, string name, object oldValue, object newValue)
{
logger.Warn($"The ease of access registry value has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen...");
if (key == RegistryValue.UserHive.Cursors_Key)
{
HandleCursorRegistryChange(key, name, oldValue, newValue);
}
else if (key == RegistryValue.MachineHive.EaseOfAccess_Key)
{
HandleEaseOfAccessRegistryChange(key, name, oldValue, newValue);
}
}
private void HandleCursorRegistryChange(string key, string name, object oldValue, object newValue)
{
logger.Warn($@"The cursor registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen...");
if (!sessionLocked)
{
var message = text.Get(TextKey.LockScreen_CursorMessage);
var title = text.Get(TextKey.LockScreen_Title);
var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorContinueOption) };
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorTerminateOption) };
sessionLocked = true;
registry.StopMonitoring(key, name);
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });
if (result.OptionId == continueOption.Id)
{
logger.Info("The session will be allowed to resume as requested by the user...");
}
else if (result.OptionId == terminateOption.Id)
{
logger.Info("Attempting to shutdown as requested by the user...");
TryRequestShutdown();
}
sessionLocked = false;
}
else
{
logger.Info("Lock screen is already active.");
}
}
private void HandleEaseOfAccessRegistryChange(string key, string name, object oldValue, object newValue)
{
logger.Warn($@"The ease of access registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen...");
if (!sessionLocked)
{
@ -713,7 +773,7 @@ namespace SafeExamBrowser.Client
var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_EaseOfAccessTerminateOption) };
sessionLocked = true;
registry.StopMonitoring();
registry.StopMonitoring(key, name);
var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption });

View file

@ -94,8 +94,9 @@ namespace SafeExamBrowser.Client
InitializeLogging();
InitializeText();
uiFactory = BuildUserInterfaceFactory();
var registry = new Registry(ModuleLogger(nameof(Registry)));
uiFactory = BuildUserInterfaceFactory();
actionCenter = uiFactory.CreateActionCenter();
context = new ClientContext();
messageBox = BuildMessageBox();
@ -103,7 +104,7 @@ namespace SafeExamBrowser.Client
networkAdapter = new NetworkAdapter(ModuleLogger(nameof(NetworkAdapter)), nativeMethods);
powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply)));
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client);
systemInfo = new SystemInfo();
systemInfo = new SystemInfo(registry);
taskbar = uiFactory.CreateTaskbar(ModuleLogger("Taskbar"));
taskview = uiFactory.CreateTaskview();
userInfo = new UserInfo(ModuleLogger(nameof(UserInfo)));
@ -116,7 +117,6 @@ namespace SafeExamBrowser.Client
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var fileSystemDialog = BuildFileSystemDialog();
var hashAlgorithm = new HashAlgorithm();
var registry = new Registry(ModuleLogger(nameof(Registry)));
var splashScreen = uiFactory.CreateSplashScreen();
var systemMonitor = new SystemMonitor(ModuleLogger(nameof(SystemMonitor)));
@ -335,9 +335,9 @@ namespace SafeExamBrowser.Client
switch (uiMode)
{
case UserInterfaceMode.Mobile:
return new Mobile.FileSystemDialogFactory(text);
return new Mobile.FileSystemDialogFactory(systemInfo, text);
default:
return new Desktop.FileSystemDialogFactory(text);
return new Desktop.FileSystemDialogFactory(systemInfo, text);
}
}

View file

@ -68,6 +68,9 @@ namespace SafeExamBrowser.I18n.Contracts
LockScreen_ApplicationsAllowOption,
LockScreen_ApplicationsMessage,
LockScreen_ApplicationsTerminateOption,
LockScreen_CursorContinueOption,
LockScreen_CursorMessage,
LockScreen_CursorTerminateOption,
LockScreen_DisplayConfigurationContinueOption,
LockScreen_DisplayConfigurationMessage,
LockScreen_DisplayConfigurationTerminateOption,

View file

@ -162,6 +162,15 @@
<Entry key="LockScreen_ApplicationsTerminateOption">
Safe Exam Browser beenden. WARNUNG: Sie werden keine Möglichkeit haben, Daten zu speichern oder weitere Aktionen auszuführen, SEB wird sofort beendet!
</Entry>
<Entry key="LockScreen_CursorContinueOption">
Die Cursor-Konfiguration vorübergehend zulassen. Dies gilt nur für die momentan laufende Sitzung!
</Entry>
<Entry key="LockScreen_CursorMessage">
Eine verbotene Cursor-Konfiguration wurde detektiert. Bitte wählen Sie eine der verfügbaren Optionen aus und geben Sie das korrekte Passwort ein, um SEB zu entsperren.
</Entry>
<Entry key="LockScreen_CursorTerminateOption">
Safe Exam Browser beenden. WARNUNG: Sie werden keine Möglichkeit haben, Daten zu speichern oder weitere Aktionen auszuführen, SEB wird sofort beendet!
</Entry>
<Entry key="LockScreen_DisplayConfigurationContinueOption">
Bildschirm-Konfiguration temporär erlauben. Dies gilt nur für die momentan laufende Sitzung!
</Entry>

View file

@ -162,6 +162,15 @@
<Entry key="LockScreen_ApplicationsTerminateOption">
Terminate Safe Exam Browser. WARNING: There will be no possibility to save data or perform any further actions, the shutdown will be initiated immediately!
</Entry>
<Entry key="LockScreen_CursorContinueOption">
Temporarily allow the cursor configuration. This applies only to the currently running session!
</Entry>
<Entry key="LockScreen_CursorMessage">
A prohibited cursor configuration has been detected. In order to unlock SEB, please select one of the available options and enter the correct unlock password.
</Entry>
<Entry key="LockScreen_CursorTerminateOption">
Terminate Safe Exam Browser. WARNING: There will be no possibility to save data or perform any further actions, the shutdown will be initiated immediately!
</Entry>
<Entry key="LockScreen_DisplayConfigurationContinueOption">
Temporarily allow the display configuration. This applies only to the currently running session!
</Entry>

View file

@ -162,6 +162,15 @@
<Entry key="LockScreen_ApplicationsTerminateOption">
Finalizar Safe Exam Browser. ADVERTENCIA: No habrá posibilidad de guardar datos o realizar ninguna otra acción, ¡el cierre se iniciará inmediatamente!
</Entry>
<Entry key="LockScreen_CursorContinueOption">
Permitir temporalmente la configuración del cursor. Esto sólo se aplica a la sesión que se está ejecutando en ese momento.
</Entry>
<Entry key="LockScreen_CursorMessage">
Se ha detectado una configuración de cursor prohibida. Para desbloquear SEB, seleccione una de las opciones disponibles e introduzca la contraseña de desbloqueo correcta.
</Entry>
<Entry key="LockScreen_CursorTerminateOption">
Finalizar Safe Exam Browser. ADVERTENCIA: No habrá posibilidad de guardar datos o realizar ninguna otra acción, ¡el cierre se iniciará inmediatamente!
</Entry>
<Entry key="LockScreen_DisplayConfigurationContinueOption">
Permitir temporalmente la configuración de la pantalla. Esto sólo se aplica a la sesión que se está ejecutando en ese momento.
</Entry>

View file

@ -162,6 +162,15 @@
<Entry key="LockScreen_ApplicationsTerminateOption">
Terminer Safe Exam Browser. AVERTISSEMENT: Il n'y aura aucune possibilité de sauvegarder les données ou d'effectuer d'autres actions, la fermeture sera initiée immédiatement!
</Entry>
<Entry key="LockScreen_CursorContinueOption">
Autoriser temporairement la configuration du curseur. Ceci s'applique uniquement à la session en cours!
</Entry>
<Entry key="LockScreen_CursorMessage">
Une configuration interdite du curseur a été détectée. Pour déverrouiller SEB, veuillez sélectionner l'une des options disponibles et saisir le mot de passe de déverrouillage correct.
</Entry>
<Entry key="LockScreen_CursorTerminateOption">
Terminer Safe Exam Browser. AVERTISSEMENT: Il n'y aura aucune possibilité de sauvegarder les données ou d'effectuer d'autres actions, la fermeture sera initiée immédiatement!
</Entry>
<Entry key="LockScreen_DisplayConfigurationContinueOption">
Autoriser temporairement la configuration de l'affichage. Ceci s'applique uniquement à la session en cours!
</Entry>

View file

@ -162,6 +162,15 @@
<Entry key="LockScreen_ApplicationsTerminateOption">
Terminare Safe Exam Browser. ATTENZIONE: Non sarà possibile salvare i dati o eseguire ulteriori azioni, la chiusura verrà avviata immediatamente!
</Entry>
<Entry key="LockScreen_CursorContinueOption">
Consente temporaneamente la configurazione del cursore. Questo vale solo per la sessione in corso!
</Entry>
<Entry key="LockScreen_CursorMessage">
È stata rilevata una configurazione del cursore non consentita. Per sbloccare SEB, seleziona una delle opzioni disponibili e inserisci la password di sblocco corretta.
</Entry>
<Entry key="LockScreen_CursorTerminateOption">
Terminare Safe Exam Browser. ATTENZIONE: Non sarà possibile salvare i dati o eseguire ulteriori azioni, la chiusura verrà avviata immediatamente!
</Entry>
<Entry key="LockScreen_DisplayConfigurationContinueOption">
Consenti temporaneamente la configurazione del display. Questo vale solo per la sessione attualmente in esecuzione!
</Entry>

View file

@ -147,6 +147,15 @@
<Entry key="LockScreen_ApplicationsTerminateOption">
强制关闭防作弊考试专用浏览器。警告:将无法保存数据或执行任何进一步的操作,关闭操作将立即启动。
</Entry>
<Entry key="LockScreen_CursorContinueOption">
暂时允许光标配置。这仅适用于当前运行的会话!
</Entry>
<Entry key="LockScreen_CursorMessage">
已检测到禁止的光标配置。要解锁 SEB请选择一个可用选项并输入正确的解锁密码。
</Entry>
<Entry key="LockScreen_CursorTerminateOption">
终止安全考试浏览器。警告:将无法保存数据或执行任何其他操作,系统将立即关闭!
</Entry>
<Entry key="LockScreen_DisplayConfigurationContinueOption">
暂时允许显示配置。 这仅适用于当前正在运行的会话!
</Entry>

View file

@ -54,16 +54,18 @@ namespace SafeExamBrowser.Runtime
const int THIRTY_SECONDS = 30000;
logger = new Logger();
systemInfo = new SystemInfo();
InitializeConfiguration();
InitializeLogging();
InitializeText();
var nativeMethods = new NativeMethods();
var registry = new Registry(ModuleLogger(nameof(Registry)));
var uiFactory = new UserInterfaceFactory(text);
var userInfo = new UserInfo(ModuleLogger(nameof(UserInfo)));
systemInfo = new SystemInfo(registry);
var args = Environment.GetCommandLineArgs();
var integrityModule = new IntegrityModule(appConfig, ModuleLogger(nameof(IntegrityModule)));
var desktopFactory = new DesktopFactory(ModuleLogger(nameof(DesktopFactory)));
@ -75,7 +77,6 @@ namespace SafeExamBrowser.Runtime
var messageBox = new MessageBoxFactory(text);
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), ModuleLogger(nameof(ProxyFactory)));
var registry = new Registry(ModuleLogger(nameof(Registry)));
var remoteSessionDetector = new RemoteSessionDetector(ModuleLogger(nameof(RemoteSessionDetector)));
var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS);
var runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig);

View file

@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Linq;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
@ -32,14 +34,28 @@ namespace SafeExamBrowser.Runtime.Operations
{
StatusChanged?.Invoke(TextKey.OperationStatus_VerifySessionIntegrity);
return VerifyEaseOfAccessConfiguration();
var success = VerifyCursorConfiguration();
if (success)
{
success = VerifyEaseOfAccessConfiguration();
}
return success ? OperationResult.Success : OperationResult.Failed;
}
public override OperationResult Repeat()
{
StatusChanged?.Invoke(TextKey.OperationStatus_VerifySessionIntegrity);
return VerifyEaseOfAccessConfiguration();
var success = VerifyCursorConfiguration();
if (success)
{
success = VerifyEaseOfAccessConfiguration();
}
return success ? OperationResult.Success : OperationResult.Failed;
}
public override OperationResult Revert()
@ -47,9 +63,44 @@ namespace SafeExamBrowser.Runtime.Operations
return OperationResult.Success;
}
private OperationResult VerifyEaseOfAccessConfiguration()
private bool VerifyCursorConfiguration()
{
var result = OperationResult.Failed;
var success = true;
var systemPath = $@"{Environment.ExpandEnvironmentVariables("%SystemRoot%")}\cursors\";
logger.Info($"Attempting to verify cursor configuration...");
if (registry.TryGetNames(RegistryValue.UserHive.Cursors_Key, out var cursors))
{
foreach (var cursor in cursors.Where(c => !string.IsNullOrWhiteSpace(c)))
{
success &= registry.TryRead(RegistryValue.UserHive.Cursors_Key, cursor, out var value);
success &= value == default || !(value is string) || (value is string path && (string.IsNullOrWhiteSpace(path) || path.StartsWith(systemPath)));
if (!success)
{
logger.Warn($"{(value != default ? $"Cursor configuration is compromised: '{value}'" : $"Failed to verify configuration of cursor '{cursor}'")}! Aborting session initialization...");
break;
}
}
if (success)
{
logger.Info("Cursor configuration successfully verified.");
}
}
else
{
logger.Error("Failed to verify cursor configuration!");
}
return success;
}
private bool VerifyEaseOfAccessConfiguration()
{
var success = false;
logger.Info($"Attempting to verify ease of access configuration...");
@ -57,12 +108,12 @@ namespace SafeExamBrowser.Runtime.Operations
{
if (value == default || (value is string s && string.IsNullOrWhiteSpace(s)))
{
result = OperationResult.Success;
success = true;
logger.Info("Ease of access configuration successfully verified.");
}
else if (!Context.Next.Settings.Service.IgnoreService)
{
result = OperationResult.Success;
success = true;
logger.Info($"Ease of access configuration is compromised ('{value}'), but service will be active in the next session.");
}
else
@ -75,7 +126,7 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Error("Failed to verify ease of access configuration!");
}
return result;
return success;
}
}
}

View file

@ -6,6 +6,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Collections.Generic;
using System.IO;
namespace SafeExamBrowser.SystemComponents.Contracts
{
/// <summary>
@ -62,5 +65,10 @@ namespace SafeExamBrowser.SystemComponents.Contracts
/// Provides the device ID information of the user's Plug and Play devices.
/// </summary>
string[] PlugAndPlayDeviceIds { get; }
/// <summary>
/// Retrieves all logical drives of the computer system.
/// </summary>
IEnumerable<DriveInfo> GetDrives();
}
}

View file

@ -11,5 +11,5 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Registry.Events
/// <summary>
/// Indicates that a registry value has changed.
/// </summary>
public delegate void RegistryValueChangedEventHandler(object oldValue, object newValue);
public delegate void RegistryValueChangedEventHandler(string key, string name, object oldValue, object newValue);
}

View file

@ -6,8 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using SafeExamBrowser.SystemComponents.Contracts.Registry.Events;
using System.Collections.Generic;
using SafeExamBrowser.SystemComponents.Contracts.Registry.Events;
namespace SafeExamBrowser.SystemComponents.Contracts.Registry
{
@ -31,6 +31,11 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Registry
/// </summary>
void StopMonitoring();
/// <summary>
/// Stops the monitoring of the specified registry value.
/// </summary>
void StopMonitoring(string key, string name);
/// <summary>
/// Attempts to read the value of the given name under the specified registry key.
/// </summary>

View file

@ -22,6 +22,35 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Registry
public const string AppPaths_Key = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
public const string EaseOfAccess_Key = @"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\Utilman.exe";
public const string EaseOfAccess_Name = "Debugger";
public const string HardwareConfig_Key = @"HKEY_LOCAL_MACHINE\SYSTEM\HardwareConfig";
}
/// <summary>
/// All registry values located in the user hive.
/// </summary>
public static class UserHive
{
public const string Cursors_Key = @"HKEY_CURRENT_USER\Control Panel\Cursors";
public const string Cursors_AppStarting_Name = "AppStarting";
public const string Cursors_Arrow_Name = "Arrow";
public const string Cursors_Crosshair_Name = "Crosshair";
public const string Cursors_Hand_Name = "Hand";
public const string Cursors_Help_Name = "Help";
public const string Cursors_IBeam_Name = "IBeam";
public const string Cursors_No_Name = "No";
public const string Cursors_NWPen_Name = "NWPen";
public const string Cursors_Person_Name = "Person";
public const string Cursors_Pin_Name = "Pin";
public const string Cursors_SizeAll_Name = "SizeAll";
public const string Cursors_SizeNESW_Name = "SizeNESW";
public const string Cursors_SizeNS_Name = "SizeNS";
public const string Cursors_SizeNWSE_Name = "SizeNWSE";
public const string Cursors_SizeWE_Name = "SizeWE";
public const string Cursors_UpArrow_Name = "UpArrow";
public const string Cursors_Wait_Name = "Wait";
public const string DeviceCache_Key = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\TaskFlow\DeviceCache";
public const string NoDrives_Key = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer";
public const string NoDrives_Name = "NoDrives";
}
}
}

View file

@ -9,7 +9,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Timers;
using Microsoft.Win32;
using SafeExamBrowser.Logging.Contracts;
@ -21,7 +20,7 @@ namespace SafeExamBrowser.SystemComponents.Registry
public class Registry : IRegistry
{
private readonly ILogger logger;
private readonly ConcurrentBag<(string key, string name, object value)> values;
private readonly ConcurrentDictionary<(string key, string name), object> values;
private Timer timer;
@ -30,7 +29,7 @@ namespace SafeExamBrowser.SystemComponents.Registry
public Registry(ILogger logger)
{
this.logger = logger;
this.values = new ConcurrentBag<(string key, string name, object value)>();
this.values = new ConcurrentDictionary<(string key, string name), object>();
}
public void StartMonitoring(string key, string name)
@ -47,7 +46,7 @@ namespace SafeExamBrowser.SystemComponents.Registry
if (TryRead(key, name, out var value))
{
values.Add((key, name, value));
values.TryAdd((key, name), value);
logger.Debug($"Started monitoring value '{name}' from registry key '{key}'. Initial value: '{value}'.");
}
else
@ -58,10 +57,7 @@ namespace SafeExamBrowser.SystemComponents.Registry
public void StopMonitoring()
{
while (!values.IsEmpty)
{
values.TryTake(out _);
}
values.Clear();
if (timer != null)
{
@ -70,19 +66,24 @@ namespace SafeExamBrowser.SystemComponents.Registry
}
}
public void StopMonitoring(string key, string name)
{
values.TryRemove((key, name), out _);
}
public bool TryRead(string key, string name, out object value)
{
var success = true;
var success = false;
value = default;
try
{
value = Microsoft.Win32.Registry.GetValue(key, name, default);
success = true;
}
catch (Exception e)
{
success = false;
logger.Error($"Failed to read value '{name}' from registry key '{key}'!", e);
}
@ -93,155 +94,133 @@ namespace SafeExamBrowser.SystemComponents.Registry
{
names = default;
if (!TryOpenKey(keyName, out var key))
if (TryOpenKey(keyName, out var key))
{
return false;
using (key)
{
try
{
names = key.GetValueNames();
}
catch (Exception e)
{
logger.Error($"Failed to get registry value names for '{keyName}'!", e);
}
}
}
else
{
logger.Warn($"Failed to get names for '{keyName}'.");
}
var success = true;
using (key)
{
try
{
names = key.GetValueNames();
}
catch (Exception e)
{
logger.Error($"Failed to get registry value names '{keyName}'!", e);
success = false;
}
}
return success;
return names != default;
}
public bool TryGetSubKeys(string keyName, out IEnumerable<string> subKeys)
{
subKeys = default;
if (!TryOpenKey(keyName, out var key))
if (TryOpenKey(keyName, out var key))
{
return false;
}
var success = true;
using (key)
{
try
using (key)
{
subKeys = key.GetSubKeyNames();
}
catch (Exception e)
{
logger.Error($"Failed to get registry value names '{keyName}'!", e);
success = false;
try
{
subKeys = key.GetSubKeyNames();
}
catch (Exception e)
{
logger.Error($"Failed to get registry sub key names for '{keyName}'!", e);
}
}
}
else
{
logger.Warn($"Failed to get sub keys for '{keyName}'.");
}
return success;
return subKeys != default;
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
foreach (var item in values)
{
if (TryRead(item.key, item.name, out var value))
if (TryRead(item.Key.key, item.Key.name, out var value))
{
if (item.value != value)
if (!Equals(item.Value, value))
{
logger.Debug($"Value '{item.name}' from registry key '{item.key}' has changed from '{item.value}' to '{value}'!");
ValueChanged?.Invoke(item.value, value);
logger.Debug($"Value '{item.Key.name}' from registry key '{item.Key.key}' has changed from '{item.Value}' to '{value}'!");
ValueChanged?.Invoke(item.Key.key, item.Key.name, item.Value, value);
}
}
else
{
logger.Error($"Failed to monitor value '{item.name}' from registry key '{item.key}'!");
logger.Error($"Failed to monitor value '{item.Key.name}' from registry key '{item.Key.key}'!");
}
}
}
private bool TryGetBaseKeyFromKeyName(string keyName, out RegistryKey baseKey, out string subKeyName)
{
baseKey = default;
subKeyName = default;
string basekeyName;
var baseKeyLength = keyName.IndexOf('\\');
if (baseKeyLength != -1)
{
basekeyName = keyName.Substring(0, baseKeyLength).ToUpper(System.Globalization.CultureInfo.InvariantCulture);
}
else
{
basekeyName = keyName.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
}
switch (basekeyName)
{
case "HKEY_CURRENT_USER":
case "HKCU":
baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64);
break;
case "HKEY_LOCAL_MACHINE":
case "HKLM":
baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
break;
case "HKEY_CLASSES_ROOT":
case "HKCR":
baseKey = RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Registry64);
break;
case "HKEY_USERS":
case "HKU":
baseKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Registry64);
break;
case "HKEY_PERFORMANCE_DATA":
case "HKPD":
baseKey = RegistryKey.OpenBaseKey(RegistryHive.PerformanceData, RegistryView.Registry64);
break;
case "HKEY_CURRENT_CONFIG":
case "HKCC":
baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentConfig, RegistryView.Registry64);
break;
case "HKEY_DYN_DATA":
case "HKDD":
baseKey = RegistryKey.OpenBaseKey(RegistryHive.DynData, RegistryView.Registry64);
break;
default:
return false;
}
if (baseKeyLength == -1 || baseKeyLength == keyName.Length)
{
subKeyName = string.Empty;
}
else
{
subKeyName = keyName.Substring(baseKeyLength + 1, keyName.Length - baseKeyLength - 1);
}
return true;
}
private bool TryOpenKey(string keyName, out RegistryKey key)
{
key = default;
try
{
if (TryGetBaseKeyFromKeyName(keyName, out var baseKey, out var subKey))
if (TryGetHiveForKey(keyName, out var hive))
{
key = baseKey.OpenSubKey(subKey);
if (keyName == hive.Name)
{
key = hive;
}
else
{
key = hive.OpenSubKey(keyName.Replace($@"{hive.Name}\", ""));
}
}
else
{
logger.Warn($"Failed to get hive for key '{keyName}'!");
}
}
catch (Exception e)
{
logger.Error($"Failed to open registry key '{keyName}'!", e);
return false;
}
return key != default;
}
private bool TryGetHiveForKey(string keyName, out RegistryKey hive)
{
var length = keyName.IndexOf('\\');
var name = length != -1 ? keyName.Substring(0, length).ToUpperInvariant() : keyName.ToUpperInvariant();
hive = default;
switch (name)
{
case "HKEY_CLASSES_ROOT":
hive = Microsoft.Win32.Registry.ClassesRoot;
break;
case "HKEY_CURRENT_CONFIG":
hive = Microsoft.Win32.Registry.CurrentConfig;
break;
case "HKEY_CURRENT_USER":
hive = Microsoft.Win32.Registry.CurrentUser;
break;
case "HKEY_LOCAL_MACHINE":
hive = Microsoft.Win32.Registry.LocalMachine;
break;
case "HKEY_PERFORMANCE_DATA":
hive = Microsoft.Win32.Registry.PerformanceData;
break;
case "HKEY_USERS":
hive = Microsoft.Win32.Registry.Users;
break;
}
return hive != default;
}
}
}

View file

@ -8,10 +8,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management;
using System.Windows.Forms;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using BatteryChargeStatus = System.Windows.Forms.BatteryChargeStatus;
using OperatingSystem = SafeExamBrowser.SystemComponents.Contracts.OperatingSystem;
@ -19,6 +21,8 @@ namespace SafeExamBrowser.SystemComponents
{
public class SystemInfo : ISystemInfo
{
private readonly IRegistry registry;
public string BiosInfo { get; private set; }
public string CpuName { get; private set; }
public bool HasBattery { get; private set; }
@ -30,8 +34,10 @@ namespace SafeExamBrowser.SystemComponents
public string OperatingSystemInfo => $"{OperatingSystemName()}, {Environment.OSVersion.VersionString} ({Architecture()})";
public string[] PlugAndPlayDeviceIds { get; private set; }
public SystemInfo()
public SystemInfo(IRegistry registry)
{
this.registry = registry;
InitializeBattery();
InitializeBiosInfo();
InitializeCpuName();
@ -41,6 +47,20 @@ namespace SafeExamBrowser.SystemComponents
InitializePnPDevices();
}
public IEnumerable<DriveInfo> GetDrives()
{
var drives = DriveInfo.GetDrives();
registry.TryRead(RegistryValue.UserHive.NoDrives_Key, RegistryValue.UserHive.NoDrives_Name, out var value);
if (value is int noDrives && noDrives > 0)
{
drives = drives.Where(drive => (noDrives & (int) Math.Pow(2, drive.RootDirectory.ToString()[0] - 65)) == 0).ToArray();
}
return drives;
}
private void InitializeBattery()
{
var status = SystemInformation.PowerStatus.BatteryChargeStatus;

View file

@ -136,16 +136,14 @@ namespace SafeExamBrowser.SystemComponents
private bool HasHistoricVirtualMachineHardwareConfiguration()
{
const string HARDWARE_ROOT_KEY = @"HKEY_LOCAL_MACHINE\SYSTEM\HardwareConfig";
var hasHistoricConfiguration = false;
if (registry.TryGetSubKeys(HARDWARE_ROOT_KEY, out var hardwareConfigSubkeys))
if (registry.TryGetSubKeys(RegistryValue.MachineHive.HardwareConfig_Key, out var hardwareConfigSubkeys))
{
foreach (var configId in hardwareConfigSubkeys)
{
var hardwareConfigKey = $"{HARDWARE_ROOT_KEY}\\{configId}";
var computerIdsKey = $"{hardwareConfigKey}\\ComputerIds";
var hardwareConfigKey = $@"{RegistryValue.MachineHive.HardwareConfig_Key}\{configId}";
var computerIdsKey = $@"{hardwareConfigKey}\ComputerIds";
var success = true;
success &= registry.TryRead(hardwareConfigKey, "BIOSVendor", out var biosVendor);
@ -178,17 +176,15 @@ namespace SafeExamBrowser.SystemComponents
private bool HasLocalVirtualMachineDeviceCache()
{
const string DEVICE_CACHE_PARENT_KEY = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\TaskFlow\DeviceCache";
var deviceName = System.Environment.GetEnvironmentVariable("COMPUTERNAME");
var hasDeviceCache = false;
var hasDeviceCacheKeys = registry.TryGetSubKeys(DEVICE_CACHE_PARENT_KEY, out var deviceCacheKeys);
var hasDeviceCacheKeys = registry.TryGetSubKeys(RegistryValue.UserHive.DeviceCache_Key, out var deviceCacheKeys);
if (deviceName != default && hasDeviceCacheKeys)
{
foreach (var cacheId in deviceCacheKeys)
{
var cacheIdKey = $@"{DEVICE_CACHE_PARENT_KEY}\{cacheId}";
var cacheIdKey = $@"{RegistryValue.UserHive.DeviceCache_Key}\{cacheId}";
var didReadKeys = true;
didReadKeys &= registry.TryRead(cacheIdKey, "DeviceName", out var cacheDeviceName);

View file

@ -8,6 +8,7 @@
using System.Windows;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Desktop.Windows;
@ -16,10 +17,12 @@ namespace SafeExamBrowser.UserInterface.Desktop
{
public class FileSystemDialogFactory : IFileSystemDialog
{
private readonly ISystemInfo systemInfo;
private readonly IText text;
public FileSystemDialogFactory(IText text)
public FileSystemDialogFactory(ISystemInfo systemInfo, IText text)
{
this.systemInfo = systemInfo;
this.text = text;
}
@ -35,11 +38,11 @@ namespace SafeExamBrowser.UserInterface.Desktop
{
if (parent is Window window)
{
return window.Dispatcher.Invoke(() => new FileSystemDialog(element, operation, text, initialPath, message, title, parent, restrictNavigation, showElementPath).Show());
return window.Dispatcher.Invoke(() => new FileSystemDialog(element, operation, systemInfo, text, initialPath, message, title, parent, restrictNavigation, showElementPath).Show());
}
else
{
return new FileSystemDialog(element, operation, text, initialPath, message, title, restrictNavigation: restrictNavigation, showElementPath: showElementPath).Show();
return new FileSystemDialog(element, operation, systemInfo, text, initialPath, message, title, restrictNavigation: restrictNavigation, showElementPath: showElementPath).Show();
}
}
}

View file

@ -15,8 +15,8 @@ using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using FontAwesome.WPF;
using Microsoft.Win32;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Shared.Utilities;
@ -30,6 +30,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
private readonly string message;
private readonly FileSystemOperation operation;
private readonly IWindow parent;
private readonly ISystemInfo systemInfo;
private readonly bool restrictNavigation;
private readonly bool showElementPath;
private readonly IText text;
@ -38,6 +39,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
internal FileSystemDialog(
FileSystemElement element,
FileSystemOperation operation,
ISystemInfo systemInfo,
IText text,
string initialPath = default,
string message = default,
@ -50,6 +52,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
this.initialPath = initialPath;
this.message = message;
this.operation = operation;
this.systemInfo = systemInfo;
this.parent = parent;
this.restrictNavigation = restrictNavigation;
this.showElementPath = showElementPath;
@ -293,20 +296,6 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
InitializeFileSystem();
}
private DriveInfo[] GetDrives()
{
var drives = DriveInfo.GetDrives();
var noDrives = Registry.GetValue("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer", "NoDrives", 0) as int?;
if (noDrives.HasValue && noDrives > 0)
{
return drives.Where(drive => (noDrives & (int) Math.Pow(2, drive.RootDirectory.ToString()[0] - 65)) == 0).ToArray();
}
return drives;
}
private void InitializeFileSystem()
{
if (restrictNavigation && !string.IsNullOrEmpty(initialPath))
@ -337,7 +326,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows
private void InitializeUnrestricted()
{
foreach (var drive in GetDrives())
foreach (var drive in systemInfo.GetDrives())
{
FileSystem.Items.Add(CreateItem(drive.RootDirectory));
}

View file

@ -8,6 +8,7 @@
using System.Windows;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Mobile.Windows;
@ -16,10 +17,12 @@ namespace SafeExamBrowser.UserInterface.Mobile
{
public class FileSystemDialogFactory : IFileSystemDialog
{
private readonly ISystemInfo systemInfo;
private readonly IText text;
public FileSystemDialogFactory(IText text)
public FileSystemDialogFactory(ISystemInfo systemInfo, IText text)
{
this.systemInfo = systemInfo;
this.text = text;
}
@ -35,11 +38,11 @@ namespace SafeExamBrowser.UserInterface.Mobile
{
if (parent is Window window)
{
return window.Dispatcher.Invoke(() => new FileSystemDialog(element, operation, text, initialPath, message, title, parent, restrictNavigation, showElementPath).Show());
return window.Dispatcher.Invoke(() => new FileSystemDialog(element, operation, systemInfo, text, initialPath, message, title, parent, restrictNavigation, showElementPath).Show());
}
else
{
return new FileSystemDialog(element, operation, text, initialPath, message, title, restrictNavigation: restrictNavigation, showElementPath: showElementPath).Show();
return new FileSystemDialog(element, operation, systemInfo, text, initialPath, message, title, restrictNavigation: restrictNavigation, showElementPath: showElementPath).Show();
}
}
}

View file

@ -15,8 +15,8 @@ using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using FontAwesome.WPF;
using Microsoft.Win32;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Shared.Utilities;
@ -30,6 +30,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
private readonly string message;
private readonly FileSystemOperation operation;
private readonly IWindow parent;
private readonly ISystemInfo systemInfo;
private readonly bool restrictNavigation;
private readonly bool showElementPath;
private readonly IText text;
@ -38,6 +39,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
internal FileSystemDialog(
FileSystemElement element,
FileSystemOperation operation,
ISystemInfo systemInfo,
IText text,
string initialPath = default,
string message = default,
@ -50,6 +52,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
this.initialPath = initialPath;
this.message = message;
this.operation = operation;
this.systemInfo = systemInfo;
this.parent = parent;
this.restrictNavigation = restrictNavigation;
this.showElementPath = showElementPath;
@ -293,20 +296,6 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
InitializeFileSystem();
}
private DriveInfo[] GetDrives()
{
var drives = DriveInfo.GetDrives();
var noDrives = Registry.GetValue("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer", "NoDrives", 0) as int?;
if (noDrives.HasValue && noDrives > 0)
{
return drives.Where(drive => (noDrives & (int) Math.Pow(2, drive.RootDirectory.ToString()[0] - 65)) == 0).ToArray();
}
return drives;
}
private void InitializeFileSystem()
{
if (restrictNavigation && !string.IsNullOrEmpty(initialPath))
@ -337,7 +326,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows
private void InitializeUnrestricted()
{
foreach (var drive in GetDrives())
foreach (var drive in systemInfo.GetDrives())
{
FileSystem.Items.Add(CreateItem(drive.RootDirectory));
}