diff --git a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs index c293bf1e..dab3a886 100644 --- a/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs +++ b/SafeExamBrowser.Client.UnitTests/ClientControllerTests.cs @@ -28,6 +28,7 @@ using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Windows; +using SafeExamBrowser.UserInterface.Contracts.Windows.Data; using SafeExamBrowser.WindowsApi.Contracts; namespace SafeExamBrowser.Client.UnitTests @@ -170,11 +171,9 @@ namespace SafeExamBrowser.Client.UnitTests RequestId = Guid.NewGuid() }; var dialog = new Mock(); - var result = new Mock(); + var result = new PasswordDialogResult { Password = "blubb", Success = true }; - dialog.Setup(d => d.Show(It.IsAny())).Returns(result.Object); - result.SetupGet(r => r.Password).Returns("blubb"); - result.SetupGet(r => r.Success).Returns(true); + dialog.Setup(d => d.Show(It.IsAny())).Returns(result); uiFactory.Setup(f => f.CreatePasswordDialog(It.IsAny(), It.IsAny())).Returns(dialog.Object); sut.TryStart(); @@ -182,8 +181,8 @@ namespace SafeExamBrowser.Client.UnitTests runtimeProxy.Verify(p => p.SubmitPassword( It.Is(g => g == args.RequestId), - It.Is(b => b == result.Object.Success), - It.Is(s => s == result.Object.Password)), Times.Once); + It.Is(b => b == result.Success), + It.Is(s => s == result.Password)), Times.Once); } [TestMethod] @@ -461,14 +460,11 @@ namespace SafeExamBrowser.Client.UnitTests { var args = new System.ComponentModel.CancelEventArgs(); var dialog = new Mock(); - var dialogResult = new Mock(); - var password = "blobb"; + var dialogResult = new PasswordDialogResult { Password = "blobb", Success = true }; settings.QuitPasswordHash = "1234"; - dialog.Setup(d => d.Show(It.IsAny())).Returns(dialogResult.Object); - dialogResult.SetupGet(r => r.Password).Returns(password); - dialogResult.SetupGet(r => r.Success).Returns(true); - hashAlgorithm.Setup(h => h.GenerateHashFor(It.Is(s => s == password))).Returns(settings.QuitPasswordHash); + dialog.Setup(d => d.Show(It.IsAny())).Returns(dialogResult); + hashAlgorithm.Setup(h => h.GenerateHashFor(It.Is(s => s == dialogResult.Password))).Returns(settings.QuitPasswordHash); runtimeProxy.Setup(r => r.RequestShutdown()).Returns(new CommunicationResult(true)); uiFactory.Setup(u => u.CreatePasswordDialog(It.IsAny(), It.IsAny())).Returns(dialog.Object); @@ -486,11 +482,10 @@ namespace SafeExamBrowser.Client.UnitTests { var args = new System.ComponentModel.CancelEventArgs(); var dialog = new Mock(); - var dialogResult = new Mock(); + var dialogResult = new PasswordDialogResult { Success = false }; settings.QuitPasswordHash = "1234"; - dialog.Setup(d => d.Show(It.IsAny())).Returns(dialogResult.Object); - dialogResult.SetupGet(r => r.Success).Returns(false); + dialog.Setup(d => d.Show(It.IsAny())).Returns(dialogResult); runtimeProxy.Setup(r => r.RequestShutdown()).Returns(new CommunicationResult(true)); uiFactory.Setup(u => u.CreatePasswordDialog(It.IsAny(), It.IsAny())).Returns(dialog.Object); @@ -508,12 +503,10 @@ namespace SafeExamBrowser.Client.UnitTests { var args = new System.ComponentModel.CancelEventArgs(); var dialog = new Mock(); - var dialogResult = new Mock(); + var dialogResult = new PasswordDialogResult { Password = "blobb", Success = true }; settings.QuitPasswordHash = "1234"; - dialog.Setup(d => d.Show(It.IsAny())).Returns(dialogResult.Object); - dialogResult.SetupGet(r => r.Password).Returns("blobb"); - dialogResult.SetupGet(r => r.Success).Returns(true); + dialog.Setup(d => d.Show(It.IsAny())).Returns(dialogResult); hashAlgorithm.Setup(h => h.GenerateHashFor(It.IsAny())).Returns("9876"); uiFactory.Setup(u => u.CreatePasswordDialog(It.IsAny(), It.IsAny())).Returns(dialog.Object); diff --git a/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs b/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs index fa81b364..6efb788f 100644 --- a/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs +++ b/SafeExamBrowser.Client.UnitTests/Operations/ShellOperationTests.cs @@ -29,7 +29,6 @@ namespace SafeExamBrowser.Client.UnitTests.Operations public class ShellOperationTests { private Mock actionCenter; - private List activators; private Mock audio; private ClientContext context; private Mock logger; @@ -52,7 +51,6 @@ namespace SafeExamBrowser.Client.UnitTests.Operations public void Initialize() { actionCenter = new Mock(); - activators = new List(); audio = new Mock(); context = new ClientContext(); logger = new Mock(); @@ -77,7 +75,6 @@ namespace SafeExamBrowser.Client.UnitTests.Operations sut = new ShellOperation( actionCenter.Object, - activators, audio.Object, aboutInfo.Object, aboutController.Object, @@ -109,7 +106,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations foreach (var activator in activatorMocks) { - activators.Add(activator.Object); + context.Activators.Add(activator.Object); } sut.Perform(); @@ -272,7 +269,7 @@ namespace SafeExamBrowser.Client.UnitTests.Operations foreach (var activator in activatorMocks) { - activators.Add(activator.Object); + context.Activators.Add(activator.Object); } sut.Revert(); diff --git a/SafeExamBrowser.Client/ClientContext.cs b/SafeExamBrowser.Client/ClientContext.cs index e729521f..bc4e366e 100644 --- a/SafeExamBrowser.Client/ClientContext.cs +++ b/SafeExamBrowser.Client/ClientContext.cs @@ -7,10 +7,12 @@ */ using System; +using System.Collections.Generic; using SafeExamBrowser.Browser.Contracts; using SafeExamBrowser.Communication.Contracts.Hosts; using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Settings; +using SafeExamBrowser.UserInterface.Contracts.Shell; namespace SafeExamBrowser.Client { @@ -19,6 +21,11 @@ namespace SafeExamBrowser.Client /// internal class ClientContext { + /// + /// All activators for the action center. + /// + internal IList Activators { get; } + /// /// The global application configuration. /// @@ -43,5 +50,10 @@ namespace SafeExamBrowser.Client /// The settings for the current session. /// internal AppSettings Settings { get; set; } + + internal ClientContext() + { + Activators = new List(); + } } } diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs index 3d87c78d..a23e59e5 100644 --- a/SafeExamBrowser.Client/ClientController.cs +++ b/SafeExamBrowser.Client/ClientController.cs @@ -30,6 +30,7 @@ using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Windows; +using SafeExamBrowser.UserInterface.Contracts.Windows.Data; using SafeExamBrowser.WindowsApi.Contracts; namespace SafeExamBrowser.Client @@ -235,9 +236,60 @@ namespace SafeExamBrowser.Client private void ApplicationMonitor_TerminationFailed(IEnumerable applications) { - // foreach actionCenterActivator -> Pause - // TODO: Show lock screen! - // foreach actionCenterActivator -> Resume + var applicationList = string.Join(Environment.NewLine, applications.Select(a => $"- {a.Name}")); + var message = $"{text.Get(TextKey.LockScreen_Message)}{Environment.NewLine}{Environment.NewLine}{applicationList}"; + var title = text.Get(TextKey.LockScreen_Title); + var hasQuitPassword = !string.IsNullOrEmpty(Settings.QuitPasswordHash); + var allowOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_AllowOption) }; + var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_TerminateOption) }; + var lockScreen = uiFactory.CreateLockScreen(message, title, new [] { allowOption, terminateOption }); + var result = default(LockScreenResult); + + logger.Warn("Showing lock screen due to failed termination of blacklisted application(s)!"); + PauseActivators(); + lockScreen.Show(); + + for (var unlocked = false; !unlocked;) + { + result = lockScreen.WaitForResult(); + + if (hasQuitPassword) + { + var passwordHash = hashAlgorithm.GenerateHashFor(result.Password); + var isCorrect = Settings.QuitPasswordHash.Equals(passwordHash, StringComparison.OrdinalIgnoreCase); + + if (isCorrect) + { + logger.Info("The user entered the correct unlock password."); + unlocked = true; + } + else + { + logger.Info("The user entered the wrong unlock password."); + messageBox.Show(TextKey.MessageBox_InvalidUnlockPassword, TextKey.MessageBox_InvalidUnlockPasswordTitle, icon: MessageBoxIcon.Warning, parent: lockScreen); + } + } + else + { + logger.Warn($"No unlock password is defined, allowing user to resume session!"); + unlocked = true; + } + } + + lockScreen.Close(); + ResumeActivators(); + logger.Info("Closed lock screen."); + + if (result.OptionId == allowOption.Id) + { + logger.Info($"The blacklisted application(s) {string.Join(", ", applications.Select(a => $"'{a.Name}'"))} will be temporarily allowed."); + } + else if (result.OptionId == terminateOption.Id) + { + logger.Info("Initiating shutdown request..."); + + TryRequestShutdown(); + } } private void Browser_ConfigurationDownloadRequested(string fileName, DownloadEventArgs args) @@ -409,16 +461,16 @@ namespace SafeExamBrowser.Client private void Shell_QuitButtonClicked(System.ComponentModel.CancelEventArgs args) { - terminationActivator.Pause(); + PauseActivators(); args.Cancel = !TryInitiateShutdown(); - terminationActivator.Resume(); + ResumeActivators(); } private void TerminationActivator_Activated() { - terminationActivator.Pause(); + PauseActivators(); TryInitiateShutdown(); - terminationActivator.Resume(); + ResumeActivators(); } private void AskForAutomaticApplicationTermination(ApplicationTerminationEventArgs args) @@ -442,10 +494,33 @@ namespace SafeExamBrowser.Client messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: splashScreen); } + private void PauseActivators() + { + // TODO: Same for task view activator! + terminationActivator.Pause(); + + foreach (var activator in context.Activators) + { + activator.Pause(); + } + } + + private void ResumeActivators() + { + // TODO: Same for task view activator! + terminationActivator.Resume(); + + foreach (var activator in context.Activators) + { + activator.Resume(); + } + } + private bool TryInitiateShutdown() { - var hasQuitPassword = !String.IsNullOrEmpty(Settings.QuitPasswordHash); + var hasQuitPassword = !string.IsNullOrEmpty(Settings.QuitPasswordHash); var requestShutdown = false; + var succes = false; if (hasQuitPassword) { @@ -458,20 +533,10 @@ namespace SafeExamBrowser.Client if (requestShutdown) { - var communication = runtime.RequestShutdown(); - - if (communication.Success) - { - return true; - } - else - { - logger.Error("Failed to communicate shutdown request to the runtime!"); - messageBox.Show(TextKey.MessageBox_QuitError, TextKey.MessageBox_QuitErrorTitle, icon: MessageBoxIcon.Error); - } + succes = TryRequestShutdown(); } - return false; + return succes; } private bool TryConfirmShutdown() @@ -512,5 +577,18 @@ namespace SafeExamBrowser.Client return false; } + + private bool TryRequestShutdown() + { + var communication = runtime.RequestShutdown(); + + if (!communication.Success) + { + logger.Error("Failed to communicate shutdown request to the runtime!"); + messageBox.Show(TextKey.MessageBox_QuitError, TextKey.MessageBox_QuitErrorTitle, icon: MessageBoxIcon.Error); + } + + return communication.Success; + } } } diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index b9fce267..7eab91d3 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -243,14 +243,8 @@ namespace SafeExamBrowser.Client var logController = new LogNotificationController(logger, uiFactory); var powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply))); var wirelessAdapter = new WirelessAdapter(ModuleLogger(nameof(WirelessAdapter))); - var activators = new IActionCenterActivator[] - { - new KeyboardActivator(ModuleLogger(nameof(KeyboardActivator))), - new TouchActivator(ModuleLogger(nameof(TouchActivator))) - }; var operation = new ShellOperation( actionCenter, - activators, audio, aboutInfo, aboutController, @@ -267,6 +261,9 @@ namespace SafeExamBrowser.Client uiFactory, wirelessAdapter); + context.Activators.Add(new KeyboardActivator(ModuleLogger(nameof(KeyboardActivator)))); + context.Activators.Add(new TouchActivator(ModuleLogger(nameof(TouchActivator)))); + return operation; } diff --git a/SafeExamBrowser.Client/Operations/ShellOperation.cs b/SafeExamBrowser.Client/Operations/ShellOperation.cs index fbe90366..f71f7ccf 100644 --- a/SafeExamBrowser.Client/Operations/ShellOperation.cs +++ b/SafeExamBrowser.Client/Operations/ShellOperation.cs @@ -6,7 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System.Collections.Generic; using SafeExamBrowser.Client.Contracts; using SafeExamBrowser.Core.Contracts.OperationModel; using SafeExamBrowser.Core.Contracts.OperationModel.Events; @@ -26,7 +25,6 @@ namespace SafeExamBrowser.Client.Operations internal class ShellOperation : ClientOperation { private IActionCenter actionCenter; - private IEnumerable activators; private IAudio audio; private INotificationInfo aboutInfo; private INotificationController aboutController; @@ -47,7 +45,6 @@ namespace SafeExamBrowser.Client.Operations public ShellOperation( IActionCenter actionCenter, - IEnumerable activators, IAudio audio, INotificationInfo aboutInfo, INotificationController aboutController, @@ -67,7 +64,6 @@ namespace SafeExamBrowser.Client.Operations this.aboutInfo = aboutInfo; this.aboutController = aboutController; this.actionCenter = actionCenter; - this.activators = activators; this.audio = audio; this.keyboard = keyboard; this.logger = logger; @@ -113,7 +109,7 @@ namespace SafeExamBrowser.Client.Operations if (Context.Settings.ActionCenter.EnableActionCenter) { - foreach (var activator in activators) + foreach (var activator in Context.Activators) { actionCenter.Register(activator); activator.Start(); @@ -283,7 +279,7 @@ namespace SafeExamBrowser.Client.Operations if (Context.Settings.ActionCenter.EnableActionCenter) { - foreach (var activator in activators) + foreach (var activator in Context.Activators) { activator.Stop(); } diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.Browser.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.Browser.cs index db2f838c..c5c543bb 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.Browser.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.Browser.cs @@ -106,19 +106,57 @@ namespace SafeExamBrowser.Configuration.ConfigurationData } } - private void MapEnableContentRequestFilter(AppSettings settings, object value) + private void MapMainWindowMode(AppSettings settings, object value) { - if (value is bool process) + const int FULLSCREEN = 1; + + if (value is int mode) { - settings.Browser.Filter.ProcessContentRequests = process; + settings.Browser.MainWindow.FullScreenMode = mode == FULLSCREEN; } } - private void MapEnableMainRequestFilter(AppSettings settings, object value) + private void MapRequestFilter(IDictionary rawData, AppSettings settings) { - if (value is bool process) + var processMainRequests = rawData.TryGetValue(Keys.Browser.Filter.EnableMainRequestFilter, out var value) && value as bool? == true; + var processContentRequests = rawData.TryGetValue(Keys.Browser.UserAgentModeMobile, out value) && value as bool? == true; + + settings.Browser.Filter.ProcessMainRequests = processMainRequests; + settings.Browser.Filter.ProcessContentRequests = processMainRequests && processContentRequests; + } + + private void MapShowReloadWarning(AppSettings settings, object value) + { + if (value is bool show) { - settings.Browser.Filter.ProcessMainRequests = process; + settings.Browser.MainWindow.ShowReloadWarning = show; + } + } + + private void MapShowReloadWarningAdditionalWindow(AppSettings settings, object value) + { + if (value is bool show) + { + settings.Browser.AdditionalWindow.ShowReloadWarning = show; + } + } + + private void MapUserAgentMode(IDictionary rawData, AppSettings settings) + { + const int DEFAULT = 0; + + var useCustomForDesktop = rawData.TryGetValue(Keys.Browser.UserAgentModeDesktop, out var value) && value as int? != DEFAULT; + var useCustomForMobile = rawData.TryGetValue(Keys.Browser.UserAgentModeMobile, out value) && value as int? != DEFAULT; + + if (settings.UserInterfaceMode == UserInterfaceMode.Desktop && useCustomForDesktop) + { + settings.Browser.UseCustomUserAgent = true; + settings.Browser.CustomUserAgent = rawData[Keys.Browser.CustomUserAgentDesktop] as string; + } + else if (settings.UserInterfaceMode == UserInterfaceMode.Mobile && useCustomForMobile) + { + settings.Browser.UseCustomUserAgent = true; + settings.Browser.CustomUserAgent = rawData[Keys.Browser.CustomUserAgentMobile] as string; } } @@ -159,50 +197,5 @@ namespace SafeExamBrowser.Configuration.ConfigurationData } } } - - private void MapMainWindowMode(AppSettings settings, object value) - { - const int FULLSCREEN = 1; - - if (value is int mode) - { - settings.Browser.MainWindow.FullScreenMode = mode == FULLSCREEN; - } - } - - private void MapShowReloadWarning(AppSettings settings, object value) - { - if (value is bool show) - { - settings.Browser.MainWindow.ShowReloadWarning = show; - } - } - - private void MapShowReloadWarningAdditionalWindow(AppSettings settings, object value) - { - if (value is bool show) - { - settings.Browser.AdditionalWindow.ShowReloadWarning = show; - } - } - - private void MapUserAgentMode(IDictionary rawData, AppSettings settings) - { - const int DEFAULT = 0; - - var useCustomForDesktop = rawData.TryGetValue(Keys.Browser.UserAgentModeDesktop, out var value) && value as int? != DEFAULT; - var useCustomForMobile = rawData.TryGetValue(Keys.Browser.UserAgentModeMobile, out value) && value as int? != DEFAULT; - - if (settings.UserInterfaceMode == UserInterfaceMode.Desktop && useCustomForDesktop) - { - settings.Browser.UseCustomUserAgent = true; - settings.Browser.CustomUserAgent = rawData[Keys.Browser.CustomUserAgentDesktop] as string; - } - else if (settings.UserInterfaceMode == UserInterfaceMode.Mobile && useCustomForMobile) - { - settings.Browser.UseCustomUserAgent = true; - settings.Browser.CustomUserAgent = rawData[Keys.Browser.CustomUserAgentMobile] as string; - } - } } } diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.cs index 11bdb604..3b6884ee 100644 --- a/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.cs +++ b/SafeExamBrowser.Configuration/ConfigurationData/DataMapper.cs @@ -28,6 +28,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData } MapApplicationLogAccess(rawData, settings); + MapRequestFilter(rawData, settings); MapKioskMode(rawData, settings); MapUserAgentMode(rawData, settings); } @@ -95,12 +96,6 @@ namespace SafeExamBrowser.Configuration.ConfigurationData case Keys.Browser.AdditionalWindow.ShowReloadWarning: MapShowReloadWarningAdditionalWindow(settings, value); break; - case Keys.Browser.Filter.EnableContentRequestFilter: - MapEnableContentRequestFilter(settings, value); - break; - case Keys.Browser.Filter.EnableMainRequestFilter: - MapEnableMainRequestFilter(settings, value); - break; case Keys.Browser.Filter.UrlFilterRules: MapUrlFilterRules(settings, value); break; diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs index 71c83398..70482646 100644 --- a/SafeExamBrowser.I18n.Contracts/TextKey.cs +++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs @@ -21,6 +21,11 @@ namespace SafeExamBrowser.I18n.Contracts BrowserWindow_DeveloperConsoleMenuItem, BrowserWindow_ZoomMenuItem, Build, + LockScreen_AllowOption, + LockScreen_Message, + LockScreen_TerminateOption, + LockScreen_Title, + LockScreen_UnlockButton, LogWindow_Title, MessageBox_ApplicationAutoTerminationDataLossWarning, MessageBox_ApplicationAutoTerminationQuestion, @@ -44,6 +49,8 @@ namespace SafeExamBrowser.I18n.Contracts MessageBox_InvalidPasswordErrorTitle, MessageBox_InvalidQuitPassword, MessageBox_InvalidQuitPasswordTitle, + MessageBox_InvalidUnlockPassword, + MessageBox_InvalidUnlockPasswordTitle, MessageBox_NoButton, MessageBox_NotSupportedConfigurationResource, MessageBox_NotSupportedConfigurationResourceTitle, diff --git a/SafeExamBrowser.I18n/Text.xml b/SafeExamBrowser.I18n/Text.xml index 199c670f..31240a34 100644 --- a/SafeExamBrowser.I18n/Text.xml +++ b/SafeExamBrowser.I18n/Text.xml @@ -7,7 +7,7 @@ Back to previous page - Access to this page is not allowed according to the application configuration. + Access to this page is not allowed according to the current configuration. Page Blocked @@ -21,6 +21,21 @@ Build + + Temporarily allow the blacklisted applications. This applies only to the currently running instances and session! + + + The blacklisted applications listed below were started and could not be automatically terminated! In order to unlock SEB, please select one of the available options and enter the correct unlock password. + + + Terminate Safe Exam Browser. WARNING: There will be no possibility to save data or perform any further actions, the shutdown will be initiated immediately! + + + SEB LOCKED + + + Unlock + Application Log @@ -34,7 +49,7 @@ IMPORTANT: Any unsaved application data might be lost! - An unrecoverable error has occurred! Please consult the application log for more information. The application will now shut down... + An unrecoverable error has occurred! Please consult the log files for more information. SEB will now shut down... Application Error @@ -46,7 +61,7 @@ Automatic Termination Failed - Access to "%%URL%%" is not allowed according to the application configuration. + Access to "%%URL%%" is not allowed according to the current configuration. Page Blocked @@ -55,19 +70,19 @@ Cancel - The local client configuration has failed! Please consult the application log for more information. The application will now shut down... + The local client configuration has failed! Please consult the log files for more information. SEB will now shut down... Client Configuration Error - The client configuration has been saved and will be used when you start the application the next time. Do you want to quit for now? + The client configuration has been saved and will be used when you start SEB the next time. Do you want to quit for now? Configuration Successful - Failed to download the new application configuration. Please try again or contact technical support. + Failed to download the new configuration. Please try again or contact technical support. Download Error @@ -79,17 +94,23 @@ Configuration Error - You failed to enter the correct password within 5 attempts. The application will now terminate... + You failed to enter the correct password within 5 attempts. SEB will now terminate... Invalid Password - The application can only be terminated by entering the correct quit password. + SEB can only be terminated by entering the correct quit password. Invalid Quit Password + + SEB can only be unlocked by entering the correct password. + + + Invalid Unlock Password + No @@ -103,7 +124,7 @@ OK - Do you want to quit the application? + Do you want to quit SEB? Quit? @@ -115,7 +136,7 @@ Quit Error - You are not allowed to reconfigure the application. + You are not allowed to reconfigure SEB. Reconfiguration Denied @@ -133,37 +154,37 @@ Reload? - Failed to initialize the SEB service! The application will now terminate since the service is configured to be mandatory. + Failed to initialize the SEB service! SEB will now terminate since the service is configured to be mandatory. Service Unavailable - Failed to initialize the SEB service. The application will continue initialization since the service is configured to be optional. + Failed to initialize the SEB service. SEB will continue initialization since the service is configured to be optional. Service Unavailable - The application failed to start a new session! Please consult the application log for more information... + SEB failed to start a new session! Please consult the log files for more information... Session Start Error - An unexpected error occurred during the shutdown procedure! Please consult the application log for more information... + An unexpected error occurred during the shutdown procedure! Please consult the log files for more information... Shutdown Error - An unexpected error occurred during the startup procedure! Please consult the application log for more information... + An unexpected error occurred during the startup procedure! Please consult the log files for more information... Startup Error - An unexpected error occurred while trying to load configuration resource "%%URI%%"! Please consult the application log for more information... + An unexpected error occurred while trying to load configuration resource "%%URI%%"! Please consult the log files for more information... Configuration Error @@ -196,7 +217,7 @@ Initializing browser - Initializing application configuration + Initializing configuration Initializing kiosk mode @@ -283,19 +304,19 @@ Settings Password Required - Please enter the quit password in order to terminate the application: + Please enter the quit password in order to terminate SEB: Quit Password Required - Please enter the settings password for the selected application configuration: + Please enter the settings password for the selected configuration: Settings Password Required - The application is running. + SEB is running. Terminate Session diff --git a/SafeExamBrowser.Runtime.UnitTests/RuntimeControllerTests.cs b/SafeExamBrowser.Runtime.UnitTests/RuntimeControllerTests.cs index a16b7df6..2c541629 100644 --- a/SafeExamBrowser.Runtime.UnitTests/RuntimeControllerTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/RuntimeControllerTests.cs @@ -14,16 +14,17 @@ using SafeExamBrowser.Communication.Contracts.Events; using SafeExamBrowser.Communication.Contracts.Hosts; using SafeExamBrowser.Communication.Contracts.Proxies; using SafeExamBrowser.Configuration.Contracts; -using SafeExamBrowser.Settings; -using SafeExamBrowser.Settings.Service; using SafeExamBrowser.Core.Contracts.OperationModel; using SafeExamBrowser.Core.Contracts.OperationModel.Events; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Runtime.Operations.Events; +using SafeExamBrowser.Settings; +using SafeExamBrowser.Settings.Service; using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.Windows; +using SafeExamBrowser.UserInterface.Contracts.Windows.Data; using SafeExamBrowser.WindowsApi.Contracts; namespace SafeExamBrowser.Runtime.UnitTests @@ -219,14 +220,11 @@ namespace SafeExamBrowser.Runtime.UnitTests public void Operations_MustRequestPasswordViaDialogOnDefaultDesktop() { var args = new PasswordRequiredEventArgs(); - var password = "test1234"; var passwordDialog = new Mock(); - var result = new Mock(); + var result = new PasswordDialogResult { Password = "test1234", Success = true }; currentSettings.KioskMode = KioskMode.DisableExplorerShell; - passwordDialog.Setup(p => p.Show(It.IsAny())).Returns(result.Object); - result.SetupGet(r => r.Password).Returns(password); - result.SetupGet(r => r.Success).Returns(true); + passwordDialog.Setup(p => p.Show(It.IsAny())).Returns(result); uiFactory.Setup(u => u.CreatePasswordDialog(It.IsAny(), It.IsAny())).Returns(passwordDialog.Object); sut.TryStart(); @@ -237,7 +235,7 @@ namespace SafeExamBrowser.Runtime.UnitTests uiFactory.Verify(u => u.CreatePasswordDialog(It.IsAny(), It.IsAny()), Times.Once); Assert.AreEqual(true, args.Success); - Assert.AreEqual(password, args.Password); + Assert.AreEqual(result.Password, args.Password); } [TestMethod] diff --git a/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs b/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs index 9e7c6ded..ddee4229 100644 --- a/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs +++ b/SafeExamBrowser.UserInterface.Contracts/IUserInterfaceFactory.cs @@ -6,12 +6,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System.Collections.Generic; using SafeExamBrowser.Applications.Contracts; using SafeExamBrowser.Client.Contracts; using SafeExamBrowser.Configuration.Contracts; -using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.Logging.Contracts; +using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.SystemComponents.Contracts.Audio; using SafeExamBrowser.SystemComponents.Contracts.Keyboard; using SafeExamBrowser.SystemComponents.Contracts.PowerSupply; @@ -19,6 +20,7 @@ using SafeExamBrowser.SystemComponents.Contracts.WirelessNetwork; using SafeExamBrowser.UserInterface.Contracts.Browser; using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Contracts.Windows; +using SafeExamBrowser.UserInterface.Contracts.Windows.Data; namespace SafeExamBrowser.UserInterface.Contracts { @@ -52,6 +54,11 @@ namespace SafeExamBrowser.UserInterface.Contracts /// ISystemControl CreateKeyboardLayoutControl(IKeyboard keyboard, Location location); + /// + /// Creates a lock screen with the given message, title and options. + /// + ILockScreen CreateLockScreen(string message, string title, IEnumerable options); + /// /// Creates a new log window which runs on its own thread. /// diff --git a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj index 6b92b2f8..78e2165f 100644 --- a/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj +++ b/SafeExamBrowser.UserInterface.Contracts/SafeExamBrowser.UserInterface.Contracts.csproj @@ -75,9 +75,12 @@ + + + - + diff --git a/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenterActivator.cs b/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenterActivator.cs index 78e40ff2..b956d08d 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenterActivator.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Shell/IActionCenterActivator.cs @@ -30,6 +30,16 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell /// event ActivatorEventHandler Toggled; + /// + /// Temporarily stops processing all user input. + /// + void Pause(); + + /// + /// Resumes processing user input. + /// + void Resume(); + /// /// Starts monitoring user input events. /// diff --git a/SafeExamBrowser.UserInterface.Contracts/Windows/Data/LockScreenOption.cs b/SafeExamBrowser.UserInterface.Contracts/Windows/Data/LockScreenOption.cs new file mode 100644 index 00000000..70983b97 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/Windows/Data/LockScreenOption.cs @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 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/. + */ + +using System; + +namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data +{ + /// + /// Defines an option for the user to select on the . + /// + public class LockScreenOption + { + /// + /// The unique identifier for this option. + /// + public Guid Id { get; set; } + + /// + /// The text to be displayed to the user. + /// + public string Text { get; set; } + + public LockScreenOption() + { + Id = Guid.NewGuid(); + } + } +} diff --git a/SafeExamBrowser.UserInterface.Contracts/Windows/Data/LockScreenResult.cs b/SafeExamBrowser.UserInterface.Contracts/Windows/Data/LockScreenResult.cs new file mode 100644 index 00000000..9d54fa63 --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/Windows/Data/LockScreenResult.cs @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 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/. + */ + +using System; + +namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data +{ + /// + /// Defines the result of a lock screen interaction by the user. + /// + public class LockScreenResult + { + /// + /// The identifier of the option selected by the user, if available. + /// + public Guid? OptionId { get; set; } + + /// + /// The password entered by the user. + /// + public string Password { get; set; } + } +} diff --git a/SafeExamBrowser.UserInterface.Contracts/Windows/IPasswordDialogResult.cs b/SafeExamBrowser.UserInterface.Contracts/Windows/Data/PasswordDialogResult.cs similarity index 78% rename from SafeExamBrowser.UserInterface.Contracts/Windows/IPasswordDialogResult.cs rename to SafeExamBrowser.UserInterface.Contracts/Windows/Data/PasswordDialogResult.cs index 04840c84..a7861813 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Windows/IPasswordDialogResult.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Windows/Data/PasswordDialogResult.cs @@ -6,21 +6,21 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -namespace SafeExamBrowser.UserInterface.Contracts.Windows +namespace SafeExamBrowser.UserInterface.Contracts.Windows.Data { /// /// Defines the user interaction result of an . /// - public interface IPasswordDialogResult + public class PasswordDialogResult { /// /// The password entered by the user, or null if the interaction was unsuccessful. /// - string Password { get; } + public string Password { get; set; } /// /// Indicates whether the user confirmed the dialog or not. /// - bool Success { get; } + public bool Success { get; set; } } } diff --git a/SafeExamBrowser.UserInterface.Contracts/Windows/ILockScreen.cs b/SafeExamBrowser.UserInterface.Contracts/Windows/ILockScreen.cs new file mode 100644 index 00000000..e6571c2a --- /dev/null +++ b/SafeExamBrowser.UserInterface.Contracts/Windows/ILockScreen.cs @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019 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/. + */ + +using SafeExamBrowser.UserInterface.Contracts.Windows.Data; + +namespace SafeExamBrowser.UserInterface.Contracts.Windows +{ + /// + /// Defines the functionality of a lock screen which covers all active displays and prevents the user from continuing their work. + /// + public interface ILockScreen : IWindow + { + /// + /// Waits for the user to provide the required input to unlock the application. + /// + LockScreenResult WaitForResult(); + } +} diff --git a/SafeExamBrowser.UserInterface.Contracts/Windows/IPasswordDialog.cs b/SafeExamBrowser.UserInterface.Contracts/Windows/IPasswordDialog.cs index 95687cfc..ec9ad2c3 100644 --- a/SafeExamBrowser.UserInterface.Contracts/Windows/IPasswordDialog.cs +++ b/SafeExamBrowser.UserInterface.Contracts/Windows/IPasswordDialog.cs @@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using SafeExamBrowser.UserInterface.Contracts.Windows.Data; + namespace SafeExamBrowser.UserInterface.Contracts.Windows { /// @@ -16,6 +18,6 @@ namespace SafeExamBrowser.UserInterface.Contracts.Windows /// /// Shows the dialog as topmost window. If a parent window is specified, the dialog is rendered modally for the given parent. /// - IPasswordDialogResult Show(IWindow parent = null); + PasswordDialogResult Show(IWindow parent = null); } } diff --git a/SafeExamBrowser.UserInterface.Desktop/LockScreen.xaml b/SafeExamBrowser.UserInterface.Desktop/LockScreen.xaml new file mode 100644 index 00000000..7d6448ac --- /dev/null +++ b/SafeExamBrowser.UserInterface.Desktop/LockScreen.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + +