SEBWIN-313: Implemented lock screen mechanism for blacklisted processes.

This commit is contained in:
dbuechel 2019-10-11 15:46:15 +02:00
parent de6cb5e75c
commit b6dbe6451d
32 changed files with 837 additions and 177 deletions

View file

@ -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<IPasswordDialog>();
var result = new Mock<IPasswordDialogResult>();
var result = new PasswordDialogResult { Password = "blubb", Success = true };
dialog.Setup(d => d.Show(It.IsAny<IWindow>())).Returns(result.Object);
result.SetupGet(r => r.Password).Returns("blubb");
result.SetupGet(r => r.Success).Returns(true);
dialog.Setup(d => d.Show(It.IsAny<IWindow>())).Returns(result);
uiFactory.Setup(f => f.CreatePasswordDialog(It.IsAny<string>(), It.IsAny<string>())).Returns(dialog.Object);
sut.TryStart();
@ -182,8 +181,8 @@ namespace SafeExamBrowser.Client.UnitTests
runtimeProxy.Verify(p => p.SubmitPassword(
It.Is<Guid>(g => g == args.RequestId),
It.Is<bool>(b => b == result.Object.Success),
It.Is<string>(s => s == result.Object.Password)), Times.Once);
It.Is<bool>(b => b == result.Success),
It.Is<string>(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<IPasswordDialog>();
var dialogResult = new Mock<IPasswordDialogResult>();
var password = "blobb";
var dialogResult = new PasswordDialogResult { Password = "blobb", Success = true };
settings.QuitPasswordHash = "1234";
dialog.Setup(d => d.Show(It.IsAny<IWindow>())).Returns(dialogResult.Object);
dialogResult.SetupGet(r => r.Password).Returns(password);
dialogResult.SetupGet(r => r.Success).Returns(true);
hashAlgorithm.Setup(h => h.GenerateHashFor(It.Is<string>(s => s == password))).Returns(settings.QuitPasswordHash);
dialog.Setup(d => d.Show(It.IsAny<IWindow>())).Returns(dialogResult);
hashAlgorithm.Setup(h => h.GenerateHashFor(It.Is<string>(s => s == dialogResult.Password))).Returns(settings.QuitPasswordHash);
runtimeProxy.Setup(r => r.RequestShutdown()).Returns(new CommunicationResult(true));
uiFactory.Setup(u => u.CreatePasswordDialog(It.IsAny<TextKey>(), It.IsAny<TextKey>())).Returns(dialog.Object);
@ -486,11 +482,10 @@ namespace SafeExamBrowser.Client.UnitTests
{
var args = new System.ComponentModel.CancelEventArgs();
var dialog = new Mock<IPasswordDialog>();
var dialogResult = new Mock<IPasswordDialogResult>();
var dialogResult = new PasswordDialogResult { Success = false };
settings.QuitPasswordHash = "1234";
dialog.Setup(d => d.Show(It.IsAny<IWindow>())).Returns(dialogResult.Object);
dialogResult.SetupGet(r => r.Success).Returns(false);
dialog.Setup(d => d.Show(It.IsAny<IWindow>())).Returns(dialogResult);
runtimeProxy.Setup(r => r.RequestShutdown()).Returns(new CommunicationResult(true));
uiFactory.Setup(u => u.CreatePasswordDialog(It.IsAny<TextKey>(), It.IsAny<TextKey>())).Returns(dialog.Object);
@ -508,12 +503,10 @@ namespace SafeExamBrowser.Client.UnitTests
{
var args = new System.ComponentModel.CancelEventArgs();
var dialog = new Mock<IPasswordDialog>();
var dialogResult = new Mock<IPasswordDialogResult>();
var dialogResult = new PasswordDialogResult { Password = "blobb", Success = true };
settings.QuitPasswordHash = "1234";
dialog.Setup(d => d.Show(It.IsAny<IWindow>())).Returns(dialogResult.Object);
dialogResult.SetupGet(r => r.Password).Returns("blobb");
dialogResult.SetupGet(r => r.Success).Returns(true);
dialog.Setup(d => d.Show(It.IsAny<IWindow>())).Returns(dialogResult);
hashAlgorithm.Setup(h => h.GenerateHashFor(It.IsAny<string>())).Returns("9876");
uiFactory.Setup(u => u.CreatePasswordDialog(It.IsAny<TextKey>(), It.IsAny<TextKey>())).Returns(dialog.Object);

View file

@ -29,7 +29,6 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
public class ShellOperationTests
{
private Mock<IActionCenter> actionCenter;
private List<IActionCenterActivator> activators;
private Mock<IAudio> audio;
private ClientContext context;
private Mock<ILogger> logger;
@ -52,7 +51,6 @@ namespace SafeExamBrowser.Client.UnitTests.Operations
public void Initialize()
{
actionCenter = new Mock<IActionCenter>();
activators = new List<IActionCenterActivator>();
audio = new Mock<IAudio>();
context = new ClientContext();
logger = new Mock<ILogger>();
@ -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();

View file

@ -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
/// </summary>
internal class ClientContext
{
/// <summary>
/// All activators for the action center.
/// </summary>
internal IList<IActionCenterActivator> Activators { get; }
/// <summary>
/// The global application configuration.
/// </summary>
@ -43,5 +50,10 @@ namespace SafeExamBrowser.Client
/// The settings for the current session.
/// </summary>
internal AppSettings Settings { get; set; }
internal ClientContext()
{
Activators = new List<IActionCenterActivator>();
}
}
}

View file

@ -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<RunningApplication> 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;
}
}
}

View file

@ -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;
}

