Update Safe Exam Browser Patch to 3.10.0.826
This commit is contained in:
@@ -12,14 +12,13 @@ using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Bootstrap
|
||||
{
|
||||
internal class ApplicationIntegrityOperation : IOperation
|
||||
{
|
||||
private readonly IIntegrityModule module;
|
||||
private readonly ILogger logger;
|
||||
|
||||
public event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
public event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public ApplicationIntegrityOperation(IIntegrityModule module, ILogger logger)
|
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.Core.OperationModel;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Bootstrap
|
||||
{
|
||||
internal class BootstrapOperationSequence : OperationSequence<IOperation>
|
||||
{
|
||||
private readonly ISplashScreen splashScreen;
|
||||
|
||||
public BootstrapOperationSequence(ILogger logger, IEnumerable<IOperation> operations, ISplashScreen splashScreen) : base(logger, operations)
|
||||
{
|
||||
this.splashScreen = splashScreen;
|
||||
|
||||
ProgressChanged += Operations_ProgressChanged;
|
||||
StatusChanged += Operations_StatusChanged;
|
||||
}
|
||||
|
||||
private void Operations_ProgressChanged(ProgressChangedEventArgs args)
|
||||
{
|
||||
if (args.CurrentValue.HasValue)
|
||||
{
|
||||
splashScreen.SetValue(args.CurrentValue.Value);
|
||||
}
|
||||
|
||||
if (args.IsIndeterminate == true)
|
||||
{
|
||||
splashScreen.SetIndeterminate();
|
||||
}
|
||||
|
||||
if (args.MaxValue.HasValue)
|
||||
{
|
||||
splashScreen.SetMaxValue(args.MaxValue.Value);
|
||||
}
|
||||
|
||||
if (args.Progress == true)
|
||||
{
|
||||
splashScreen.Progress();
|
||||
}
|
||||
|
||||
if (args.Regress == true)
|
||||
{
|
||||
splashScreen.Regress();
|
||||
}
|
||||
}
|
||||
|
||||
private void Operations_StatusChanged(TextKey status)
|
||||
{
|
||||
splashScreen.UpdateStatus(status, true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Communication.Contracts.Data;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.Runtime.Operations.Events;
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
internal abstract class ConfigurationBaseOperation : SessionOperation
|
||||
{
|
||||
protected string[] commandLineArgs;
|
||||
protected IConfigurationRepository configuration;
|
||||
|
||||
protected string AppDataFilePath => Context.Next.AppConfig.AppDataFilePath;
|
||||
protected string ProgramDataFilePath => Context.Next.AppConfig.ProgramDataFilePath;
|
||||
|
||||
public ConfigurationBaseOperation(string[] commandLineArgs, IConfigurationRepository configuration, SessionContext context) : base(context)
|
||||
{
|
||||
this.commandLineArgs = commandLineArgs;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
protected abstract void InvokeActionRequired(ActionRequiredEventArgs args);
|
||||
|
||||
protected LoadStatus? TryLoadSettings(Uri uri, UriSource source, out PasswordParameters passwordParams, out AppSettings settings, string currentPassword = default)
|
||||
{
|
||||
passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true };
|
||||
|
||||
var status = configuration.TryLoadSettings(uri, out settings, passwordParams);
|
||||
|
||||
if (status == LoadStatus.PasswordNeeded && currentPassword != default)
|
||||
{
|
||||
passwordParams.Password = currentPassword;
|
||||
passwordParams.IsHash = true;
|
||||
|
||||
status = configuration.TryLoadSettings(uri, out settings, passwordParams);
|
||||
}
|
||||
|
||||
for (var attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
|
||||
{
|
||||
var isLocalConfig = source == UriSource.AppData || source == UriSource.ProgramData;
|
||||
var purpose = isLocalConfig ? PasswordRequestPurpose.LocalSettings : PasswordRequestPurpose.Settings;
|
||||
var success = TryGetPassword(purpose, out var password);
|
||||
|
||||
if (success)
|
||||
{
|
||||
passwordParams.Password = password;
|
||||
passwordParams.IsHash = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
status = configuration.TryLoadSettings(uri, out settings, passwordParams);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
protected bool TryGetPassword(PasswordRequestPurpose purpose, out string password)
|
||||
{
|
||||
var args = new PasswordRequiredEventArgs { Purpose = purpose };
|
||||
|
||||
InvokeActionRequired(args);
|
||||
password = args.Password;
|
||||
|
||||
return args.Success;
|
||||
}
|
||||
|
||||
protected enum UriSource
|
||||
{
|
||||
Undefined,
|
||||
AppData,
|
||||
CommandLine,
|
||||
ProgramData,
|
||||
Reconfiguration,
|
||||
Server
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* 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;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Display;
|
||||
using SafeExamBrowser.Runtime.Operations.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
internal class DisplayMonitorOperation : SessionOperation
|
||||
{
|
||||
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, IText text) : base(context)
|
||||
{
|
||||
this.displayMonitor = displayMonitor;
|
||||
this.logger = logger;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
return CheckDisplayConfiguration();
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
return CheckDisplayConfiguration();
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private OperationResult CheckDisplayConfiguration()
|
||||
{
|
||||
logger.Info("Validating display configuration...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_ValidateDisplayConfiguration);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class ClientConfigurationErrorMessageArgs : MessageEventArgs
|
||||
{
|
||||
internal ClientConfigurationErrorMessageArgs()
|
||||
{
|
||||
Icon = MessageBoxIcon.Error;
|
||||
Message = TextKey.MessageBox_ClientConfigurationError;
|
||||
Title = TextKey.MessageBox_ClientConfigurationErrorTitle;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class ConfigurationCompletedEventArgs : ActionRequiredEventArgs
|
||||
{
|
||||
public bool AbortStartup { get; set; }
|
||||
}
|
||||
}
|
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class ExamSelectionEventArgs : ActionRequiredEventArgs
|
||||
{
|
||||
internal IEnumerable<Exam> Exams { get; set; }
|
||||
internal Exam SelectedExam { get; set; }
|
||||
internal bool Success { get; set; }
|
||||
|
||||
internal ExamSelectionEventArgs(IEnumerable<Exam> exams)
|
||||
{
|
||||
Exams = exams;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class InvalidDataMessageArgs : MessageEventArgs
|
||||
{
|
||||
internal InvalidDataMessageArgs(string uri)
|
||||
{
|
||||
Icon = MessageBoxIcon.Error;
|
||||
Message = TextKey.MessageBox_InvalidConfigurationData;
|
||||
MessagePlaceholders["%%URI%%"] = uri;
|
||||
Title = TextKey.MessageBox_InvalidConfigurationDataTitle;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class InvalidPasswordMessageArgs : MessageEventArgs
|
||||
{
|
||||
internal InvalidPasswordMessageArgs()
|
||||
{
|
||||
Icon = MessageBoxIcon.Error;
|
||||
Message = TextKey.MessageBox_InvalidPasswordError;
|
||||
Title = TextKey.MessageBox_InvalidPasswordErrorTitle;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class MessageEventArgs : ActionRequiredEventArgs
|
||||
{
|
||||
internal MessageBoxAction Action { get; set; }
|
||||
internal MessageBoxIcon Icon { get; set; }
|
||||
internal TextKey Message { get; set; }
|
||||
internal MessageBoxResult Result { get; set; }
|
||||
internal TextKey Title { get; set; }
|
||||
internal Dictionary<string, string> MessagePlaceholders { get; private set; }
|
||||
internal Dictionary<string, string> TitlePlaceholders { get; private set; }
|
||||
|
||||
public MessageEventArgs()
|
||||
{
|
||||
Action = MessageBoxAction.Ok;
|
||||
MessagePlaceholders = new Dictionary<string, string>();
|
||||
TitlePlaceholders = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class NotSupportedMessageArgs : MessageEventArgs
|
||||
{
|
||||
internal NotSupportedMessageArgs(string uri)
|
||||
{
|
||||
Icon = MessageBoxIcon.Error;
|
||||
Message = TextKey.MessageBox_NotSupportedConfigurationResource;
|
||||
MessagePlaceholders["%%URI%%"] = uri;
|
||||
Title = TextKey.MessageBox_NotSupportedConfigurationResourceTitle;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Communication.Contracts.Data;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class PasswordRequiredEventArgs : ActionRequiredEventArgs
|
||||
{
|
||||
public string Password { get; set; }
|
||||
public PasswordRequestPurpose Purpose { get; set; }
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class ServerFailureEventArgs : ActionRequiredEventArgs
|
||||
{
|
||||
public bool Abort { get; set; }
|
||||
public bool Fallback { get; set; }
|
||||
public string Message { get; set; }
|
||||
public bool Retry { get; set; }
|
||||
public bool ShowFallback { get; }
|
||||
|
||||
public ServerFailureEventArgs(string message, bool showFallback)
|
||||
{
|
||||
Message = message;
|
||||
ShowFallback = showFallback;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class UnexpectedErrorMessageArgs : MessageEventArgs
|
||||
{
|
||||
internal UnexpectedErrorMessageArgs(string uri)
|
||||
{
|
||||
Icon = MessageBoxIcon.Error;
|
||||
Message = TextKey.MessageBox_UnexpectedConfigurationError;
|
||||
MessagePlaceholders["%%URI%%"] = uri;
|
||||
Title = TextKey.MessageBox_UnexpectedConfigurationErrorTitle;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Events
|
||||
{
|
||||
internal class VersionRestrictionMessageArgs : MessageEventArgs
|
||||
{
|
||||
internal VersionRestrictionMessageArgs(string version, string requiredVersions)
|
||||
{
|
||||
Icon = MessageBoxIcon.Error;
|
||||
Message = TextKey.MessageBox_VersionRestrictionError;
|
||||
MessagePlaceholders["%%_VERSION_%%"] = version;
|
||||
MessagePlaceholders["%%_REQUIRED_VERSIONS_%%"] = requiredVersions;
|
||||
Title = TextKey.MessageBox_VersionRestrictionErrorTitle;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts;
|
||||
using SafeExamBrowser.Runtime.Operations.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
internal class RemoteSessionOperation : SessionOperation
|
||||
{
|
||||
private readonly IRemoteSessionDetector detector;
|
||||
private readonly ILogger logger;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired;
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public RemoteSessionOperation(IRemoteSessionDetector detector, ILogger logger, SessionContext context) : base(context)
|
||||
{
|
||||
this.detector = detector;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
return ValidatePolicy();
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
return ValidatePolicy();
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private OperationResult ValidatePolicy()
|
||||
{
|
||||
logger.Info($"Validating remote session policy...");
|
||||
//StatusChanged?.Invoke(TextKey.OperationStatus_ValidateRemoteSessionPolicy);
|
||||
|
||||
//if (Context.Next.Settings.Service.DisableRemoteConnections && detector.IsRemoteSession())
|
||||
//{
|
||||
// var args = new MessageEventArgs
|
||||
// {
|
||||
// Icon = MessageBoxIcon.Error,
|
||||
// Message = TextKey.MessageBox_RemoteSessionNotAllowed,
|
||||
// Title = TextKey.MessageBox_RemoteSessionNotAllowedTitle
|
||||
// };
|
||||
|
||||
// logger.Error("Detected remote session while SEB is not allowed to be run in a remote session! Aborting...");
|
||||
// ActionRequired?.Invoke(args);
|
||||
|
||||
// return OperationResult.Aborted;
|
||||
//}
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,15 +16,13 @@ using SafeExamBrowser.Communication.Contracts.Proxies;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class ClientOperation : SessionOperation
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly IProcessFactory processFactory;
|
||||
private readonly IProxyFactory proxyFactory;
|
||||
private readonly IRuntimeHost runtimeHost;
|
||||
@@ -42,18 +40,15 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
set { Context.ClientProxy = value; }
|
||||
}
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public ClientOperation(
|
||||
ILogger logger,
|
||||
Dependencies dependencies,
|
||||
IProcessFactory processFactory,
|
||||
IProxyFactory proxyFactory,
|
||||
IRuntimeHost runtimeHost,
|
||||
SessionContext sessionContext,
|
||||
int timeout_ms) : base(sessionContext)
|
||||
int timeout_ms) : base(dependencies)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.processFactory = processFactory;
|
||||
this.proxyFactory = proxyFactory;
|
||||
this.runtimeHost = runtimeHost;
|
||||
@@ -68,11 +63,11 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Successfully started new client instance.");
|
||||
Logger.Info("Successfully started new client instance.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to start new client instance! Aborting procedure...");
|
||||
Logger.Error("Failed to start new client instance! Aborting procedure...");
|
||||
}
|
||||
|
||||
return success ? OperationResult.Success : OperationResult.Failed;
|
||||
@@ -112,18 +107,18 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
var clientTerminated = false;
|
||||
var clientTerminatedEventHandler = new ProcessTerminatedEventHandler(_ => { clientTerminated = true; clientReadyEvent.Set(); });
|
||||
|
||||
logger.Info("Starting new client process...");
|
||||
Logger.Info("Starting new client process...");
|
||||
runtimeHost.AllowConnection = true;
|
||||
runtimeHost.AuthenticationToken = Context.Next.ClientAuthenticationToken;
|
||||
runtimeHost.ClientReady += clientReadyEventHandler;
|
||||
ClientProcess = processFactory.StartNew(executablePath, logFilePath, logLevel, runtimeHostUri, authenticationToken, uiMode);
|
||||
ClientProcess.Terminated += clientTerminatedEventHandler;
|
||||
|
||||
logger.Info("Waiting for client to complete initialization...");
|
||||
Logger.Info("Waiting for client to complete initialization...");
|
||||
clientReady = clientReadyEvent.WaitOne();
|
||||
|
||||
runtimeHost.AllowConnection = false;
|
||||
runtimeHost.AuthenticationToken = default(Guid?);
|
||||
runtimeHost.AuthenticationToken = default;
|
||||
runtimeHost.ClientReady -= clientReadyEventHandler;
|
||||
ClientProcess.Terminated -= clientTerminatedEventHandler;
|
||||
|
||||
@@ -134,12 +129,12 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (!clientReady)
|
||||
{
|
||||
logger.Error($"Failed to start client!");
|
||||
Logger.Error($"Failed to start client!");
|
||||
}
|
||||
|
||||
if (clientTerminated)
|
||||
{
|
||||
logger.Error("Client instance terminated unexpectedly during initialization!");
|
||||
Logger.Error("Client instance terminated unexpectedly during initialization!");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -149,12 +144,12 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
var success = false;
|
||||
|
||||
logger.Info("Client has been successfully started and initialized. Creating communication proxy for client host...");
|
||||
Logger.Info("Client has been successfully started and initialized. Creating communication proxy for client host...");
|
||||
ClientProxy = proxyFactory.CreateClientProxy(Context.Next.AppConfig.ClientAddress, Interlocutor.Runtime);
|
||||
|
||||
if (ClientProxy.Connect(Context.Next.ClientAuthenticationToken))
|
||||
{
|
||||
logger.Info("Connection with client has been established. Requesting authentication...");
|
||||
Logger.Info("Connection with client has been established. Requesting authentication...");
|
||||
|
||||
var communication = ClientProxy.RequestAuthentication();
|
||||
var response = communication.Value;
|
||||
@@ -163,16 +158,16 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Authentication of client has been successful, client is ready to operate.");
|
||||
Logger.Info("Authentication of client has been successful, client is ready to operate.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to verify client integrity!");
|
||||
Logger.Error("Failed to verify client integrity!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to connect to client!");
|
||||
Logger.Error("Failed to connect to client!");
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -195,26 +190,26 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
runtimeHost.ClientDisconnected += disconnectedEventHandler;
|
||||
ClientProcess.Terminated += terminatedEventHandler;
|
||||
|
||||
logger.Info("Instructing client to initiate shutdown procedure.");
|
||||
Logger.Info("Instructing client to initiate shutdown procedure.");
|
||||
ClientProxy.InitiateShutdown();
|
||||
|
||||
logger.Info("Disconnecting from client communication host.");
|
||||
Logger.Info("Disconnecting from client communication host.");
|
||||
ClientProxy.Disconnect();
|
||||
|
||||
logger.Info("Waiting for client to disconnect from runtime communication host...");
|
||||
Logger.Info("Waiting for client to disconnect from runtime communication host...");
|
||||
disconnected = disconnectedEvent.WaitOne(timeout_ms / 2);
|
||||
|
||||
if (!disconnected)
|
||||
{
|
||||
logger.Error($"Client failed to disconnect within {timeout_ms / 2 / 1000} seconds!");
|
||||
Logger.Error($"Client failed to disconnect within {timeout_ms / 2 / 1000} seconds!");
|
||||
}
|
||||
|
||||
logger.Info("Waiting for client process to terminate...");
|
||||
Logger.Info("Waiting for client process to terminate...");
|
||||
terminated = terminatedEvent.WaitOne(timeout_ms / 2);
|
||||
|
||||
if (!terminated)
|
||||
{
|
||||
logger.Error($"Client failed to terminate within {timeout_ms / 2 / 1000} seconds!");
|
||||
Logger.Error($"Client failed to terminate within {timeout_ms / 2 / 1000} seconds!");
|
||||
}
|
||||
|
||||
runtimeHost.ClientDisconnected -= disconnectedEventHandler;
|
||||
@@ -223,12 +218,12 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (disconnected && terminated)
|
||||
{
|
||||
logger.Info("Client has been successfully terminated.");
|
||||
Logger.Info("Client has been successfully terminated.");
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Attempting to kill client process since graceful termination failed!");
|
||||
Logger.Warn("Attempting to kill client process since graceful termination failed!");
|
||||
success = TryKillClient();
|
||||
}
|
||||
|
||||
@@ -247,7 +242,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
for (var attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)
|
||||
{
|
||||
logger.Info($"Attempt {attempt}/{MAX_ATTEMPTS} to kill client process with ID = {ClientProcess.Id}.");
|
||||
Logger.Info($"Attempt {attempt}/{MAX_ATTEMPTS} to kill client process with ID = {ClientProcess.Id}.");
|
||||
|
||||
if (ClientProcess.TryKill(500))
|
||||
{
|
||||
@@ -257,11 +252,11 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (ClientProcess.HasTerminated)
|
||||
{
|
||||
logger.Info("Client process has terminated.");
|
||||
Logger.Info("Client process has terminated.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to kill client process within {MAX_ATTEMPTS} attempts!");
|
||||
Logger.Error($"Failed to kill client process within {MAX_ATTEMPTS} attempts!");
|
||||
}
|
||||
|
||||
return ClientProcess.HasTerminated;
|
@@ -9,20 +9,18 @@
|
||||
using SafeExamBrowser.Communication.Contracts.Hosts;
|
||||
using SafeExamBrowser.Communication.Contracts.Proxies;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class ClientTerminationOperation : ClientOperation
|
||||
{
|
||||
public ClientTerminationOperation(
|
||||
ILogger logger,
|
||||
Dependencies dependencies,
|
||||
IProcessFactory processFactory,
|
||||
IProxyFactory proxyFactory,
|
||||
IRuntimeHost runtimeHost,
|
||||
SessionContext sessionContext,
|
||||
int timeout_ms) : base(logger, processFactory, proxyFactory, runtimeHost, sessionContext, timeout_ms)
|
||||
int timeout_ms) : base(dependencies, processFactory, proxyFactory, runtimeHost, timeout_ms)
|
||||
{
|
||||
}
|
||||
|
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Communication.Contracts.Data;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Runtime.Communication;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal abstract class ConfigurationBaseOperation : SessionOperation
|
||||
{
|
||||
protected readonly IConfigurationRepository repository;
|
||||
protected readonly IUserInterfaceFactory uiFactory;
|
||||
|
||||
protected string AppDataFilePath => Context.Next.AppConfig.AppDataFilePath;
|
||||
protected string ProgramDataFilePath => Context.Next.AppConfig.ProgramDataFilePath;
|
||||
|
||||
public ConfigurationBaseOperation(
|
||||
Dependencies dependencies,
|
||||
IConfigurationRepository repository,
|
||||
IUserInterfaceFactory uiFactory) : base(dependencies)
|
||||
{
|
||||
this.repository = repository;
|
||||
this.uiFactory = uiFactory;
|
||||
}
|
||||
|
||||
protected LoadStatus? TryLoadSettings(Uri uri, UriSource source, out PasswordParameters passwordParams, out AppSettings settings, string currentPassword = default)
|
||||
{
|
||||
passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true };
|
||||
|
||||
var status = repository.TryLoadSettings(uri, out settings, passwordParams);
|
||||
|
||||
if (status == LoadStatus.PasswordNeeded && currentPassword != default)
|
||||
{
|
||||
passwordParams.Password = currentPassword;
|
||||
passwordParams.IsHash = true;
|
||||
|
||||
status = repository.TryLoadSettings(uri, out settings, passwordParams);
|
||||
}
|
||||
|
||||
for (var attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
|
||||
{
|
||||
var isLocalConfig = source == UriSource.AppData || source == UriSource.ProgramData;
|
||||
var purpose = isLocalConfig ? PasswordRequestPurpose.LocalSettings : PasswordRequestPurpose.Settings;
|
||||
var success = TryGetPassword(purpose, out var password);
|
||||
|
||||
if (success)
|
||||
{
|
||||
passwordParams.Password = password;
|
||||
passwordParams.IsHash = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
status = repository.TryLoadSettings(uri, out settings, passwordParams);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
protected bool TryGetPassword(PasswordRequestPurpose purpose, out string password)
|
||||
{
|
||||
var success = false;
|
||||
|
||||
if (ClientBridge.IsRequired())
|
||||
{
|
||||
ClientBridge.TryGetPassword(purpose, out password);
|
||||
}
|
||||
else
|
||||
{
|
||||
var (message, title) = GetTextKeysFor(purpose);
|
||||
var dialog = uiFactory.CreatePasswordDialog(Text.Get(message), Text.Get(title));
|
||||
var result = dialog.Show(RuntimeWindow);
|
||||
|
||||
password = result.Password;
|
||||
success = result.Success;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private (TextKey message, TextKey title) GetTextKeysFor(PasswordRequestPurpose purpose)
|
||||
{
|
||||
var message = default(TextKey);
|
||||
var title = default(TextKey);
|
||||
|
||||
switch (purpose)
|
||||
{
|
||||
case PasswordRequestPurpose.LocalAdministrator:
|
||||
message = TextKey.PasswordDialog_LocalAdminPasswordRequired;
|
||||
title = TextKey.PasswordDialog_LocalAdminPasswordRequiredTitle;
|
||||
break;
|
||||
case PasswordRequestPurpose.LocalSettings:
|
||||
message = TextKey.PasswordDialog_LocalSettingsPasswordRequired;
|
||||
title = TextKey.PasswordDialog_LocalSettingsPasswordRequiredTitle;
|
||||
break;
|
||||
case PasswordRequestPurpose.Settings:
|
||||
message = TextKey.PasswordDialog_SettingsPasswordRequired;
|
||||
title = TextKey.PasswordDialog_SettingsPasswordRequiredTitle;
|
||||
break;
|
||||
}
|
||||
|
||||
return (message, title);
|
||||
}
|
||||
|
||||
protected enum UriSource
|
||||
{
|
||||
Undefined,
|
||||
AppData,
|
||||
CommandLine,
|
||||
ProgramData,
|
||||
Reconfiguration,
|
||||
Server
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SafeExamBrowser.Communication.Contracts.Data;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
@@ -14,39 +15,38 @@ using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
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.Security;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class ConfigurationOperation : ConfigurationBaseOperation
|
||||
{
|
||||
private readonly string[] commandLineArgs;
|
||||
private readonly IFileSystem fileSystem;
|
||||
private readonly IHashAlgorithm hashAlgorithm;
|
||||
private readonly ILogger logger;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired;
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public ConfigurationOperation(
|
||||
string[] commandLineArgs,
|
||||
IConfigurationRepository configuration,
|
||||
Dependencies dependencies,
|
||||
IFileSystem fileSystem,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
ILogger logger,
|
||||
SessionContext sessionContext) : base(commandLineArgs, configuration, sessionContext)
|
||||
IConfigurationRepository repository,
|
||||
IUserInterfaceFactory uiFactory) : base(dependencies, repository, uiFactory)
|
||||
{
|
||||
this.commandLineArgs = commandLineArgs;
|
||||
this.fileSystem = fileSystem;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
logger.Info("Initializing application configuration...");
|
||||
Logger.Info("Initializing application configuration...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration);
|
||||
|
||||
var result = OperationResult.Failed;
|
||||
@@ -68,7 +68,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
logger.Info("Initializing new application configuration...");
|
||||
Logger.Info("Initializing new application configuration...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration);
|
||||
|
||||
var result = OperationResult.Failed;
|
||||
@@ -80,7 +80,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"The resource specified for reconfiguration does not exist or is not valid!");
|
||||
Logger.Warn($"The resource specified for reconfiguration does not exist or is not valid!");
|
||||
}
|
||||
|
||||
LogOperationResult(result);
|
||||
@@ -93,15 +93,10 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
protected override void InvokeActionRequired(ActionRequiredEventArgs args)
|
||||
{
|
||||
ActionRequired?.Invoke(args);
|
||||
}
|
||||
|
||||
private OperationResult LoadDefaultSettings()
|
||||
{
|
||||
logger.Info("No valid configuration resource specified and no local client configuration found - loading default settings...");
|
||||
Context.Next.Settings = configuration.LoadDefaultSettings();
|
||||
Logger.Info("No valid configuration resource specified and no local client configuration found - loading default settings...");
|
||||
Context.Next.Settings = repository.LoadDefaultSettings();
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
@@ -170,7 +165,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
}
|
||||
|
||||
fileSystem.Delete(uri.LocalPath);
|
||||
logger.Info($"Deleted temporary configuration file '{uri}'.");
|
||||
Logger.Info($"Deleted temporary configuration file '{uri}'.");
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -213,17 +208,18 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
Context.Next.IsBrowserResource = true;
|
||||
Context.Next.Settings.Applications.Blacklist.Clear();
|
||||
Context.Next.Settings.Applications.Whitelist.Clear();
|
||||
Context.Next.Settings.Display.AllowedDisplays = 10;
|
||||
Context.Next.Settings.Display.IgnoreError = true;
|
||||
Context.Next.Settings.Display.InternalDisplayOnly = false;
|
||||
Context.Next.Settings.Browser.DeleteCacheOnShutdown = false;
|
||||
Context.Next.Settings.Browser.DeleteCookiesOnShutdown = false;
|
||||
Context.Next.Settings.Browser.StartUrl = uri.AbsoluteUri;
|
||||
Context.Next.Settings.Display.AllowedDisplays = 10;
|
||||
Context.Next.Settings.Display.IgnoreError = true;
|
||||
Context.Next.Settings.Display.InternalDisplayOnly = false;
|
||||
Context.Next.Settings.Security.AllowReconfiguration = true;
|
||||
Context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Allow;
|
||||
Context.Next.Settings.Service.IgnoreService = true;
|
||||
Context.Next.Settings.UserInterface.ActionCenter.EnableActionCenter = false;
|
||||
|
||||
logger.Info($"The configuration resource needs authentication or is a webpage, using '{uri}' as start URL for the browser.");
|
||||
Logger.Info($"The configuration resource needs authentication or is a webpage, using '{uri}' as start URL for the browser.");
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
@@ -249,7 +245,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
private void HandleReconfigurationByBrowserResource()
|
||||
{
|
||||
Context.Next.Settings.Browser.DeleteCookiesOnStartup = false;
|
||||
logger.Info("Some browser settings were overridden in order to retain a potential LMS / web application session.");
|
||||
Logger.Info("Some browser settings were overridden in order to retain a potential LMS / web application session.");
|
||||
}
|
||||
|
||||
private void HandleStartUrlQuery(Uri uri, UriSource source)
|
||||
@@ -268,49 +264,34 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
private bool? TryConfigureClient(Uri uri, PasswordParameters passwordParams, string currentPassword = default)
|
||||
{
|
||||
var mustAuthenticate = IsRequiredToAuthenticateForClientConfiguration(passwordParams, currentPassword);
|
||||
var success = new bool?(true);
|
||||
|
||||
logger.Info("Starting client configuration...");
|
||||
Logger.Info("Starting client configuration...");
|
||||
|
||||
if (mustAuthenticate)
|
||||
{
|
||||
var authenticated = AuthenticateForClientConfiguration(currentPassword);
|
||||
|
||||
if (authenticated == true)
|
||||
{
|
||||
logger.Info("Authentication was successful.");
|
||||
}
|
||||
|
||||
if (authenticated == false)
|
||||
{
|
||||
logger.Info("Authentication has failed!");
|
||||
ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!authenticated.HasValue)
|
||||
{
|
||||
logger.Info("Authentication was aborted.");
|
||||
|
||||
return null;
|
||||
}
|
||||
success = AuthenticateForClientConfiguration(currentPassword);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info("Authentication is not required.");
|
||||
Logger.Info("Authentication is not required.");
|
||||
}
|
||||
|
||||
var status = configuration.ConfigureClientWith(uri, passwordParams);
|
||||
var success = status == SaveStatus.Success;
|
||||
if (success == true)
|
||||
{
|
||||
var status = repository.ConfigureClientWith(uri, passwordParams);
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Client configuration was successful.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Client configuration failed with status '{status}'!");
|
||||
ActionRequired?.Invoke(new ClientConfigurationErrorMessageArgs());
|
||||
success = status == SaveStatus.Success;
|
||||
|
||||
if (success == true)
|
||||
{
|
||||
Logger.Info("Client configuration was successful.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error($"Client configuration failed with status '{status}'!");
|
||||
ShowMessageBox(TextKey.MessageBox_ClientConfigurationError, TextKey.MessageBox_ClientConfigurationErrorTitle, icon: MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -344,9 +325,9 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
private bool? AuthenticateForClientConfiguration(string currentPassword)
|
||||
{
|
||||
var authenticated = false;
|
||||
var authenticated = default(bool?);
|
||||
|
||||
for (var attempts = 0; attempts < 5 && !authenticated; attempts++)
|
||||
for (var attempts = 0; attempts < 5 && authenticated != true; attempts++)
|
||||
{
|
||||
var success = TryGetPassword(PasswordRequestPurpose.LocalAdministrator, out var password);
|
||||
|
||||
@@ -356,40 +337,73 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
authenticated = default;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (authenticated == true)
|
||||
{
|
||||
Logger.Info("Authentication was successful.");
|
||||
}
|
||||
|
||||
if (authenticated == false)
|
||||
{
|
||||
Logger.Info("Authentication has failed!");
|
||||
ShowMessageBox(TextKey.MessageBox_InvalidPasswordError, TextKey.MessageBox_InvalidPasswordErrorTitle, icon: MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
if (authenticated == default)
|
||||
{
|
||||
Logger.Info("Authentication was aborted.");
|
||||
}
|
||||
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
private bool AbortAfterClientConfiguration()
|
||||
{
|
||||
var args = new ConfigurationCompletedEventArgs();
|
||||
var message = TextKey.MessageBox_ClientConfigurationQuestion;
|
||||
var title = TextKey.MessageBox_ClientConfigurationQuestionTitle;
|
||||
var result = ShowMessageBox(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question);
|
||||
var abort = result == MessageBoxResult.Yes;
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} startup after successful client configuration.");
|
||||
Logger.Info($"The user chose to {(abort ? "abort" : "continue")} startup after successful client configuration.");
|
||||
|
||||
return args.AbortStartup;
|
||||
return abort;
|
||||
}
|
||||
|
||||
private void ShowFailureMessage(LoadStatus status, Uri uri)
|
||||
{
|
||||
var error = MessageBoxIcon.Error;
|
||||
var message = default(TextKey);
|
||||
var placeholders = new Dictionary<string, string>();
|
||||
var title = default(TextKey);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case LoadStatus.PasswordNeeded:
|
||||
ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
|
||||
message = TextKey.MessageBox_InvalidPasswordError;
|
||||
title = TextKey.MessageBox_InvalidPasswordErrorTitle;
|
||||
break;
|
||||
case LoadStatus.InvalidData:
|
||||
ActionRequired?.Invoke(new InvalidDataMessageArgs(uri.ToString()));
|
||||
message = TextKey.MessageBox_InvalidConfigurationData;
|
||||
placeholders["%%URI%%"] = uri.ToString();
|
||||
title = TextKey.MessageBox_InvalidConfigurationDataTitle;
|
||||
break;
|
||||
case LoadStatus.NotSupported:
|
||||
ActionRequired?.Invoke(new NotSupportedMessageArgs(uri.ToString()));
|
||||
message = TextKey.MessageBox_NotSupportedConfigurationResource;
|
||||
placeholders["%%URI%%"] = uri.ToString();
|
||||
title = TextKey.MessageBox_NotSupportedConfigurationResourceTitle;
|
||||
break;
|
||||
case LoadStatus.UnexpectedError:
|
||||
ActionRequired?.Invoke(new UnexpectedErrorMessageArgs(uri.ToString()));
|
||||
message = TextKey.MessageBox_UnexpectedConfigurationError;
|
||||
placeholders["%%URI%%"] = uri.ToString();
|
||||
title = TextKey.MessageBox_UnexpectedConfigurationErrorTitle;
|
||||
break;
|
||||
}
|
||||
|
||||
ShowMessageBox(message, title, icon: error, messagePlaceholders: placeholders);
|
||||
}
|
||||
|
||||
private bool TryInitializeSettingsUri(out Uri uri, out UriSource source)
|
||||
@@ -403,21 +417,21 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
isValidUri = Uri.TryCreate(commandLineArgs[1], UriKind.Absolute, out uri);
|
||||
source = UriSource.CommandLine;
|
||||
logger.Info($"Found command-line argument for configuration resource: '{uri}', the URI is {(isValidUri ? "valid" : "invalid")}.");
|
||||
Logger.Info($"Found command-line argument for configuration resource: '{uri}', the URI is {(isValidUri ? "valid" : "invalid")}.");
|
||||
}
|
||||
|
||||
if (!isValidUri && File.Exists(ProgramDataFilePath))
|
||||
{
|
||||
isValidUri = Uri.TryCreate(ProgramDataFilePath, UriKind.Absolute, out uri);
|
||||
source = UriSource.ProgramData;
|
||||
logger.Info($"Found configuration file in program data directory: '{uri}'.");
|
||||
Logger.Info($"Found configuration file in program data directory: '{uri}'.");
|
||||
}
|
||||
|
||||
if (!isValidUri && File.Exists(AppDataFilePath))
|
||||
{
|
||||
isValidUri = Uri.TryCreate(AppDataFilePath, UriKind.Absolute, out uri);
|
||||
source = UriSource.AppData;
|
||||
logger.Info($"Found configuration file in app data directory: '{uri}'.");
|
||||
Logger.Info($"Found configuration file in app data directory: '{uri}'.");
|
||||
}
|
||||
|
||||
return isValidUri;
|
||||
@@ -438,13 +452,13 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
switch (result)
|
||||
{
|
||||
case OperationResult.Aborted:
|
||||
logger.Info("The configuration was aborted by the user.");
|
||||
Logger.Info("The configuration was aborted by the user.");
|
||||
break;
|
||||
case OperationResult.Failed:
|
||||
logger.Warn("The configuration has failed!");
|
||||
Logger.Warn("The configuration has failed!");
|
||||
break;
|
||||
case OperationResult.Success:
|
||||
logger.Info("The configuration was successful.");
|
||||
Logger.Info("The configuration was successful.");
|
||||
break;
|
||||
}
|
||||
}
|
43
SafeExamBrowser.Runtime/Operations/Session/Dependencies.cs
Normal file
43
SafeExamBrowser.Runtime/Operations/Session/Dependencies.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Runtime.Communication;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class Dependencies
|
||||
{
|
||||
public ClientBridge ClientBridge { get; }
|
||||
internal ILogger Logger { get; }
|
||||
internal IMessageBox MessageBox { get; }
|
||||
internal RuntimeContext RuntimeContext { get; }
|
||||
internal IRuntimeWindow RuntimeWindow { get; }
|
||||
internal IText Text { get; }
|
||||
|
||||
internal Dependencies(
|
||||
ClientBridge clientBridge,
|
||||
ILogger logger,
|
||||
IMessageBox messageBox,
|
||||
IRuntimeWindow runtimeWindow,
|
||||
RuntimeContext runtimeContext,
|
||||
IText text)
|
||||
{
|
||||
ClientBridge = clientBridge ?? throw new ArgumentNullException(nameof(clientBridge));
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
MessageBox = messageBox ?? throw new ArgumentNullException(nameof(messageBox));
|
||||
RuntimeWindow = runtimeWindow ?? throw new ArgumentNullException(nameof(runtimeWindow));
|
||||
RuntimeContext = runtimeContext ?? throw new ArgumentNullException(nameof(runtimeContext));
|
||||
Text = text ?? throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,22 +9,16 @@
|
||||
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.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class DisclaimerOperation : SessionOperation
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired;
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public DisclaimerOperation(ILogger logger, SessionContext context) : base(context)
|
||||
public DisclaimerOperation(Dependencies dependencies) : base(dependencies)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
@@ -58,29 +52,23 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
private OperationResult ShowScreenProctoringDisclaimer()
|
||||
{
|
||||
var args = new MessageEventArgs
|
||||
{
|
||||
Action = MessageBoxAction.OkCancel,
|
||||
Icon = MessageBoxIcon.Information,
|
||||
Message = TextKey.MessageBox_ScreenProctoringDisclaimer,
|
||||
Title = TextKey.MessageBox_ScreenProctoringDisclaimerTitle
|
||||
};
|
||||
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_WaitDisclaimerConfirmation);
|
||||
ActionRequired?.Invoke(args);
|
||||
|
||||
if (args.Result == MessageBoxResult.Ok)
|
||||
var message = TextKey.MessageBox_ScreenProctoringDisclaimer;
|
||||
var title = TextKey.MessageBox_ScreenProctoringDisclaimerTitle;
|
||||
var result = ShowMessageBox(message, title, action: MessageBoxAction.OkCancel);
|
||||
var operationResult = result == MessageBoxResult.Ok ? OperationResult.Success : OperationResult.Aborted;
|
||||
|
||||
if (result == MessageBoxResult.Ok)
|
||||
{
|
||||
logger.Info("The user confirmed the screen proctoring disclaimer.");
|
||||
|
||||
return OperationResult.Success;
|
||||
Logger.Info("The user confirmed the screen proctoring disclaimer.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("The user did not confirm the screen proctoring disclaimer! Aborting session initialization...");
|
||||
|
||||
return OperationResult.Aborted;
|
||||
Logger.Warn("The user did not confirm the screen proctoring disclaimer! Aborting session initialization...");
|
||||
}
|
||||
|
||||
return operationResult;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Display;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class DisplayMonitorOperation : SessionOperation
|
||||
{
|
||||
private readonly IDisplayMonitor displayMonitor;
|
||||
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public DisplayMonitorOperation(Dependencies dependencies, IDisplayMonitor displayMonitor) : base(dependencies)
|
||||
{
|
||||
this.displayMonitor = displayMonitor;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
return CheckDisplayConfiguration();
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
return CheckDisplayConfiguration();
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private OperationResult CheckDisplayConfiguration()
|
||||
{
|
||||
Logger.Info("Validating display configuration...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_ValidateDisplayConfiguration);
|
||||
|
||||
var validation = displayMonitor.ValidateConfiguration(Context.Next.Settings.Display);
|
||||
var result = validation.IsAllowed ? OperationResult.Success : OperationResult.Failed;
|
||||
|
||||
if (validation.IsAllowed)
|
||||
{
|
||||
Logger.Info("Display configuration is allowed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error("Display configuration is not allowed!");
|
||||
ShowError(validation);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ShowError(ValidationResult validation)
|
||||
{
|
||||
var internalOnly = Text.Get(TextKey.MessageBox_DisplayConfigurationInternal);
|
||||
var internalOrExternal = Text.Get(TextKey.MessageBox_DisplayConfigurationInternalOrExternal);
|
||||
var message = TextKey.MessageBox_DisplayConfigurationError;
|
||||
var title = TextKey.MessageBox_DisplayConfigurationErrorTitle;
|
||||
var placeholders = new Dictionary<string, string>
|
||||
{
|
||||
{ "%%_ALLOWED_COUNT_%%", Convert.ToString(Context.Next.Settings.Display.AllowedDisplays) },
|
||||
{ "%%_EXTERNAL_COUNT_%%", Convert.ToString(validation.ExternalDisplays) },
|
||||
{ "%%_INTERNAL_COUNT_%%", Convert.ToString(validation.InternalDisplays) },
|
||||
{ "%%_TYPE_%%", Context.Next.Settings.Display.InternalDisplayOnly ? internalOnly : internalOrExternal }
|
||||
};
|
||||
|
||||
ShowMessageBox(message, title, icon: MessageBoxIcon.Error, messagePlaceholders: placeholders);
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,45 +9,40 @@
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings.Security;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class KioskModeOperation : SessionOperation
|
||||
{
|
||||
private readonly IDesktopFactory desktopFactory;
|
||||
private readonly IDesktopMonitor desktopMonitor;
|
||||
private readonly IExplorerShell explorerShell;
|
||||
private readonly ILogger logger;
|
||||
private readonly IProcessFactory processFactory;
|
||||
|
||||
private KioskMode? activeMode;
|
||||
private IDesktop customDesktop;
|
||||
private IDesktop originalDesktop;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public KioskModeOperation(
|
||||
Dependencies dependencies,
|
||||
IDesktopFactory desktopFactory,
|
||||
IDesktopMonitor desktopMonitor,
|
||||
IExplorerShell explorerShell,
|
||||
ILogger logger,
|
||||
IProcessFactory processFactory,
|
||||
SessionContext sessionContext) : base(sessionContext)
|
||||
IProcessFactory processFactory) : base(dependencies)
|
||||
{
|
||||
this.desktopFactory = desktopFactory;
|
||||
this.desktopMonitor = desktopMonitor;
|
||||
this.explorerShell = explorerShell;
|
||||
this.logger = logger;
|
||||
this.processFactory = processFactory;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
logger.Info($"Initializing kiosk mode '{Context.Next.Settings.Security.KioskMode}'...");
|
||||
Logger.Info($"Initializing kiosk mode '{Context.Next.Settings.Security.KioskMode}'...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeKioskMode);
|
||||
|
||||
activeMode = Context.Next.Settings.Security.KioskMode;
|
||||
@@ -71,11 +66,11 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (activeMode == newMode)
|
||||
{
|
||||
logger.Info($"New kiosk mode '{newMode}' is the same as the currently active mode, skipping re-initialization...");
|
||||
Logger.Info($"New kiosk mode '{newMode}' is the same as the currently active mode, skipping re-initialization...");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Switching from kiosk mode '{activeMode}' to '{newMode}'...");
|
||||
Logger.Info($"Switching from kiosk mode '{activeMode}' to '{newMode}'...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeKioskMode);
|
||||
|
||||
switch (activeMode)
|
||||
@@ -106,7 +101,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
logger.Info($"Reverting kiosk mode '{activeMode}'...");
|
||||
Logger.Info($"Reverting kiosk mode '{activeMode}'...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_RevertKioskMode);
|
||||
|
||||
switch (activeMode)
|
||||
@@ -125,14 +120,14 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
private void CreateCustomDesktop()
|
||||
{
|
||||
originalDesktop = desktopFactory.GetCurrent();
|
||||
logger.Info($"Current desktop is {originalDesktop}.");
|
||||
Logger.Info($"Current desktop is {originalDesktop}.");
|
||||
|
||||
customDesktop = desktopFactory.CreateRandom();
|
||||
logger.Info($"Created custom desktop {customDesktop}.");
|
||||
Logger.Info($"Created custom desktop {customDesktop}.");
|
||||
|
||||
customDesktop.Activate();
|
||||
processFactory.StartupDesktop = customDesktop;
|
||||
logger.Info("Successfully activated custom desktop.");
|
||||
Logger.Info("Successfully activated custom desktop.");
|
||||
|
||||
desktopMonitor.Start(customDesktop);
|
||||
}
|
||||
@@ -145,21 +140,21 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
originalDesktop.Activate();
|
||||
processFactory.StartupDesktop = originalDesktop;
|
||||
logger.Info($"Switched back to original desktop {originalDesktop}.");
|
||||
Logger.Info($"Switched back to original desktop {originalDesktop}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"No original desktop found to activate!");
|
||||
Logger.Warn($"No original desktop found to activate!");
|
||||
}
|
||||
|
||||
if (customDesktop != default)
|
||||
{
|
||||
customDesktop.Close();
|
||||
logger.Info($"Closed custom desktop {customDesktop}.");
|
||||
Logger.Info($"Closed custom desktop {customDesktop}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"No custom desktop found to close!");
|
||||
Logger.Warn($"No custom desktop found to close!");
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class RemoteSessionOperation : SessionOperation
|
||||
{
|
||||
private readonly IRemoteSessionDetector detector;
|
||||
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public RemoteSessionOperation(Dependencies dependencies, IRemoteSessionDetector detector) : base(dependencies)
|
||||
{
|
||||
this.detector = detector;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
return ValidatePolicy();
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
return ValidatePolicy();
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private OperationResult ValidatePolicy()
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
Logger.Info($"Validating remote session policy...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_ValidateRemoteSessionPolicy);
|
||||
|
||||
if (Context.Next.Settings.Service.DisableRemoteConnections && detector.IsRemoteSession())
|
||||
{
|
||||
result = OperationResult.Aborted;
|
||||
Logger.Error("Detected remote session while SEB is not allowed to be run in a remote session! Aborting...");
|
||||
ShowMessageBox(TextKey.MessageBox_RemoteSessionNotAllowed, TextKey.MessageBox_RemoteSessionNotAllowedTitle, icon: MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,35 +13,29 @@ using SafeExamBrowser.Configuration.Contracts;
|
||||
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.Server.Contracts;
|
||||
using SafeExamBrowser.Server.Contracts.Data;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class ServerOperation : ConfigurationBaseOperation
|
||||
{
|
||||
private readonly IFileSystem fileSystem;
|
||||
private readonly ILogger logger;
|
||||
private readonly IServerProxy server;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired;
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public ServerOperation(
|
||||
string[] commandLineArgs,
|
||||
IConfigurationRepository configuration,
|
||||
Dependencies dependencies,
|
||||
IFileSystem fileSystem,
|
||||
ILogger logger,
|
||||
SessionContext context,
|
||||
IServerProxy server) : base(commandLineArgs, configuration, context)
|
||||
IConfigurationRepository repository,
|
||||
IServerProxy server,
|
||||
IUserInterfaceFactory uiFactory) : base(dependencies, repository, uiFactory)
|
||||
{
|
||||
this.fileSystem = fileSystem;
|
||||
this.logger = logger;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@@ -56,7 +50,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
var exams = default(IEnumerable<Exam>);
|
||||
var uri = default(Uri);
|
||||
|
||||
logger.Info("Initializing server...");
|
||||
Logger.Info("Initializing server...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer);
|
||||
|
||||
server.Initialize(Context.Next.Settings.Server);
|
||||
@@ -96,14 +90,23 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
if (abort)
|
||||
{
|
||||
result = OperationResult.Aborted;
|
||||
logger.Info("The user aborted the server operation.");
|
||||
Logger.Info("The user aborted the server operation.");
|
||||
}
|
||||
|
||||
if (fallback)
|
||||
{
|
||||
Context.Next.Settings.SessionMode = SessionMode.Normal;
|
||||
result = OperationResult.Success;
|
||||
logger.Info("The user chose to fallback and start a normal session.");
|
||||
Logger.Info("The user chose to fallback and start a normal session.");
|
||||
}
|
||||
|
||||
if (result == OperationResult.Success)
|
||||
{
|
||||
Logger.Info("Successfully initialized server.");
|
||||
}
|
||||
else if (result == OperationResult.Failed)
|
||||
{
|
||||
Logger.Error("Failed to initialize server!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +119,16 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (Context.Current.Settings.SessionMode == SessionMode.Server && Context.Next.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
result = AbortServerReconfiguration();
|
||||
result = Revert();
|
||||
|
||||
if (result == OperationResult.Success)
|
||||
{
|
||||
result = Perform();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error($"Cannot start new server session due to failed finalization of current server session! Terminating...");
|
||||
}
|
||||
}
|
||||
else if (Context.Current.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
@@ -136,7 +148,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (Context.Current?.Settings.SessionMode == SessionMode.Server || Context.Next?.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
logger.Info("Finalizing server...");
|
||||
Logger.Info("Finalizing server...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServer);
|
||||
|
||||
var disconnect = server.Disconnect();
|
||||
@@ -144,21 +156,18 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
if (disconnect.Success)
|
||||
{
|
||||
result = OperationResult.Success;
|
||||
Logger.Info("Successfully finalized server.");
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Failed;
|
||||
Logger.Error("Failed to finalize server!");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override void InvokeActionRequired(ActionRequiredEventArgs args)
|
||||
{
|
||||
ActionRequired?.Invoke(args);
|
||||
}
|
||||
|
||||
private OperationResult TryLoadServerSettings(Exam exam, Uri uri)
|
||||
{
|
||||
var info = server.GetConnectionInfo();
|
||||
@@ -170,6 +179,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
if (status == LoadStatus.Success)
|
||||
{
|
||||
var browserSettings = Context.Next.Settings.Browser;
|
||||
var invigilationSettings = settings.Server.Invigilation;
|
||||
var serverSettings = Context.Next.Settings.Server;
|
||||
|
||||
Context.Next.AppConfig.ServerApi = info.Api;
|
||||
@@ -181,6 +191,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
Context.Next.Settings.Browser.StartUrl = exam.Url;
|
||||
Context.Next.Settings.Browser.StartUrlQuery = browserSettings.StartUrlQuery;
|
||||
Context.Next.Settings.Server = serverSettings;
|
||||
Context.Next.Settings.Server.Invigilation = invigilationSettings;
|
||||
Context.Next.Settings.SessionMode = SessionMode.Server;
|
||||
|
||||
result = OperationResult.Success;
|
||||
@@ -236,19 +247,33 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
private bool Retry(string message, out bool abort, out bool fallback)
|
||||
{
|
||||
var args = new ServerFailureEventArgs(message, Context.Next.Settings.Server.PerformFallback);
|
||||
AskForServerFailureAction(message, out abort, out fallback, out var retry);
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
|
||||
abort = args.Abort;
|
||||
fallback = args.Fallback;
|
||||
|
||||
if (args.Retry)
|
||||
if (retry)
|
||||
{
|
||||
logger.Debug("The user chose to retry the current server request.");
|
||||
Logger.Debug("The user chose to retry the current server request.");
|
||||
}
|
||||
|
||||
return args.Retry;
|
||||
return retry;
|
||||
}
|
||||
|
||||
private void AskForServerFailureAction(string message, out bool abort, out bool fallback, out bool retry)
|
||||
{
|
||||
var showFallback = Context.Next.Settings.Server.PerformFallback;
|
||||
|
||||
if (ClientBridge.IsRequired())
|
||||
{
|
||||
ClientBridge.TryAskForServerFailureAction(message, showFallback, out abort, out fallback, out retry);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dialog = uiFactory.CreateServerFailureDialog(message, showFallback);
|
||||
var result = dialog.Show(RuntimeWindow);
|
||||
|
||||
abort = result.Abort;
|
||||
fallback = result.Fallback;
|
||||
retry = result.Retry;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySelectExam(IEnumerable<Exam> exams, out Exam exam)
|
||||
@@ -257,36 +282,35 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Context.Next.Settings.Server.ExamId))
|
||||
{
|
||||
var args = new ExamSelectionEventArgs(exams);
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
|
||||
exam = args.SelectedExam;
|
||||
success = args.Success;
|
||||
success = TryAskForExamSelection(exams, out exam);
|
||||
}
|
||||
else
|
||||
{
|
||||
exam = exams.First();
|
||||
logger.Info("Automatically selected exam as defined in configuration.");
|
||||
Logger.Info("Automatically selected exam as defined in configuration.");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private OperationResult AbortServerReconfiguration()
|
||||
private bool TryAskForExamSelection(IEnumerable<Exam> exams, out Exam exam)
|
||||
{
|
||||
var args = new MessageEventArgs
|
||||
var success = false;
|
||||
|
||||
if (ClientBridge.IsRequired())
|
||||
{
|
||||
Action = MessageBoxAction.Ok,
|
||||
Icon = MessageBoxIcon.Warning,
|
||||
Message = TextKey.MessageBox_ServerReconfigurationWarning,
|
||||
Title = TextKey.MessageBox_ServerReconfigurationWarningTitle
|
||||
};
|
||||
success = ClientBridge.TryAskForExamSelection(exams, out exam);
|
||||
}
|
||||
else
|
||||
{
|
||||
var dialog = uiFactory.CreateExamSelectionDialog(exams);
|
||||
var result = dialog.Show(RuntimeWindow);
|
||||
|
||||
logger.Warn("Server reconfiguration is currently not supported, aborting...");
|
||||
ActionRequired?.Invoke(args);
|
||||
exam = result.SelectedExam;
|
||||
success = result.Success;
|
||||
}
|
||||
|
||||
return OperationResult.Aborted;
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
@@ -15,50 +15,45 @@ using SafeExamBrowser.Configuration.Contracts;
|
||||
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.Service;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class ServiceOperation : SessionOperation
|
||||
{
|
||||
private ILogger logger;
|
||||
private IRuntimeHost runtimeHost;
|
||||
private IServiceProxy service;
|
||||
private readonly IRuntimeHost runtimeHost;
|
||||
private readonly IServiceProxy serviceProxy;
|
||||
private readonly int timeout_ms;
|
||||
private readonly IUserInfo userInfo;
|
||||
|
||||
private string serviceEventName;
|
||||
private Guid? sessionId;
|
||||
private int timeout_ms;
|
||||
private IUserInfo userInfo;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired;
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public ServiceOperation(
|
||||
ILogger logger,
|
||||
Dependencies dependencies,
|
||||
IRuntimeHost runtimeHost,
|
||||
IServiceProxy service,
|
||||
SessionContext sessionContext,
|
||||
IServiceProxy serviceProxy,
|
||||
int timeout_ms,
|
||||
IUserInfo userInfo) : base(sessionContext)
|
||||
IUserInfo userInfo) : base(dependencies)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.runtimeHost = runtimeHost;
|
||||
this.service = service;
|
||||
this.serviceProxy = serviceProxy;
|
||||
this.timeout_ms = timeout_ms;
|
||||
this.userInfo = userInfo;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
logger.Info($"Initializing service...");
|
||||
Logger.Info($"Initializing service...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
|
||||
|
||||
var success = IgnoreService() || TryInitializeConnection();
|
||||
|
||||
if (success && service.IsConnected)
|
||||
if (success && serviceProxy.IsConnected)
|
||||
{
|
||||
success = TryStartSession();
|
||||
}
|
||||
@@ -68,12 +63,12 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
logger.Info($"Initializing service...");
|
||||
Logger.Info($"Initializing service...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
|
||||
|
||||
var success = true;
|
||||
|
||||
if (service.IsConnected)
|
||||
if (serviceProxy.IsConnected)
|
||||
{
|
||||
if (sessionId.HasValue)
|
||||
{
|
||||
@@ -90,7 +85,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
success = IgnoreService() || TryInitializeConnection();
|
||||
}
|
||||
|
||||
if (success && service.IsConnected)
|
||||
if (success && serviceProxy.IsConnected)
|
||||
{
|
||||
success = TryStartSession();
|
||||
}
|
||||
@@ -100,12 +95,12 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
logger.Info("Finalizing service...");
|
||||
Logger.Info("Finalizing service...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession);
|
||||
|
||||
var success = true;
|
||||
|
||||
if (service.IsConnected)
|
||||
if (serviceProxy.IsConnected)
|
||||
{
|
||||
if (sessionId.HasValue)
|
||||
{
|
||||
@@ -122,7 +117,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
if (Context.Next.Settings.Service.IgnoreService)
|
||||
{
|
||||
logger.Info("The service will be ignored for the next session.");
|
||||
Logger.Info("The service will be ignored for the next session.");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -134,32 +129,22 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
var mandatory = Context.Next.Settings.Service.Policy == ServicePolicy.Mandatory;
|
||||
var warn = Context.Next.Settings.Service.Policy == ServicePolicy.Warn;
|
||||
var connected = service.Connect();
|
||||
var connected = serviceProxy.Connect();
|
||||
var success = connected || !mandatory;
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info($"The service is {(mandatory ? "mandatory" : "optional")} and {(connected ? "connected." : "not connected.")}");
|
||||
Logger.Info($"The service is {(mandatory ? "mandatory" : "optional")} and {(connected ? "connected." : "not connected.")}");
|
||||
|
||||
if (!connected && warn)
|
||||
{
|
||||
ActionRequired?.Invoke(new MessageEventArgs
|
||||
{
|
||||
Icon = MessageBoxIcon.Warning,
|
||||
Message = TextKey.MessageBox_ServiceUnavailableWarning,
|
||||
Title = TextKey.MessageBox_ServiceUnavailableWarningTitle
|
||||
});
|
||||
ShowMessageBox(TextKey.MessageBox_ServiceUnavailableWarning, TextKey.MessageBox_ServiceUnavailableWarningTitle, icon: MessageBoxIcon.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("The service is mandatory but no connection could be established!");
|
||||
ActionRequired?.Invoke(new MessageEventArgs
|
||||
{
|
||||
Icon = MessageBoxIcon.Error,
|
||||
Message = TextKey.MessageBox_ServiceUnavailableError,
|
||||
Title = TextKey.MessageBox_ServiceUnavailableErrorTitle
|
||||
});
|
||||
Logger.Error("The service is mandatory but no connection could be established!");
|
||||
ShowMessageBox(TextKey.MessageBox_ServiceUnavailableError, TextKey.MessageBox_ServiceUnavailableErrorTitle, icon: MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -167,15 +152,15 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
private bool TryTerminateConnection()
|
||||
{
|
||||
var disconnected = service.Disconnect();
|
||||
var disconnected = serviceProxy.Disconnect();
|
||||
|
||||
if (disconnected)
|
||||
{
|
||||
logger.Info("Successfully disconnected from service.");
|
||||
Logger.Info("Successfully disconnected from service.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to disconnect from service!");
|
||||
Logger.Error("Failed to disconnect from service!");
|
||||
}
|
||||
|
||||
return disconnected;
|
||||
@@ -193,9 +178,9 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
};
|
||||
var started = false;
|
||||
|
||||
logger.Info("Starting new service session...");
|
||||
Logger.Info("Starting new service session...");
|
||||
|
||||
var communication = service.StartSession(configuration);
|
||||
var communication = serviceProxy.StartSession(configuration);
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
@@ -205,16 +190,16 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
sessionId = Context.Next.SessionId;
|
||||
serviceEventName = Context.Next.AppConfig.ServiceEventName;
|
||||
logger.Info("Successfully started new service session.");
|
||||
Logger.Info("Successfully started new service session.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!");
|
||||
Logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to communicate session start command to service!");
|
||||
Logger.Error("Failed to communicate session start command to service!");
|
||||
}
|
||||
|
||||
return started;
|
||||
@@ -224,9 +209,9 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
var success = false;
|
||||
|
||||
logger.Info("Stopping current service session...");
|
||||
Logger.Info("Stopping current service session...");
|
||||
|
||||
var communication = service.StopSession(sessionId.Value);
|
||||
var communication = serviceProxy.StopSession(sessionId.Value);
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
@@ -234,32 +219,32 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (success)
|
||||
{
|
||||
sessionId = default(Guid?);
|
||||
serviceEventName = default(string);
|
||||
logger.Info("Successfully stopped service session.");
|
||||
sessionId = default;
|
||||
serviceEventName = default;
|
||||
Logger.Info("Successfully stopped service session.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!");
|
||||
Logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to communicate session stop command to service!");
|
||||
Logger.Error("Failed to communicate session stop command to service!");
|
||||
}
|
||||
|
||||
if (success && isFinalSession)
|
||||
{
|
||||
communication = service.RunSystemConfigurationUpdate();
|
||||
communication = serviceProxy.RunSystemConfigurationUpdate();
|
||||
success = communication.Success;
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
logger.Info("Instructed service to perform system configuration update.");
|
||||
Logger.Info("Instructed service to perform system configuration update.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to communicate system configuration update command to service!");
|
||||
Logger.Error("Failed to communicate system configuration update command to service!");
|
||||
}
|
||||
}
|
||||
|
@@ -8,20 +8,15 @@
|
||||
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class SessionActivationOperation : SessionOperation
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
public override event StatusChangedEventHandler StatusChanged { add { } remove { } }
|
||||
|
||||
public SessionActivationOperation(ILogger logger, SessionContext sessionContext) : base(sessionContext)
|
||||
public SessionActivationOperation(Dependencies dependencies) : base(dependencies)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
@@ -47,31 +42,31 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
private void SwitchLogSeverity()
|
||||
{
|
||||
if (logger.LogLevel != Context.Next.Settings.LogLevel)
|
||||
if (Logger.LogLevel != Context.Next.Settings.LogLevel)
|
||||
{
|
||||
var current = logger.LogLevel.ToString().ToUpper();
|
||||
var current = Logger.LogLevel.ToString().ToUpper();
|
||||
var next = Context.Next.Settings.LogLevel.ToString().ToUpper();
|
||||
|
||||
logger.Info($"Switching from log severity '{current}' to '{next}' for new session.");
|
||||
logger.LogLevel = Context.Next.Settings.LogLevel;
|
||||
Logger.Info($"Switching from log severity '{current}' to '{next}' for new session.");
|
||||
Logger.LogLevel = Context.Next.Settings.LogLevel;
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateNewSession()
|
||||
{
|
||||
var isFirstSession = Context.Current == null;
|
||||
var isFirstSession = Context.Current == default;
|
||||
|
||||
if (isFirstSession)
|
||||
{
|
||||
logger.Info($"Successfully activated first session '{Context.Next.SessionId}'.");
|
||||
Logger.Info($"Successfully activated first session '{Context.Next.SessionId}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"Successfully terminated old session '{Context.Current.SessionId}' and activated new session '{Context.Next.SessionId}'.");
|
||||
Logger.Info($"Successfully terminated old session '{Context.Current.SessionId}' and activated new session '{Context.Next.SessionId}'.");
|
||||
}
|
||||
|
||||
Context.Current = Context.Next;
|
||||
Context.Next = null;
|
||||
Context.Next = default;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,37 +6,28 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Communication.Contracts.Hosts;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class SessionInitializationOperation : SessionOperation
|
||||
{
|
||||
private IConfigurationRepository configuration;
|
||||
private IFileSystem fileSystem;
|
||||
private ILogger logger;
|
||||
private IRuntimeHost runtimeHost;
|
||||
private readonly IConfigurationRepository repository;
|
||||
private readonly IFileSystem fileSystem;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public SessionInitializationOperation(
|
||||
IConfigurationRepository configuration,
|
||||
Dependencies dependencies,
|
||||
IFileSystem fileSystem,
|
||||
ILogger logger,
|
||||
IRuntimeHost runtimeHost,
|
||||
SessionContext sessionContext) : base(sessionContext)
|
||||
IConfigurationRepository repository) : base(dependencies)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.fileSystem = fileSystem;
|
||||
this.logger = logger;
|
||||
this.runtimeHost = runtimeHost;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
@@ -62,14 +53,14 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
private void InitializeSessionConfiguration()
|
||||
{
|
||||
logger.Info("Initializing new session configuration...");
|
||||
Logger.Info("Initializing new session configuration...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSession);
|
||||
|
||||
Context.Next = configuration.InitializeSessionConfiguration();
|
||||
Context.Next = repository.InitializeSessionConfiguration();
|
||||
|
||||
logger.Info($" -> Client-ID: {Context.Next.AppConfig.ClientId}");
|
||||
logger.Info($" -> Runtime-ID: {Context.Next.AppConfig.RuntimeId}");
|
||||
logger.Info($" -> Session-ID: {Context.Next.SessionId}");
|
||||
Logger.Info($" -> Client-ID: {Context.Next.AppConfig.ClientId}");
|
||||
Logger.Info($" -> Runtime-ID: {Context.Next.AppConfig.RuntimeId}");
|
||||
Logger.Info($" -> Session-ID: {Context.Next.SessionId}");
|
||||
|
||||
fileSystem.CreateDirectory(Context.Next.AppConfig.TemporaryDirectory);
|
||||
}
|
@@ -9,22 +9,18 @@
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.System;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class SessionIntegrityOperation : SessionOperation
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly ISystemSentinel sentinel;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public SessionIntegrityOperation(ILogger logger, ISystemSentinel sentinel, SessionContext context) : base(context)
|
||||
public SessionIntegrityOperation(Dependencies dependencies, ISystemSentinel sentinel) : base(dependencies)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.sentinel = sentinel;
|
||||
}
|
||||
|
||||
@@ -88,11 +84,11 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Successfully ensured session integrity.");
|
||||
Logger.Info("Successfully ensured session integrity.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to ensure session integrity! Aborting session initialization...");
|
||||
Logger.Error("Failed to ensure session integrity! Aborting session initialization...");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +102,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("Verification of cursor configuration is disabled.");
|
||||
Logger.Debug("Verification of cursor configuration is disabled.");
|
||||
}
|
||||
|
||||
return success;
|
||||
@@ -120,12 +116,12 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
if (Context.Current?.Settings.Service.IgnoreService == false)
|
||||
{
|
||||
logger.Info($"Ease of access configuration is compromised but service was active in the current session.");
|
||||
Logger.Info($"Ease of access configuration is compromised but service was active in the current session.");
|
||||
success = true;
|
||||
}
|
||||
else if (!Context.Next.Settings.Service.IgnoreService)
|
||||
{
|
||||
logger.Info($"Ease of access configuration is compromised but service will be active in the next session.");
|
||||
Logger.Info($"Ease of access configuration is compromised but service will be active in the next session.");
|
||||
success = true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Runtime.Communication;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
/// <summary>
|
||||
/// The base implementation to be used for all operations in the session operation sequence.
|
||||
/// </summary>
|
||||
internal abstract class SessionOperation : IRepeatableOperation
|
||||
{
|
||||
private readonly IMessageBox messageBox;
|
||||
|
||||
protected ClientBridge ClientBridge { get; }
|
||||
protected RuntimeContext Context { get; }
|
||||
protected ILogger Logger { get; }
|
||||
protected IRuntimeWindow RuntimeWindow { get; }
|
||||
protected IText Text { get; }
|
||||
|
||||
public abstract event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
internal SessionOperation(Dependencies dependencies)
|
||||
{
|
||||
ClientBridge = dependencies.ClientBridge;
|
||||
Context = dependencies.RuntimeContext;
|
||||
Logger = dependencies.Logger;
|
||||
messageBox = dependencies.MessageBox;
|
||||
RuntimeWindow = dependencies.RuntimeWindow;
|
||||
Text = dependencies.Text;
|
||||
}
|
||||
|
||||
public abstract OperationResult Perform();
|
||||
public abstract OperationResult Repeat();
|
||||
public abstract OperationResult Revert();
|
||||
|
||||
/// <summary>
|
||||
/// Shows a message box either directly or (if required) via the currently running client application component. All session operations
|
||||
/// should always use this method instead of using <see cref="IMessageBox"/> directly!
|
||||
/// </summary>
|
||||
protected MessageBoxResult ShowMessageBox(
|
||||
TextKey messageKey,
|
||||
TextKey titleKey,
|
||||
MessageBoxAction action = MessageBoxAction.Ok,
|
||||
MessageBoxIcon icon = MessageBoxIcon.Information,
|
||||
IDictionary<string, string> messagePlaceholders = default,
|
||||
IDictionary<string, string> titlePlaceholders = default)
|
||||
{
|
||||
var message = Text.Get(messageKey);
|
||||
var result = default(MessageBoxResult);
|
||||
var title = Text.Get(titleKey);
|
||||
|
||||
foreach (var placeholder in messagePlaceholders ?? Enumerable.Empty<KeyValuePair<string, string>>())
|
||||
{
|
||||
message = message.Replace(placeholder.Key, placeholder.Value);
|
||||
}
|
||||
|
||||
foreach (var placeholder in titlePlaceholders ?? Enumerable.Empty<KeyValuePair<string, string>>())
|
||||
{
|
||||
title = title.Replace(placeholder.Key, placeholder.Value);
|
||||
}
|
||||
|
||||
if (ClientBridge.IsRequired())
|
||||
{
|
||||
result = ClientBridge.ShowMessageBox(message, title, action, icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = messageBox.Show(message, title, action, icon, RuntimeWindow);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.Core.OperationModel;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class SessionOperationSequence : RepeatableOperationSequence<SessionOperation>
|
||||
{
|
||||
private readonly IRuntimeWindow runtimeWindow;
|
||||
|
||||
internal SessionOperationSequence(ILogger logger, IEnumerable<SessionOperation> operations, IRuntimeWindow runtimeWindow) : base(logger, operations)
|
||||
{
|
||||
this.runtimeWindow = runtimeWindow;
|
||||
|
||||
ProgressChanged += SessionSequence_ProgressChanged;
|
||||
StatusChanged += SessionSequence_StatusChanged;
|
||||
}
|
||||
|
||||
private void SessionSequence_ProgressChanged(ProgressChangedEventArgs args)
|
||||
{
|
||||
if (args.CurrentValue.HasValue)
|
||||
{
|
||||
runtimeWindow?.SetValue(args.CurrentValue.Value);
|
||||
}
|
||||
|
||||
if (args.IsIndeterminate == true)
|
||||
{
|
||||
runtimeWindow?.SetIndeterminate();
|
||||
}
|
||||
|
||||
if (args.MaxValue.HasValue)
|
||||
{
|
||||
runtimeWindow?.SetMaxValue(args.MaxValue.Value);
|
||||
}
|
||||
|
||||
if (args.Progress == true)
|
||||
{
|
||||
runtimeWindow?.Progress();
|
||||
}
|
||||
|
||||
if (args.Regress == true)
|
||||
{
|
||||
runtimeWindow?.Regress();
|
||||
}
|
||||
}
|
||||
|
||||
private void SessionSequence_StatusChanged(TextKey status)
|
||||
{
|
||||
runtimeWindow?.UpdateStatus(status, true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,26 +12,19 @@ using System.Text;
|
||||
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.Security;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class VersionRestrictionOperation : SessionOperation
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly IText text;
|
||||
|
||||
private IList<VersionRestriction> Restrictions => Context.Next.Settings.Security.VersionRestrictions;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired;
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public VersionRestrictionOperation(ILogger logger, SessionContext context, IText text) : base(context)
|
||||
public VersionRestrictionOperation(Dependencies dependencies) : base(dependencies)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
@@ -53,7 +46,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
logger.Info("Validating version restrictions...");
|
||||
Logger.Info("Validating version restrictions...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_ValidateVersionRestrictions);
|
||||
|
||||
if (Restrictions.Any())
|
||||
@@ -63,19 +56,18 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
if (Restrictions.Any(r => IsFulfilled(r)))
|
||||
{
|
||||
logger.Info($"The installed SEB version '{version}' complies with the version restrictions: {requiredVersions}.");
|
||||
Logger.Info($"The installed SEB version '{version}' complies with the version restrictions: {requiredVersions}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Aborted;
|
||||
logger.Error($"The installed SEB version '{version}' does not comply with the version restrictions: {requiredVersions}.");
|
||||
|
||||
ActionRequired?.Invoke(new VersionRestrictionMessageArgs(version, BuildRequiredVersions()));
|
||||
Logger.Error($"The installed SEB version '{version}' does not comply with the version restrictions: {requiredVersions}.");
|
||||
ShowErrorMessage(version);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"There are no version restrictions for the configuration.");
|
||||
Logger.Info($"There are no version restrictions for the configuration.");
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -83,40 +75,40 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
private bool IsFulfilled(VersionRestriction restriction)
|
||||
{
|
||||
var isFulfilled = true;
|
||||
var fulfilled = true;
|
||||
var (major, minor, patch, build, isAllianceEdition) = GetVersion();
|
||||
|
||||
if (restriction.IsMinimumRestriction)
|
||||
{
|
||||
isFulfilled &= restriction.Major <= major;
|
||||
fulfilled &= restriction.Major <= major;
|
||||
|
||||
if (restriction.Major == major)
|
||||
{
|
||||
isFulfilled &= restriction.Minor <= minor;
|
||||
fulfilled &= restriction.Minor <= minor;
|
||||
|
||||
if (restriction.Minor == minor)
|
||||
{
|
||||
isFulfilled &= !restriction.Patch.HasValue || restriction.Patch <= patch;
|
||||
fulfilled &= !restriction.Patch.HasValue || restriction.Patch <= patch;
|
||||
|
||||
if (restriction.Patch == patch)
|
||||
{
|
||||
isFulfilled &= !restriction.Build.HasValue || restriction.Build <= build;
|
||||
fulfilled &= !restriction.Build.HasValue || restriction.Build <= build;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isFulfilled &= !restriction.RequiresAllianceEdition || isAllianceEdition;
|
||||
fulfilled &= !restriction.RequiresAllianceEdition || isAllianceEdition;
|
||||
}
|
||||
else
|
||||
{
|
||||
isFulfilled &= restriction.Major == major;
|
||||
isFulfilled &= restriction.Minor == minor;
|
||||
isFulfilled &= !restriction.Patch.HasValue || restriction.Patch == patch;
|
||||
isFulfilled &= !restriction.Build.HasValue || restriction.Build == build;
|
||||
isFulfilled &= !restriction.RequiresAllianceEdition || isAllianceEdition;
|
||||
fulfilled &= restriction.Major == major;
|
||||
fulfilled &= restriction.Minor == minor;
|
||||
fulfilled &= !restriction.Patch.HasValue || restriction.Patch == patch;
|
||||
fulfilled &= !restriction.Build.HasValue || restriction.Build == build;
|
||||
fulfilled &= !restriction.RequiresAllianceEdition || isAllianceEdition;
|
||||
}
|
||||
|
||||
return isFulfilled;
|
||||
return fulfilled;
|
||||
}
|
||||
|
||||
private (int major, int minor, int patch, int build, bool isAllianceEdition) GetVersion()
|
||||
@@ -134,7 +126,7 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
private string BuildRequiredVersions()
|
||||
{
|
||||
var info = new StringBuilder();
|
||||
var minimumVersionText = text.Get(TextKey.MessageBox_VersionRestrictionMinimum);
|
||||
var minimumVersionText = Text.Get(TextKey.MessageBox_VersionRestrictionMinimum);
|
||||
|
||||
info.AppendLine();
|
||||
info.AppendLine();
|
||||
@@ -160,5 +152,18 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
return info.ToString();
|
||||
}
|
||||
|
||||
private void ShowErrorMessage(string version)
|
||||
{
|
||||
var message = TextKey.MessageBox_VersionRestrictionError;
|
||||
var title = TextKey.MessageBox_VersionRestrictionErrorTitle;
|
||||
var placeholders = new Dictionary<string, string>()
|
||||
{
|
||||
{"%%_VERSION_%%", version },
|
||||
{ "%%_REQUIRED_VERSIONS_%%", BuildRequiredVersions() }
|
||||
};
|
||||
|
||||
ShowMessageBox(message, title, icon: MessageBoxIcon.Error, messagePlaceholders: placeholders);
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,26 +9,21 @@
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts;
|
||||
using SafeExamBrowser.Runtime.Operations.Events;
|
||||
using SafeExamBrowser.Settings.Security;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
namespace SafeExamBrowser.Runtime.Operations.Session
|
||||
{
|
||||
internal class VirtualMachineOperation : SessionOperation
|
||||
{
|
||||
private readonly IVirtualMachineDetector detector;
|
||||
private readonly ILogger logger;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired;
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public VirtualMachineOperation(IVirtualMachineDetector detector, ILogger logger, SessionContext context) : base(context)
|
||||
public VirtualMachineOperation(Dependencies dependencies, IVirtualMachineDetector detector) : base(dependencies)
|
||||
{
|
||||
this.detector = detector;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
@@ -48,25 +43,19 @@ namespace SafeExamBrowser.Runtime.Operations
|
||||
|
||||
private OperationResult ValidatePolicy()
|
||||
{
|
||||
logger.Info($"Validating virtual machine policy...");
|
||||
var result = OperationResult.Success;
|
||||
|
||||
Logger.Info($"Validating virtual machine policy...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_ValidateVirtualMachinePolicy);
|
||||
|
||||
if (Context.Next.Settings.Security.VirtualMachinePolicy == VirtualMachinePolicy.Deny && detector.IsVirtualMachine())
|
||||
{
|
||||
var args = new MessageEventArgs
|
||||
{
|
||||
Icon = MessageBoxIcon.Error,
|
||||
Message = TextKey.MessageBox_VirtualMachineNotAllowed,
|
||||
Title = TextKey.MessageBox_VirtualMachineNotAllowedTitle
|
||||
};
|
||||
|
||||
logger.Error("Detected virtual machine while SEB is not allowed to be run in a virtual machine! Aborting...");
|
||||
ActionRequired?.Invoke(args);
|
||||
|
||||
return OperationResult.Aborted;
|
||||
result = OperationResult.Aborted;
|
||||
Logger.Error("Detected virtual machine while SEB is not allowed to be run in a virtual machine! Aborting...");
|
||||
ShowMessageBox(TextKey.MessageBox_VirtualMachineNotAllowed, TextKey.MessageBox_VirtualMachineNotAllowedTitle, icon: MessageBoxIcon.Error);
|
||||
}
|
||||
|
||||
return OperationResult.Success;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
/// <summary>
|
||||
/// The base implementation to be used for all operations in the session operation sequence.
|
||||
/// </summary>
|
||||
internal abstract class SessionOperation : IRepeatableOperation
|
||||
{
|
||||
protected SessionContext Context { get; private set; }
|
||||
|
||||
public abstract event ActionRequiredEventHandler ActionRequired;
|
||||
public abstract event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public SessionOperation(SessionContext context)
|
||||
{
|
||||
Context = context;
|
||||
}
|
||||
|
||||
public abstract OperationResult Perform();
|
||||
public abstract OperationResult Repeat();
|
||||
public abstract OperationResult Revert();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user