diff --git a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs index 04e8cce3..a5e212dc 100644 --- a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs +++ b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs @@ -414,7 +414,7 @@ namespace SafeExamBrowser.Client.UnitTests actionCenter.Setup(t => t.InitializeBounds()).Callback(() => boundsActionCenter = ++order); displayMonitor.Setup(m => m.InitializePrimaryDisplay(It.Is<int>(h => h == height))).Callback(() => workingArea = ++order); - displayMonitor.Setup(m => m.IsAllowedConfiguration(It.IsAny<DisplaySettings>())).Returns(true); + displayMonitor.Setup(m => m.ValidateConfiguration(It.IsAny<DisplaySettings>())).Returns(new ValidationResult { IsAllowed = true }); taskbar.Setup(t => t.GetAbsoluteHeight()).Returns(height); taskbar.Setup(t => t.InitializeBounds()).Callback(() => boundsTaskbar = ++order); @@ -445,7 +445,7 @@ namespace SafeExamBrowser.Client.UnitTests actionCenter.Setup(t => t.InitializeBounds()).Callback(() => boundsActionCenter = ++order); displayMonitor.Setup(w => w.InitializePrimaryDisplay(It.Is<int>(h => h == 0))).Callback(() => workingArea = ++order); - displayMonitor.Setup(m => m.IsAllowedConfiguration(It.IsAny<DisplaySettings>())).Returns(true); + displayMonitor.Setup(m => m.ValidateConfiguration(It.IsAny<DisplaySettings>())).Returns(new ValidationResult { IsAllowed = true }); taskbar.Setup(t => t.GetAbsoluteHeight()).Returns(height); taskbar.Setup(t => t.InitializeBounds()).Callback(() => boundsTaskbar = ++order); diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs index 2127bfb1..66ecd668 100644 --- a/SafeExamBrowser.Client/ClientController.cs +++ b/SafeExamBrowser.Client/ClientController.cs @@ -513,7 +513,7 @@ namespace SafeExamBrowser.Client taskbar.InitializeBounds(); logger.Info("Desktop successfully restored."); - if (!displayMonitor.IsAllowedConfiguration(Settings.Display)) + if (!displayMonitor.ValidateConfiguration(Settings.Display).IsAllowed) { var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_DisplayConfigurationContinueOption) }; var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_DisplayConfigurationTerminateOption) }; diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs index 344a544c..36eb48cd 100644 --- a/SafeExamBrowser.I18n.Contracts/TextKey.cs +++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs @@ -100,6 +100,8 @@ namespace SafeExamBrowser.I18n.Contracts MessageBox_NoButton, MessageBox_DisplayConfigurationError, MessageBox_DisplayConfigurationErrorTitle, + MessageBox_DisplayConfigurationInternal, + MessageBox_DisplayConfigurationInternalOrExternal, MessageBox_NotSupportedConfigurationResource, MessageBox_NotSupportedConfigurationResourceTitle, MessageBox_OkButton, diff --git a/SafeExamBrowser.I18n/Data/de.xml b/SafeExamBrowser.I18n/Data/de.xml index 129ae8de..3db4478f 100644 --- a/SafeExamBrowser.I18n/Data/de.xml +++ b/SafeExamBrowser.I18n/Data/de.xml @@ -220,11 +220,17 @@ Fehler beim Herunterladen </Entry> <Entry key="MessageBox_DisplayConfigurationError"> - Die aktive Bildschirm-Konfiguration ist nicht erlaubt! Bitte konsultieren Sie das Applikations-Protokoll für mehr Informationen. SEB wird sich nun beenden... + Die aktive Bildschirm-Konfiguration ist nicht erlaubt! Erlaubt sind %%_ALLOWED_COUNT_%% %%_TYPE_%% Bildschirm(e), erkannt wurden %%_INTERNAL_COUNT_%% interne(r) und %%_EXTERNAL_COUNT_%% externe(r) Bildschirm(e). Bitte konsultieren Sie das Applikations-Protokoll für mehr Informationen. SEB wird sich nun beenden... </Entry> <Entry key="MessageBox_DisplayConfigurationErrorTitle"> Verbotene Bildschirm-Konfiguration </Entry> + <Entry key="MessageBox_DisplayConfigurationInternal"> + interne(r) + </Entry> + <Entry key="MessageBox_DisplayConfigurationInternalOrExternal"> + interne(r) oder externe(r) + </Entry> <Entry key="MessageBox_InvalidConfigurationData"> Die Konfigurations-Ressource "%%URI%%" enthält ungültige Daten! </Entry> diff --git a/SafeExamBrowser.I18n/Data/en.xml b/SafeExamBrowser.I18n/Data/en.xml index ae0cd4c2..2d0302ed 100644 --- a/SafeExamBrowser.I18n/Data/en.xml +++ b/SafeExamBrowser.I18n/Data/en.xml @@ -220,11 +220,17 @@ Download Error </Entry> <Entry key="MessageBox_DisplayConfigurationError"> - The active display configuration is not permitted. Please consult the log files for more information. SEB will now shut down... + The active display configuration is not permitted. %%_ALLOWED_COUNT_%% %%_TYPE_%% display(s) are allowed, but %%_INTERNAL_COUNT_%% internal and %%_EXTERNAL_COUNT_%% external display(s) were detected. Please consult the log files for more information. SEB will now shut down... </Entry> <Entry key="MessageBox_DisplayConfigurationErrorTitle"> Prohibited Display Configuration </Entry> + <Entry key="MessageBox_DisplayConfigurationInternal"> + internal + </Entry> + <Entry key="MessageBox_DisplayConfigurationInternalOrExternal"> + internal or external + </Entry> <Entry key="MessageBox_InvalidConfigurationData"> The configuration resource "%%URI%%" contains invalid data! </Entry> diff --git a/SafeExamBrowser.I18n/Data/fr.xml b/SafeExamBrowser.I18n/Data/fr.xml index d6579847..31574f09 100644 --- a/SafeExamBrowser.I18n/Data/fr.xml +++ b/SafeExamBrowser.I18n/Data/fr.xml @@ -220,11 +220,17 @@ Erreur de téléchargement </Entry> <Entry key="MessageBox_DisplayConfigurationError"> - La configuration d'affichage active n'est pas autorisée. Veuillez consulter les fichiers journaux pour plus d'informations. SEB va maintenant s'arrêter... + La configuration d'affichage active n'est pas autorisée. %%_ALLOWED_COUNT_%% affichages %%_TYPE_%% sont autorisés, mais %%_INTERNAL_COUNT_%% affichages interne(s) et %%_EXTERNAL_COUNT_%% externe(s) ont été détecté(s). Veuillez consulter les fichiers journaux pour plus d'informations. SEB va maintenant s'arrêter... </Entry> <Entry key="MessageBox_DisplayConfigurationErrorTitle"> Configuration d'affichage interdite </Entry> + <Entry key="MessageBox_DisplayConfigurationInternal"> + interne(s) + </Entry> + <Entry key="MessageBox_DisplayConfigurationInternalOrExternal"> + interne(s) ou externe(s) + </Entry> <Entry key="MessageBox_InvalidConfigurationData"> La ressource de configuration "%%URI%%" contient des données non valides! </Entry> diff --git a/SafeExamBrowser.I18n/Data/it.xml b/SafeExamBrowser.I18n/Data/it.xml index 3c248bc1..bf80f846 100644 --- a/SafeExamBrowser.I18n/Data/it.xml +++ b/SafeExamBrowser.I18n/Data/it.xml @@ -220,11 +220,17 @@ Errore di download </Entry> <Entry key="MessageBox_DisplayConfigurationError"> - La configurazione del display attiva non è consentita. Si prega di consultare i file di registro per ulteriori informazioni. SEB ora si spegnerà... + La configurazione del display attiva non è consentita. Sono consentiti %%_ALLOWED_COUNT_%% display %%_TYPE_%%, ma sono stati rilevati %%_INTERNAL_COUNT_%% display interno/i e %%_EXTERNAL_COUNT_%% esterno/i. Si prega di consultare i file di registro per ulteriori informazioni. SEB ora si spegnerà... </Entry> <Entry key="MessageBox_DisplayConfigurationErrorTitle"> Configurazione del display vietata </Entry> + <Entry key="MessageBox_DisplayConfigurationInternal"> + interno/i + </Entry> + <Entry key="MessageBox_DisplayConfigurationInternalOrExternal"> + interno/i o esterno/i + </Entry> <Entry key="MessageBox_InvalidConfigurationData"> La risorsa di configurazione "%%URI%%" contiene dati non validi! </Entry> diff --git a/SafeExamBrowser.I18n/Data/zh.xml b/SafeExamBrowser.I18n/Data/zh.xml index c48c08ca..a8d9492a 100644 --- a/SafeExamBrowser.I18n/Data/zh.xml +++ b/SafeExamBrowser.I18n/Data/zh.xml @@ -190,11 +190,17 @@ 下载错误 </Entry> <Entry key="MessageBox_DisplayConfigurationError"> - 不允许使用活动的显示配置。 请查阅日志文件以获取更多信息。 SEB现在将关闭... + 不允许使用活动的显示配置。 允许使用 %%_ALLOWED_COUNT_%% 个%%_TYPE_%%显示器,但检测到 %%_INTERNAL_COUNT_%% 个内部和 %%_EXTERNAL_COUNT_%% 个外部显示器。 请查阅日志文件以获取更多信息。 SEB现在将关闭... </Entry> <Entry key="MessageBox_DisplayConfigurationErrorTitle"> 禁止的显示配置 </Entry> + <Entry key="MessageBox_DisplayConfigurationInternal"> + 内部的 + </Entry> + <Entry key="MessageBox_DisplayConfigurationInternalOrExternal"> + 内部或外部 + </Entry> <Entry key="MessageBox_InvalidConfigurationData"> 配置"%%URI%%" 中包含无效数据。 </Entry> @@ -360,6 +366,9 @@ <Entry key="OperationStatus_InitializeRuntimeConnection"> 初始化运行时连接 </Entry> + <Entry key="OperationStatus_InitializeServer"> + 初始化 SEB 服务器 + </Entry> <Entry key="OperationStatus_InitializeServiceSession"> 初始化服务会话 </Entry> diff --git a/SafeExamBrowser.Monitoring.Contracts/Display/IDisplayMonitor.cs b/SafeExamBrowser.Monitoring.Contracts/Display/IDisplayMonitor.cs index 86d6f76c..1624cb6c 100644 --- a/SafeExamBrowser.Monitoring.Contracts/Display/IDisplayMonitor.cs +++ b/SafeExamBrowser.Monitoring.Contracts/Display/IDisplayMonitor.cs @@ -26,11 +26,6 @@ namespace SafeExamBrowser.Monitoring.Contracts.Display /// </summary> void InitializePrimaryDisplay(int taskbarHeight); - /// <summary> - /// Indicates whether the currently active display configuration is allowed according to the given settings. - /// </summary> - bool IsAllowedConfiguration(DisplaySettings settings); - /// <summary> /// Prevents the computer from entering sleep mode and turning its display(s) off. /// </summary> @@ -50,5 +45,10 @@ namespace SafeExamBrowser.Monitoring.Contracts.Display /// Stops monitoring for display changes. /// </summary> void StopMonitoringDisplayChanges(); + + /// <summary> + /// Validates the currently active display configuration according to the given settings. + /// </summary> + ValidationResult ValidateConfiguration(DisplaySettings settings); } } diff --git a/SafeExamBrowser.Monitoring.Contracts/Display/ValidationResult.cs b/SafeExamBrowser.Monitoring.Contracts/Display/ValidationResult.cs new file mode 100644 index 00000000..32bf3d9e --- /dev/null +++ b/SafeExamBrowser.Monitoring.Contracts/Display/ValidationResult.cs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +namespace SafeExamBrowser.Monitoring.Contracts.Display +{ + /// <summary> + /// Provides the result of a display configuration validation. + /// </summary> + public class ValidationResult + { + /// <summary> + /// Specifies the count of external displays detected. + /// </summary> + public int ExternalDisplays { get; set; } + + /// <summary> + /// Specifies the count of internal displays detected. + /// </summary> + public int InternalDisplays { get; set; } + + /// <summary> + /// Indicates whether the active display configuration is allowed. + /// </summary> + public bool IsAllowed { get; set; } + } +} diff --git a/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj b/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj index ff181e47..197fd19b 100644 --- a/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj +++ b/SafeExamBrowser.Monitoring.Contracts/SafeExamBrowser.Monitoring.Contracts.csproj @@ -60,6 +60,7 @@ <Compile Include="Display\Events\DisplayChangedEventHandler.cs" /> <Compile Include="Applications\Events\ExplorerStartedEventHandler.cs" /> <Compile Include="Display\IDisplayMonitor.cs" /> + <Compile Include="Display\ValidationResult.cs" /> <Compile Include="Keyboard\IKeyboardInterceptor.cs" /> <Compile Include="Mouse\IMouseInterceptor.cs" /> <Compile Include="Applications\InitializationResult.cs" /> diff --git a/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs b/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs index 6fc08abd..4c7adbd1 100644 --- a/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs +++ b/SafeExamBrowser.Monitoring/Display/DisplayMonitor.cs @@ -46,41 +46,6 @@ namespace SafeExamBrowser.Monitoring.Display InitializeWallpaper(); } - public bool IsAllowedConfiguration(DisplaySettings settings) - { - var allowed = false; - - if (TryLoadDisplays(out var displays)) - { - var active = displays.Where(d => d.IsActive); - var count = active.Count(); - - allowed = count <= settings.AllowedDisplays; - - if (allowed) - { - logger.Info($"Detected {count} active displays, {settings.AllowedDisplays} are allowed."); - } - else - { - logger.Warn($"Detected {count} active displays but only {settings.AllowedDisplays} are allowed!"); - } - - if (settings.InternalDisplayOnly && active.Any(d => !d.IsInternal)) - { - allowed = false; - logger.Warn("Detected external display but only internal displays are allowed!"); - } - } - else - { - allowed = settings.IgnoreError; - logger.Warn($"Failed to validate display configuration, {(allowed ? "ignoring error" : "active configuration is not allowed")}."); - } - - return allowed; - } - public void PreventSleepMode() { nativeMethods.PreventSleepMode(); @@ -105,6 +70,43 @@ namespace SafeExamBrowser.Monitoring.Display logger.Info("Stopped monitoring display changes."); } + public ValidationResult ValidateConfiguration(DisplaySettings settings) + { + var result = new ValidationResult(); + + if (TryLoadDisplays(out var displays)) + { + var active = displays.Where(d => d.IsActive); + var count = active.Count(); + + result.ExternalDisplays = active.Count(d => !d.IsInternal); + result.InternalDisplays = active.Count(d => d.IsInternal); + result.IsAllowed = count <= settings.AllowedDisplays; + + if (result.IsAllowed) + { + logger.Info($"Detected {count} active displays, {settings.AllowedDisplays} are allowed."); + } + else + { + logger.Warn($"Detected {count} active displays but only {settings.AllowedDisplays} are allowed!"); + } + + if (settings.InternalDisplayOnly && active.Any(d => !d.IsInternal)) + { + result.IsAllowed = false; + logger.Warn("Detected external display but only internal displays are allowed!"); + } + } + else + { + result.IsAllowed = settings.IgnoreError; + logger.Warn($"Failed to validate display configuration, {(result.IsAllowed ? "ignoring error" : "active configuration is not allowed")}."); + } + + return result; + } + private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) { logger.Info("Display change detected!"); diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 742eb9b5..2d188c92 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -91,7 +91,7 @@ namespace SafeExamBrowser.Runtime sessionOperations.Enqueue(new DisclaimerOperation(logger, sessionContext)); sessionOperations.Enqueue(new RemoteSessionOperation(remoteSessionDetector, logger, sessionContext)); sessionOperations.Enqueue(new VirtualMachineOperation(vmDetector, logger, sessionContext)); - sessionOperations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, sessionContext)); + sessionOperations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, sessionContext, text)); sessionOperations.Enqueue(new ServiceOperation(logger, runtimeHost, serviceProxy, sessionContext, THIRTY_SECONDS, userInfo)); sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS)); sessionOperations.Enqueue(new ProctoringWorkaroundOperation(logger, sessionContext)); diff --git a/SafeExamBrowser.Runtime/Operations/DisplayMonitorOperation.cs b/SafeExamBrowser.Runtime/Operations/DisplayMonitorOperation.cs index 9ead1567..6984e079 100644 --- a/SafeExamBrowser.Runtime/Operations/DisplayMonitorOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/DisplayMonitorOperation.cs @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; using SafeExamBrowser.Core.Contracts.OperationModel; using SafeExamBrowser.Core.Contracts.OperationModel.Events; using SafeExamBrowser.I18n.Contracts; @@ -20,14 +21,16 @@ namespace SafeExamBrowser.Runtime.Operations { private readonly IDisplayMonitor displayMonitor; private readonly ILogger logger; + private readonly IText text; public override event ActionRequiredEventHandler ActionRequired; public override event StatusChangedEventHandler StatusChanged; - public DisplayMonitorOperation(IDisplayMonitor displayMonitor, ILogger logger, SessionContext context) : base(context) + public DisplayMonitorOperation(IDisplayMonitor displayMonitor, ILogger logger, SessionContext context, IText text) : base(context) { this.displayMonitor = displayMonitor; this.logger = logger; + this.text = text; } public override OperationResult Perform() @@ -47,26 +50,34 @@ namespace SafeExamBrowser.Runtime.Operations private OperationResult CheckDisplayConfiguration() { - var args = new MessageEventArgs - { - Action = MessageBoxAction.Ok, - Icon = MessageBoxIcon.Error, - Message = TextKey.MessageBox_DisplayConfigurationError, - Title = TextKey.MessageBox_DisplayConfigurationErrorTitle - }; - var result = OperationResult.Aborted; - logger.Info("Validating display configuration..."); StatusChanged?.Invoke(TextKey.OperationStatus_ValidateDisplayConfiguration); - if (displayMonitor.IsAllowedConfiguration(Context.Next.Settings.Display)) + var result = OperationResult.Failed; + var validation = displayMonitor.ValidateConfiguration(Context.Next.Settings.Display); + + if (validation.IsAllowed) { logger.Info("Display configuration is allowed."); result = OperationResult.Success; } else { + var args = new MessageEventArgs + { + Action = MessageBoxAction.Ok, + Icon = MessageBoxIcon.Error, + Message = TextKey.MessageBox_DisplayConfigurationError, + Title = TextKey.MessageBox_DisplayConfigurationErrorTitle + }; + logger.Error("Display configuration is not allowed!"); + + args.MessagePlaceholders.Add("%%_ALLOWED_COUNT_%%", Convert.ToString(Context.Next.Settings.Display.AllowedDisplays)); + args.MessagePlaceholders.Add("%%_TYPE_%%", Context.Next.Settings.Display.InternalDisplayOnly ? text.Get(TextKey.MessageBox_DisplayConfigurationInternal) : text.Get(TextKey.MessageBox_DisplayConfigurationInternalOrExternal)); + args.MessagePlaceholders.Add("%%_EXTERNAL_COUNT_%%", Convert.ToString(validation.ExternalDisplays)); + args.MessagePlaceholders.Add("%%_INTERNAL_COUNT_%%", Convert.ToString(validation.InternalDisplays)); + ActionRequired?.Invoke(args); }