View file

@ -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<IActionCenterActivator> activators;
private IAudio audio;
private INotificationInfo aboutInfo;
private INotificationController aboutController;
@ -47,7 +45,6 @@ namespace SafeExamBrowser.Client.Operations
public ShellOperation(
IActionCenter actionCenter,
IEnumerable<IActionCenterActivator> 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();
}

View file

@ -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<string, object> 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<string, object> 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<string, object> 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;
}
}
}
}

View file

@ -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;

View file

@ -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,

View file

@ -7,7 +7,7 @@
Back to previous page
</Entry>
<Entry key="Browser_BlockedPageMessage">
Access to this page is not allowed according to the application configuration.
Access to this page is not allowed according to the current configuration.
</Entry>
<Entry key="Browser_BlockedPageTitle">
Page Blocked
@ -21,6 +21,21 @@
<Entry key="Build">
Build
</Entry>
<Entry key="LockScreen_AllowOption">
Temporarily allow the blacklisted applications. This applies only to the currently running instances and session!
</Entry>
<Entry key="LockScreen_Message">
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.
</Entry>
<Entry key="LockScreen_TerminateOption">
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_Title">
SEB LOCKED
</Entry>
<Entry key="LockScreen_UnlockButton">
Unlock
</Entry>
<Entry key="LogWindow_Title">
Application Log
</Entry>
@ -34,7 +49,7 @@
IMPORTANT: Any unsaved application data might be lost!
</Entry>
<Entry key="MessageBox_ApplicationError">
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...
</Entry>
<Entry key="MessageBox_ApplicationErrorTitle">
Application Error
@ -46,7 +61,7 @@
Automatic Termination Failed
</Entry>
<Entry key="MessageBox_BrowserNavigationBlocked">
Access to "%%URL%%" is not allowed according to the application configuration.
Access to "%%URL%%" is not allowed according to the current configuration.
</Entry>
<Entry key="MessageBox_BrowserNavigationBlockedTitle">
Page Blocked
@ -55,19 +70,19 @@
Cancel
</Entry>
<Entry key="MessageBox_ClientConfigurationError">
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...
</Entry>
<Entry key="MessageBox_ClientConfigurationErrorTitle">
Client Configuration Error
</Entry>
<Entry key="MessageBox_ClientConfigurationQuestion">
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?
</Entry>
<Entry key="MessageBox_ClientConfigurationQuestionTitle">
Configuration Successful
</Entry>
<Entry key="MessageBox_ConfigurationDownloadError">
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.
</Entry>
<Entry key="MessageBox_ConfigurationDownloadErrorTitle">
Download Error
@ -79,17 +94,23 @@
Configuration Error
</Entry>
<Entry key="MessageBox_InvalidPasswordError">
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...
</Entry>
<Entry key="MessageBox_InvalidPasswordErrorTitle">
Invalid Password
</Entry>
<Entry key="MessageBox_InvalidQuitPassword">
The application can only be terminated by entering the correct quit password.
SEB can only be terminated by entering the correct quit password.
</Entry>
<Entry key="MessageBox_InvalidQuitPasswordTitle">
Invalid Quit Password
</Entry>
<Entry key="MessageBox_InvalidUnlockPassword">
SEB can only be unlocked by entering the correct password.
</Entry>
<Entry key="MessageBox_InvalidUnlockPasswordTitle">
Invalid Unlock Password
</Entry>
<Entry key="MessageBox_NoButton">
No
</Entry>
@ -103,7 +124,7 @@
OK
</Entry>
<Entry key="MessageBox_Quit">
Do you want to quit the application?
Do you want to quit SEB?
</Entry>
<Entry key="MessageBox_QuitTitle">
Quit?
@ -115,7 +136,7 @@
Quit Error
</Entry>
<Entry key="MessageBox_ReconfigurationDenied">
You are not allowed to reconfigure the application.
You are not allowed to reconfigure SEB.
</Entry>
<Entry key="MessageBox_ReconfigurationDeniedTitle">
Reconfiguration Denied
@ -133,37 +154,37 @@
Reload?
</Entry>
<Entry key="MessageBox_ServiceUnavailableError">
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.
</Entry>
<Entry key="MessageBox_ServiceUnavailableErrorTitle">
Service Unavailable
</Entry>
<Entry key="MessageBox_ServiceUnavailableWarning">
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.
</Entry>
<Entry key="MessageBox_ServiceUnavailableWarningTitle">
Service Unavailable
</Entry>
<Entry key="MessageBox_SessionStartError">
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...
</Entry>
<Entry key="MessageBox_SessionStartErrorTitle">
Session Start Error
</Entry>
<Entry key="MessageBox_ShutdownError">
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...
</Entry>
<Entry key="MessageBox_ShutdownErrorTitle">
Shutdown Error
</Entry>
<Entry key="MessageBox_StartupError">
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...
</Entry>
<Entry key="MessageBox_StartupErrorTitle">
Startup Error
</Entry>
<Entry key="MessageBox_UnexpectedConfigurationError">
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...
</Entry>
<Entry key="MessageBox_UnexpectedConfigurationErrorTitle">
Configuration Error
@ -196,7 +217,7 @@
Initializing browser
</Entry>
<Entry key="OperationStatus_InitializeConfiguration">
Initializing application configuration
Initializing configuration
</Entry>
<Entry key="OperationStatus_InitializeKioskMode">
Initializing kiosk mode
@ -283,19 +304,19 @@
Settings Password Required
</Entry>
<Entry key="PasswordDialog_QuitPasswordRequired">
Please enter the quit password in order to terminate the application:
Please enter the quit password in order to terminate SEB:
</Entry>
<Entry key="PasswordDialog_QuitPasswordRequiredTitle">
Quit Password Required
</Entry>
<Entry key="PasswordDialog_SettingsPasswordRequired">
Please enter the settings password for the selected application configuration:
Please enter the settings password for the selected configuration:
</Entry>
<Entry key="PasswordDialog_SettingsPasswordRequiredTitle">
Settings Password Required
</Entry>
<Entry key="RuntimeWindow_ApplicationRunning">
The application is running.
SEB is running.
</Entry>
<Entry key="Shell_QuitButton">
Terminate Session

View file

@ -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<IPasswordDialog>();
var result = new Mock<IPasswordDialogResult>();
var result = new PasswordDialogResult { Password = "test1234", Success = true };
currentSettings.KioskMode = KioskMode.DisableExplorerShell;
passwordDialog.Setup(p => p.Show(It.IsAny<IWindow>())).Returns(result.Object);
result.SetupGet(r => r.Password).Returns(password);
result.SetupGet(r => r.Success).Returns(true);
passwordDialog.Setup(p => p.Show(It.IsAny<IWindow>())).Returns(result);
uiFactory.Setup(u => u.CreatePasswordDialog(It.IsAny<string>(), It.IsAny<string>())).Returns(passwordDialog.Object);
sut.TryStart();
@ -237,7 +235,7 @@ namespace SafeExamBrowser.Runtime.UnitTests
uiFactory.Verify(u => u.CreatePasswordDialog(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
Assert.AreEqual(true, args.Success);
Assert.AreEqual(password, args.Password);
Assert.AreEqual(result.Password, args.Password);
}
[TestMethod]

View file

@ -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
/// </summary>
ISystemControl CreateKeyboardLayoutControl(IKeyboard keyboard, Location location);
/// <summary>
/// Creates a lock screen with the given message, title and options.
/// </summary>
ILockScreen CreateLockScreen(string message, string title, IEnumerable<LockScreenOption> options);
/// <summary>
/// Creates a new log window which runs on its own thread.
/// </summary>

View file

@ -75,9 +75,12 @@
<Compile Include="Shell\ISystemControl.cs" />
<Compile Include="Shell\ITaskbar.cs" />
<Compile Include="Shell\Location.cs" />
<Compile Include="Windows\Data\LockScreenOption.cs" />
<Compile Include="Windows\Data\LockScreenResult.cs" />
<Compile Include="Windows\Events\WindowClosingEventHandler.cs" />
<Compile Include="Windows\ILockScreen.cs" />
<Compile Include="Windows\IPasswordDialog.cs" />
<Compile Include="Windows\IPasswordDialogResult.cs" />
<Compile Include="Windows\Data\PasswordDialogResult.cs" />
<Compile Include="Windows\IRuntimeWindow.cs" />
<Compile Include="Windows\ISplashScreen.cs" />
<Compile Include="Windows\IWindow.cs" />

View file

@ -30,6 +30,16 @@ namespace SafeExamBrowser.UserInterface.Contracts.Shell
/// </summary>
event ActivatorEventHandler Toggled;
/// <summary>
/// Temporarily stops processing all user input.
/// </summary>
void Pause();
/// <summary>
/// Resumes processing user input.
/// </summary>
void Resume();
/// <summary>
/// Starts monitoring user input events.
/// </summary>

View file

@ -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
{
/// <summary>
/// Defines an option for the user to select on the <see cref="ILockScreen"/>.
/// </summary>
public class LockScreenOption
{
/// <summary>
/// The unique identifier for this option.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// The text to be displayed to the user.
/// </summary>
public string Text { get; set; }
public LockScreenOption()
{
Id = Guid.NewGuid();
}
}
}

View file

@ -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
{
/// <summary>
/// Defines the result of a lock screen interaction by the user.
/// </summary>
public class LockScreenResult
{
/// <summary>
/// The identifier of the option selected by the user, if available.
/// </summary>
public Guid? OptionId { get; set; }
/// <summary>
/// The password entered by the user.
/// </summary>
public string Password { get; set; }
}
}

View file

@ -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
{
/// <summary>
/// Defines the user interaction result of an <see cref="IPasswordDialog"/>.
/// </summary>
public interface IPasswordDialogResult
public class PasswordDialogResult
{
/// <summary>
/// The password entered by the user, or <c>null</c> if the interaction was unsuccessful.
/// </summary>
string Password { get; }
public string Password { get; set; }
/// <summary>
/// Indicates whether the user confirmed the dialog or not.
/// </summary>
bool Success { get; }
public bool Success { get; set; }
}
}

View file

@ -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
{
/// <summary>
/// Defines the functionality of a lock screen which covers all active displays and prevents the user from continuing their work.
/// </summary>
public interface ILockScreen : IWindow
{
/// <summary>
/// Waits for the user to provide the required input to unlock the application.
/// </summary>
LockScreenResult WaitForResult();
}
}

View file

@ -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
{
/// <summary>
@ -16,6 +18,6 @@ namespace SafeExamBrowser.UserInterface.Contracts.Windows
/// <summary>
/// Shows the dialog as topmost window. If a parent window is specified, the dialog is rendered modally for the given parent.
/// </summary>
IPasswordDialogResult Show(IWindow parent = null);
PasswordDialogResult Show(IWindow parent = null);
}
}

View file

@ -0,0 +1,34 @@
<Window x:Class="SafeExamBrowser.UserInterface.Desktop.LockScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Desktop"
mc:Ignorable="d" d:DesignWidth="1500" ResizeMode="NoResize" Topmost="True" WindowState="Maximized" WindowStyle="None">
<Grid Background="Red">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Orientation="Vertical">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Margin="0,10">
<Image Height="75" Margin="0,0,20,0" Source="./Images/SafeExamBrowser.ico" />
<TextBlock Name="Heading" Foreground="White" FontSize="50" FontWeight="ExtraBold" TextAlignment="Center" Text="SEB LOCKED" />
</StackPanel>
<TextBlock Name="Message" Foreground="White" FontSize="16" FontWeight="DemiBold" Margin="0,10" Padding="5" TextWrapping="Wrap" />
<StackPanel Name="Options" Margin="0,10" Orientation="Vertical" />
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Margin="0,10">
<PasswordBox Name="Password" Margin="10,5" MinWidth="250" Padding="5" />
<Button Name="Button" Cursor="Hand" Margin="10,5" MinWidth="75" Padding="5" />
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</Window>

View file

@ -0,0 +1,168 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities;
using Screen = System.Windows.Forms.Screen;
namespace SafeExamBrowser.UserInterface.Desktop
{
public partial class LockScreen : Window, ILockScreen
{
private AutoResetEvent autoResetEvent;
private IList<Window> windows;
private IText text;
event WindowClosingEventHandler IWindow.Closing
{
add { throw new NotImplementedException(); }
remove { throw new NotImplementedException(); }
}
public LockScreen(string message, string title, IText text, IEnumerable<LockScreenOption> options)
{
this.autoResetEvent = new AutoResetEvent(false);
this.text = text;
InitializeComponent();
InitializeLockWindow(message, title, options);
}
public void BringToForeground()
{
Dispatcher.Invoke(Activate);
}
public new void Close()
{
Dispatcher.Invoke(CloseAll);
}
public new void Show()
{
Dispatcher.Invoke(ShowAll);
}
public LockScreenResult WaitForResult()
{
var result = new LockScreenResult();
autoResetEvent.WaitOne();
Dispatcher.Invoke(() =>
{
result.Password = Password.Password;
foreach (var child in Options.Children)
{
if (child is RadioButton option && option.IsChecked == true)
{
result.OptionId = option.Tag as Guid?;
}
}
});
return result;
}
private void InitializeLockWindow(string message, string title, IEnumerable<LockScreenOption> options)
{
windows = new List<Window>();
Button.Content = text.Get(TextKey.LockScreen_UnlockButton);
Button.Click += Button_Click;
Heading.Text = title;
Message.Text = message;
Password.KeyUp += Password_KeyUp;
foreach (var option in options)
{
Options.Children.Add(new RadioButton
{
Content = new TextBlock { Text = option.Text, TextWrapping = TextWrapping.Wrap },
Cursor = Cursors.Hand,
FontSize = Message.FontSize,
Foreground = Message.Foreground,
IsChecked = options.First() == option,
Margin = new Thickness(2.5),
Tag = option.Id,
VerticalContentAlignment = VerticalAlignment.Center
});
}
}
private void CloseAll()
{
foreach (var window in windows)
{
window.Close();
}
base.Close();
}
private void ShowAll()
{
foreach (var screen in Screen.AllScreens)
{
if (!screen.Primary)
{
ShowLockWindowOn(screen);
}
}
base.Show();
}
private void ShowLockWindowOn(Screen screen)
{
var window = new Window();
var position = this.TransformFromPhysical(screen.Bounds.X, screen.Bounds.Y);
var size = this.TransformFromPhysical(screen.Bounds.Width, screen.Bounds.Height);
window.Background = Brushes.Red;
window.Topmost = true;
window.Left = position.X;
window.Top = position.Y;
window.Width = size.X;
window.Height = size.Y;
window.ResizeMode = ResizeMode.NoResize;
window.WindowStyle = WindowStyle.None;
window.Show();
windows.Add(window);
// The window can only be maximized after it was shown on its screen, otherwise it is rendered on the primary screen!
window.WindowState = WindowState.Maximized;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
autoResetEvent.Set();
}
private void Password_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
autoResetEvent.Set();
}
}
}
}

View file

@ -10,6 +10,7 @@ using System.Windows;
using System.Windows.Input;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
namespace SafeExamBrowser.UserInterface.Desktop
@ -38,7 +39,7 @@ namespace SafeExamBrowser.UserInterface.Desktop
Dispatcher.Invoke(Activate);
}
public IPasswordDialogResult Show(IWindow parent = null)
public PasswordDialogResult Show(IWindow parent = null)
{
return Dispatcher.Invoke(() =>
{
@ -96,11 +97,5 @@ namespace SafeExamBrowser.UserInterface.Desktop
Close();
}
}
private class PasswordDialogResult : IPasswordDialogResult
{
public string Password { get; set; }
public bool Success { get; set; }
}
}
}

View file

@ -54,6 +54,7 @@
</Reference>
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
@ -140,6 +141,9 @@
<Compile Include="Controls\TaskbarWirelessNetworkControl.xaml.cs">
<DependentUpon>TaskbarWirelessNetworkControl.xaml</DependentUpon>
</Compile>
<Compile Include="LockScreen.xaml.cs">
<DependentUpon>LockScreen.xaml</DependentUpon>
</Compile>
<Compile Include="LogWindow.xaml.cs">
<DependentUpon>LogWindow.xaml</DependentUpon>
</Compile>
@ -306,6 +310,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Resource>
<Page Include="LockScreen.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Templates\ScrollViewers.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Windows.Media;
@ -13,9 +14,9 @@ using FontAwesome.WPF;
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;
@ -24,6 +25,7 @@ using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
using SafeExamBrowser.UserInterface.Desktop.Controls;
namespace SafeExamBrowser.UserInterface.Desktop
@ -85,6 +87,11 @@ namespace SafeExamBrowser.UserInterface.Desktop
}
}
public ILockScreen CreateLockScreen(string message, string title, IEnumerable<LockScreenOption> options)
{
return Application.Current.Dispatcher.Invoke(() => new LockScreen(message, title, text, options));
}
public IWindow CreateLogWindow(ILogger logger)
{
LogWindow logWindow = null;

View file

@ -0,0 +1,34 @@
<Window x:Class="SafeExamBrowser.UserInterface.Mobile.LockScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SafeExamBrowser.UserInterface.Mobile"
mc:Ignorable="d" d:DesignWidth="1500" FontSize="16" ResizeMode="NoResize" Topmost="True" WindowState="Maximized" WindowStyle="None">
<Grid Background="Red">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Orientation="Vertical">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Margin="0,10">
<Image Height="100" Margin="0,0,20,0" Source="./Images/SafeExamBrowser.ico" />
<TextBlock Name="Heading" Foreground="White" FontSize="75" FontWeight="ExtraBold" TextAlignment="Center" Text="SEB LOCKED" />
</StackPanel>
<TextBlock Name="Message" Foreground="White" FontSize="24" FontWeight="DemiBold" Margin="0,10" Padding="5" TextWrapping="Wrap" />
<StackPanel Name="Options" Margin="0,10" Orientation="Vertical" />
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal" Margin="0,10">
<PasswordBox Name="Password" Margin="10,5" MinWidth="500" Padding="12" />
<Button Name="Button" Cursor="Hand" Margin="10,5" MinWidth="125" Padding="12" />
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</Window>

View file

@ -0,0 +1,168 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
using SafeExamBrowser.UserInterface.Shared.Utilities;
using Screen = System.Windows.Forms.Screen;
namespace SafeExamBrowser.UserInterface.Mobile
{
public partial class LockScreen : Window, ILockScreen
{
private AutoResetEvent autoResetEvent;
private IList<Window> windows;
private IText text;
event WindowClosingEventHandler IWindow.Closing
{
add { throw new NotImplementedException(); }
remove { throw new NotImplementedException(); }
}
public LockScreen(string message, string title, IText text, IEnumerable<LockScreenOption> options)
{
this.autoResetEvent = new AutoResetEvent(false);
this.text = text;
InitializeComponent();
InitializeLockWindow(message, title, options);
}
public void BringToForeground()
{
Dispatcher.Invoke(Activate);
}
public new void Close()
{
Dispatcher.Invoke(CloseAll);
}
public new void Show()
{
Dispatcher.Invoke(ShowAll);
}
public LockScreenResult WaitForResult()
{
var result = new LockScreenResult();
autoResetEvent.WaitOne();
Dispatcher.Invoke(() =>
{
result.Password = Password.Password;
foreach (var child in Options.Children)
{
if (child is RadioButton option && option.IsChecked == true)
{
result.OptionId = option.Tag as Guid?;
}
}
});
return result;
}
private void InitializeLockWindow(string message, string title, IEnumerable<LockScreenOption> options)
{
windows = new List<Window>();
Button.Content = text.Get(TextKey.LockScreen_UnlockButton);
Button.Click += Button_Click;
Heading.Text = title;
Message.Text = message;
Password.KeyUp += Password_KeyUp;
foreach (var option in options)
{
Options.Children.Add(new RadioButton
{
Content = new TextBlock { Text = option.Text, TextWrapping = TextWrapping.Wrap },
Cursor = Cursors.Hand,
FontSize = Message.FontSize,
Foreground = Message.Foreground,
IsChecked = options.First() == option,
Margin = new Thickness(5),
Tag = option.Id,
VerticalContentAlignment = VerticalAlignment.Center
});
}
}
private void CloseAll()
{
foreach (var window in windows)
{
window.Close();
}
base.Close();
}
private void ShowAll()
{
foreach (var screen in Screen.AllScreens)
{
if (!screen.Primary)
{
ShowLockWindowOn(screen);
}
}
base.Show();
}
private void ShowLockWindowOn(Screen screen)
{
var window = new Window();
var position = this.TransformFromPhysical(screen.Bounds.X, screen.Bounds.Y);
var size = this.TransformFromPhysical(screen.Bounds.Width, screen.Bounds.Height);
window.Background = Brushes.Red;
window.Topmost = true;
window.Left = position.X;
window.Top = position.Y;
window.Width = size.X;
window.Height = size.Y;
window.ResizeMode = ResizeMode.NoResize;
window.WindowStyle = WindowStyle.None;
window.Show();
windows.Add(window);
// The window can only be maximized after it was shown on its screen, otherwise it is rendered on the primary screen!
window.WindowState = WindowState.Maximized;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
autoResetEvent.Set();
}
private void Password_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
autoResetEvent.Set();
}
}
}
}

View file

@ -10,6 +10,7 @@ using System.Windows;
using System.Windows.Input;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
using SafeExamBrowser.UserInterface.Contracts.Windows.Events;
namespace SafeExamBrowser.UserInterface.Mobile
@ -38,7 +39,7 @@ namespace SafeExamBrowser.UserInterface.Mobile
Dispatcher.Invoke(Activate);
}
public IPasswordDialogResult Show(IWindow parent = null)
public PasswordDialogResult Show(IWindow parent = null)
{
return Dispatcher.Invoke(() =>
{
@ -115,11 +116,5 @@ namespace SafeExamBrowser.UserInterface.Mobile
Dispatcher.InvokeAsync(InitializeBounds);
}
}
private class PasswordDialogResult : IPasswordDialogResult
{
public string Password { get; set; }
public bool Success { get; set; }
}
}
}

View file

@ -142,6 +142,9 @@
<Compile Include="Controls\TaskbarWirelessNetworkControl.xaml.cs">
<DependentUpon>TaskbarWirelessNetworkControl.xaml</DependentUpon>
</Compile>
<Compile Include="LockScreen.xaml.cs">
<DependentUpon>LockScreen.xaml</DependentUpon>
</Compile>
<Compile Include="LogWindow.xaml.cs">
<DependentUpon>LogWindow.xaml</DependentUpon>
</Compile>
@ -425,6 +428,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="LockScreen.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="LogWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Windows.Media;
@ -13,9 +14,9 @@ using FontAwesome.WPF;
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;
@ -24,6 +25,7 @@ using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Browser;
using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Contracts.Windows;
using SafeExamBrowser.UserInterface.Contracts.Windows.Data;
using SafeExamBrowser.UserInterface.Mobile.Controls;
namespace SafeExamBrowser.UserInterface.Mobile
@ -85,6 +87,11 @@ namespace SafeExamBrowser.UserInterface.Mobile
}
}
public ILockScreen CreateLockScreen(string message, string title, IEnumerable<LockScreenOption> options)
{
return Application.Current.Dispatcher.Invoke(() => new LockScreen(message, title, text, options));
}
public IWindow CreateLogWindow(ILogger logger)
{
LogWindow logWindow = null;

View file

@ -12,31 +12,52 @@ using System.Windows.Media;
namespace SafeExamBrowser.UserInterface.Shared.Utilities
{
/// <summary>
/// WPF works with device-independent pixels. These methods are required to transform
/// such values to their absolute, device-specific pixel values and vice versa.
///
/// Source: https://stackoverflow.com/questions/3286175/how-do-i-convert-a-wpf-size-to-physical-pixels
/// </summary>
public static class VisualExtensions
{
/// <summary>
/// WPF works with device-independent pixels. This method is required to
/// transform such values to their absolute, device-specific pixel value.
/// Source: https://stackoverflow.com/questions/3286175/how-do-i-convert-a-wpf-size-to-physical-pixels
/// </summary>
public static Vector TransformToPhysical(this Visual visual, double x, double y)
{
Matrix transformToDevice;
var matrix = default(Matrix);
var source = PresentationSource.FromVisual(visual);
if (source != null)
{
transformToDevice = source.CompositionTarget.TransformToDevice;
matrix = source.CompositionTarget.TransformToDevice;
}
else
{
using (var newSource = new HwndSource(new HwndSourceParameters()))
{
transformToDevice = newSource.CompositionTarget.TransformToDevice;
matrix = newSource.CompositionTarget.TransformToDevice;
}
}
return transformToDevice.Transform(new Vector(x, y));
return matrix.Transform(new Vector(x, y));
}
public static Vector TransformFromPhysical(this Visual visual, double x, double y)
{
var matrix = default(Matrix);
var source = PresentationSource.FromVisual(visual);
if (source != null)
{
matrix = source.CompositionTarget.TransformFromDevice;
}
else
{
using (var newSource = new HwndSource(new HwndSourceParameters()))
{
matrix = newSource.CompositionTarget.TransformFromDevice;
}
}
return matrix.Transform(new Vector(x, y));
}
}
}

View file

@ -20,7 +20,7 @@ namespace SafeExamBrowser.WindowsApi
{
public class KeyboardActivator : IActionCenterActivator
{
private bool A, LeftWindows;
private bool A, LeftWindows, paused;
private IntPtr handle;
private HookDelegate hookDelegate;
private ILogger logger;
@ -34,6 +34,18 @@ namespace SafeExamBrowser.WindowsApi
this.logger = logger;
}
public void Pause()
{
paused = true;
}
public void Resume()
{
A = false;
LeftWindows = false;
paused = false;
}
public void Start()
{
var hookReadyEvent = new AutoResetEvent(false);
@ -72,7 +84,7 @@ namespace SafeExamBrowser.WindowsApi
private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0)
if (nCode >= 0 && !paused)
{
var changed = false;
var keyData = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));

View file

@ -23,7 +23,7 @@ namespace SafeExamBrowser.WindowsApi
{
private HookDelegate hookDelegate;
private IntPtr handle;
private bool isDown;
private bool isDown, paused;
private ILogger logger;
public event ActivatorEventHandler Activated;
@ -35,6 +35,17 @@ namespace SafeExamBrowser.WindowsApi
this.logger = logger;
}
public void Pause()
{
paused = true;
}
public void Resume()
{
isDown = false;
paused = false;
}
public void Start()
{
var hookReadyEvent = new AutoResetEvent(false);
@ -73,7 +84,7 @@ namespace SafeExamBrowser.WindowsApi
private IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && !Ignore(wParam.ToInt32()))
if (nCode >= 0 && !paused && !Ignore(wParam.ToInt32()))
{
var message = wParam.ToInt32();
var mouseData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));