Restore SEBPatch
This commit is contained in:
18
SafeExamBrowser.Runtime/App.config
Normal file
18
SafeExamBrowser.Runtime/App.config
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Win32.Registry" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Principal.Windows" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
91
SafeExamBrowser.Runtime/App.cs
Normal file
91
SafeExamBrowser.Runtime/App.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime
|
||||
{
|
||||
public class App : Application
|
||||
{
|
||||
private static readonly Mutex Mutex = new Mutex(true, AppConfig.RUNTIME_MUTEX_NAME);
|
||||
private readonly CompositionRoot instances = new CompositionRoot();
|
||||
|
||||
[STAThread]
|
||||
public static void Main()
|
||||
{
|
||||
try
|
||||
{
|
||||
StartApplication();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MessageBox.Show(e.Message + "\n\n" + e.StackTrace, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Mutex.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private static void StartApplication()
|
||||
{
|
||||
if (NoInstanceRunning())
|
||||
{
|
||||
new App().Run();
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("You can only run one instance of SEB at a time.", "Startup Not Allowed", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool NoInstanceRunning()
|
||||
{
|
||||
return Mutex.WaitOne(TimeSpan.Zero, true);
|
||||
}
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
|
||||
ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
||||
|
||||
instances.BuildObjectGraph(Shutdown);
|
||||
instances.LogStartupInformation();
|
||||
|
||||
Task.Run(new Action(TryStart));
|
||||
}
|
||||
|
||||
private void TryStart()
|
||||
{
|
||||
var success = instances.RuntimeController.TryStart();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public new void Shutdown()
|
||||
{
|
||||
Task.Run(new Action(ShutdownInternal));
|
||||
}
|
||||
|
||||
private void ShutdownInternal()
|
||||
{
|
||||
instances.RuntimeController.Terminate();
|
||||
instances.LogShutdownInformation();
|
||||
|
||||
Dispatcher.Invoke(base.Shutdown);
|
||||
}
|
||||
}
|
||||
}
|
109
SafeExamBrowser.Runtime/Communication/RuntimeHost.cs
Normal file
109
SafeExamBrowser.Runtime/Communication/RuntimeHost.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
using SafeExamBrowser.Communication.Contracts.Data;
|
||||
using SafeExamBrowser.Communication.Contracts.Events;
|
||||
using SafeExamBrowser.Communication.Contracts.Hosts;
|
||||
using SafeExamBrowser.Communication.Hosts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Communication
|
||||
{
|
||||
internal class RuntimeHost : BaseHost, IRuntimeHost
|
||||
{
|
||||
public bool AllowConnection { get; set; }
|
||||
public Guid? AuthenticationToken { private get; set; }
|
||||
|
||||
public event CommunicationEventHandler ClientDisconnected;
|
||||
public event CommunicationEventHandler ClientReady;
|
||||
public event CommunicationEventHandler<ClientConfigurationEventArgs> ClientConfigurationNeeded;
|
||||
public event CommunicationEventHandler<ExamSelectionReplyEventArgs> ExamSelectionReceived;
|
||||
public event CommunicationEventHandler<MessageBoxReplyEventArgs> MessageBoxReplyReceived;
|
||||
public event CommunicationEventHandler<PasswordReplyEventArgs> PasswordReceived;
|
||||
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationRequested;
|
||||
public event CommunicationEventHandler<ServerFailureActionReplyEventArgs> ServerFailureActionReceived;
|
||||
public event CommunicationEventHandler ShutdownRequested;
|
||||
|
||||
public RuntimeHost(string address, IHostObjectFactory factory, ILogger logger, int timeout_ms) : base(address, factory, logger, timeout_ms)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool OnConnect(Guid? token = null)
|
||||
{
|
||||
var authenticated = AuthenticationToken.HasValue && AuthenticationToken == token;
|
||||
var accepted = AllowConnection && authenticated;
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
AllowConnection = false;
|
||||
}
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
protected override void OnDisconnect(Interlocutor interlocutor)
|
||||
{
|
||||
if (interlocutor == Interlocutor.Client)
|
||||
{
|
||||
ClientDisconnected?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
protected override Response OnReceive(Message message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case ExamSelectionReplyMessage m:
|
||||
ExamSelectionReceived?.InvokeAsync(new ExamSelectionReplyEventArgs { RequestId = m.RequestId, SelectedExamId = m.SelectedExamId, Success = m.Success });
|
||||
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||
case MessageBoxReplyMessage m:
|
||||
MessageBoxReplyReceived?.InvokeAsync(new MessageBoxReplyEventArgs { RequestId = m.RequestId, Result = m.Result });
|
||||
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||
case PasswordReplyMessage m:
|
||||
PasswordReceived?.InvokeAsync(new PasswordReplyEventArgs { Password = m.Password, RequestId = m.RequestId, Success = m.Success });
|
||||
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||
case ReconfigurationMessage m:
|
||||
ReconfigurationRequested?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = m.ConfigurationPath, ResourceUrl = m.ResourceUrl });
|
||||
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||
case ServerFailureActionReplyMessage m:
|
||||
ServerFailureActionReceived?.InvokeAsync(new ServerFailureActionReplyEventArgs { Abort = m.Abort, Fallback = m.Fallback, RequestId = m.RequestId, Retry = m.Retry });
|
||||
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||
}
|
||||
|
||||
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
|
||||
}
|
||||
|
||||
protected override Response OnReceive(SimpleMessagePurport message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case SimpleMessagePurport.ClientIsReady:
|
||||
ClientReady?.Invoke();
|
||||
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||
case SimpleMessagePurport.ConfigurationNeeded:
|
||||
return HandleConfigurationRequest();
|
||||
case SimpleMessagePurport.RequestShutdown:
|
||||
ShutdownRequested?.Invoke();
|
||||
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
|
||||
}
|
||||
|
||||
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
|
||||
}
|
||||
|
||||
private Response HandleConfigurationRequest()
|
||||
{
|
||||
var args = new ClientConfigurationEventArgs();
|
||||
|
||||
ClientConfigurationNeeded?.Invoke(args);
|
||||
|
||||
return new ConfigurationResponse { Configuration = args.ClientConfiguration };
|
||||
}
|
||||
}
|
||||
}
|
208
SafeExamBrowser.Runtime/CompositionRoot.cs
Normal file
208
SafeExamBrowser.Runtime/CompositionRoot.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Communication.Contracts;
|
||||
using SafeExamBrowser.Communication.Hosts;
|
||||
using SafeExamBrowser.Communication.Proxies;
|
||||
using SafeExamBrowser.Configuration;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Cryptography;
|
||||
using SafeExamBrowser.Configuration.DataCompression;
|
||||
using SafeExamBrowser.Configuration.DataFormats;
|
||||
using SafeExamBrowser.Configuration.DataResources;
|
||||
using SafeExamBrowser.Configuration.Integrity;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.OperationModel;
|
||||
using SafeExamBrowser.Core.Operations;
|
||||
using SafeExamBrowser.I18n;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring;
|
||||
using SafeExamBrowser.Monitoring.Display;
|
||||
using SafeExamBrowser.Monitoring.System;
|
||||
using SafeExamBrowser.Runtime.Communication;
|
||||
using SafeExamBrowser.Runtime.Operations;
|
||||
using SafeExamBrowser.Server;
|
||||
using SafeExamBrowser.Settings.Logging;
|
||||
using SafeExamBrowser.SystemComponents;
|
||||
using SafeExamBrowser.SystemComponents.Contracts;
|
||||
using SafeExamBrowser.SystemComponents.Registry;
|
||||
using SafeExamBrowser.UserInterface.Desktop;
|
||||
using SafeExamBrowser.WindowsApi;
|
||||
using SafeExamBrowser.WindowsApi.Desktops;
|
||||
using SafeExamBrowser.WindowsApi.Processes;
|
||||
|
||||
namespace SafeExamBrowser.Runtime
|
||||
{
|
||||
internal class CompositionRoot
|
||||
{
|
||||
private AppConfig appConfig;
|
||||
private IConfigurationRepository configuration;
|
||||
private ILogger logger;
|
||||
private ISystemInfo systemInfo;
|
||||
private IText text;
|
||||
|
||||
internal RuntimeController RuntimeController { get; private set; }
|
||||
|
||||
internal void BuildObjectGraph(Action shutdown)
|
||||
{
|
||||
const int FIVE_SECONDS = 5000;
|
||||
const int THIRTY_SECONDS = 30000;
|
||||
|
||||
logger = new Logger();
|
||||
|
||||
InitializeConfiguration();
|
||||
InitializeLogging();
|
||||
InitializeText();
|
||||
|
||||
var nativeMethods = new NativeMethods();
|
||||
var registry = new Registry(ModuleLogger(nameof(Registry)));
|
||||
var uiFactory = new UserInterfaceFactory(text);
|
||||
var userInfo = new UserInfo(ModuleLogger(nameof(UserInfo)));
|
||||
|
||||
systemInfo = new SystemInfo(registry);
|
||||
|
||||
var args = Environment.GetCommandLineArgs();
|
||||
var integrityModule = new IntegrityModule(appConfig, ModuleLogger(nameof(IntegrityModule)));
|
||||
var desktopFactory = new DesktopFactory(ModuleLogger(nameof(DesktopFactory)));
|
||||
var desktopMonitor = new DesktopMonitor(ModuleLogger(nameof(DesktopMonitor)));
|
||||
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
||||
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
|
||||
var fileSystem = new FileSystem();
|
||||
var keyGenerator = new KeyGenerator(appConfig, integrityModule, ModuleLogger(nameof(KeyGenerator)));
|
||||
var messageBox = new MessageBoxFactory(text);
|
||||
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
|
||||
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), ModuleLogger(nameof(ProxyFactory)));
|
||||
var remoteSessionDetector = new RemoteSessionDetector(ModuleLogger(nameof(RemoteSessionDetector)));
|
||||
var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS);
|
||||
var runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig);
|
||||
var sentinel = new SystemSentinel(ModuleLogger(nameof(SystemSentinel)), nativeMethods, registry);
|
||||
var server = new ServerProxy(appConfig, keyGenerator, ModuleLogger(nameof(ServerProxy)), systemInfo, userInfo);
|
||||
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
|
||||
var sessionContext = new SessionContext();
|
||||
var splashScreen = uiFactory.CreateSplashScreen(appConfig);
|
||||
var vmDetector = new VirtualMachineDetector(ModuleLogger(nameof(VirtualMachineDetector)), registry, systemInfo);
|
||||
|
||||
var bootstrapOperations = new Queue<IOperation>();
|
||||
var sessionOperations = new Queue<IRepeatableOperation>();
|
||||
|
||||
bootstrapOperations.Enqueue(new I18nOperation(logger, text));
|
||||
bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger));
|
||||
bootstrapOperations.Enqueue(new ApplicationIntegrityOperation(integrityModule, logger));
|
||||
|
||||
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, fileSystem, logger, runtimeHost, sessionContext));
|
||||
sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, new FileSystem(), new HashAlgorithm(), logger, sessionContext));
|
||||
sessionOperations.Enqueue(new ServerOperation(args, configuration, fileSystem, logger, sessionContext, server));
|
||||
sessionOperations.Enqueue(new VersionRestrictionOperation(logger, sessionContext, text));
|
||||
sessionOperations.Enqueue(new DisclaimerOperation(logger, sessionContext));
|
||||
sessionOperations.Enqueue(new RemoteSessionOperation(remoteSessionDetector, logger, sessionContext));
|
||||
sessionOperations.Enqueue(new SessionIntegrityOperation(logger, sentinel, sessionContext));
|
||||
sessionOperations.Enqueue(new VirtualMachineOperation(vmDetector, logger, sessionContext));
|
||||
sessionOperations.Enqueue(new DisplayMonitorOperation(displayMonitor, logger, sessionContext, text));
|
||||
sessionOperations.Enqueue(new ServiceOperation(logger, runtimeHost, serviceProxy, sessionContext, THIRTY_SECONDS, userInfo));
|
||||
sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
|
||||
sessionOperations.Enqueue(new KioskModeOperation(desktopFactory, desktopMonitor, explorerShell, logger, processFactory, sessionContext));
|
||||
sessionOperations.Enqueue(new ClientOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS));
|
||||
sessionOperations.Enqueue(new SessionActivationOperation(logger, sessionContext));
|
||||
|
||||
var bootstrapSequence = new OperationSequence(logger, bootstrapOperations);
|
||||
var sessionSequence = new RepeatableOperationSequence(logger, sessionOperations);
|
||||
|
||||
RuntimeController = new RuntimeController(
|
||||
appConfig,
|
||||
logger,
|
||||
messageBox,
|
||||
bootstrapSequence,
|
||||
sessionSequence,
|
||||
runtimeHost,
|
||||
runtimeWindow,
|
||||
serviceProxy,
|
||||
sessionContext,
|
||||
shutdown,
|
||||
splashScreen,
|
||||
text,
|
||||
uiFactory);
|
||||
}
|
||||
|
||||
internal void LogStartupInformation()
|
||||
{
|
||||
logger.Log($"/* {appConfig.ProgramTitle}, Version {appConfig.ProgramInformationalVersion}, Build {appConfig.ProgramBuildVersion}");
|
||||
logger.Log($"/* {appConfig.ProgramCopyright}");
|
||||
logger.Log($"/* ");
|
||||
logger.Log($"/* Please visit https://www.github.com/SafeExamBrowser for more information.");
|
||||
logger.Log(string.Empty);
|
||||
logger.Log($"# Application started at {appConfig.ApplicationStartTime:yyyy-MM-dd HH:mm:ss.fff}");
|
||||
logger.Log($"# Running on {systemInfo.OperatingSystemInfo}");
|
||||
logger.Log($"# Computer '{systemInfo.Name}' is a {systemInfo.Model} manufactured by {systemInfo.Manufacturer}");
|
||||
logger.Log($"# Runtime-ID: {appConfig.RuntimeId}");
|
||||
logger.Log(string.Empty);
|
||||
}
|
||||
|
||||
internal void LogShutdownInformation()
|
||||
{
|
||||
logger?.Log($"# Application terminated at {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}");
|
||||
}
|
||||
|
||||
private void InitializeConfiguration()
|
||||
{
|
||||
var certificateStore = new CertificateStore(ModuleLogger(nameof(CertificateStore)));
|
||||
var compressor = new GZipCompressor(ModuleLogger(nameof(GZipCompressor)));
|
||||
var passwordEncryption = new PasswordEncryption(ModuleLogger(nameof(PasswordEncryption)));
|
||||
var publicKeyEncryption = new PublicKeyEncryption(certificateStore, ModuleLogger(nameof(PublicKeyEncryption)));
|
||||
var symmetricEncryption = new PublicKeySymmetricEncryption(certificateStore, ModuleLogger(nameof(PublicKeySymmetricEncryption)), passwordEncryption);
|
||||
var repositoryLogger = ModuleLogger(nameof(ConfigurationRepository));
|
||||
var xmlParser = new XmlParser(compressor, ModuleLogger(nameof(XmlParser)));
|
||||
var xmlSerializer = new XmlSerializer(ModuleLogger(nameof(XmlSerializer)));
|
||||
|
||||
configuration = new ConfigurationRepository(certificateStore, repositoryLogger);
|
||||
appConfig = configuration.InitializeAppConfig();
|
||||
|
||||
configuration.Register(new BinaryParser(
|
||||
compressor,
|
||||
new HashAlgorithm(),
|
||||
ModuleLogger(nameof(BinaryParser)),
|
||||
passwordEncryption,
|
||||
publicKeyEncryption,
|
||||
symmetricEncryption, xmlParser));
|
||||
configuration.Register(new BinarySerializer(
|
||||
compressor,
|
||||
ModuleLogger(nameof(BinarySerializer)),
|
||||
passwordEncryption,
|
||||
publicKeyEncryption,
|
||||
symmetricEncryption,
|
||||
xmlSerializer));
|
||||
configuration.Register(new XmlParser(compressor, ModuleLogger(nameof(XmlParser))));
|
||||
configuration.Register(new XmlSerializer(ModuleLogger(nameof(XmlSerializer))));
|
||||
configuration.Register(new FileResourceLoader(ModuleLogger(nameof(FileResourceLoader))));
|
||||
configuration.Register(new FileResourceSaver(ModuleLogger(nameof(FileResourceSaver))));
|
||||
configuration.Register(new NetworkResourceLoader(appConfig, new ModuleLogger(logger, nameof(NetworkResourceLoader))));
|
||||
}
|
||||
|
||||
private void InitializeLogging()
|
||||
{
|
||||
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), appConfig.RuntimeLogFilePath);
|
||||
|
||||
logFileWriter.Initialize();
|
||||
logger.LogLevel = LogLevel.Debug;
|
||||
logger.Subscribe(logFileWriter);
|
||||
}
|
||||
|
||||
private void InitializeText()
|
||||
{
|
||||
text = new Text(ModuleLogger(nameof(Text)));
|
||||
}
|
||||
|
||||
private IModuleLogger ModuleLogger(string moduleInfo)
|
||||
{
|
||||
return new ModuleLogger(logger, moduleInfo);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Configuration.Contracts.Integrity;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.I18n.Contracts;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.module = module;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public OperationResult Perform()
|
||||
{
|
||||
logger.Info($"Attempting to verify application integrity...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_VerifyApplicationIntegrity);
|
||||
|
||||
if (module.TryVerifyCodeSignature(out var isValid))
|
||||
{
|
||||
if (isValid)
|
||||
{
|
||||
logger.Info("Application integrity successfully verified.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Application integrity is compromised!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Failed to verify application integrity!");
|
||||
}
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
public OperationResult Revert()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
270
SafeExamBrowser.Runtime/Operations/ClientOperation.cs
Normal file
270
SafeExamBrowser.Runtime/Operations/ClientOperation.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Text;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Communication.Contracts;
|
||||
using SafeExamBrowser.Communication.Contracts.Events;
|
||||
using SafeExamBrowser.Communication.Contracts.Hosts;
|
||||
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
|
||||
{
|
||||
internal class ClientOperation : SessionOperation
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly IProcessFactory processFactory;
|
||||
private readonly IProxyFactory proxyFactory;
|
||||
private readonly IRuntimeHost runtimeHost;
|
||||
private readonly int timeout_ms;
|
||||
|
||||
private IProcess ClientProcess
|
||||
{
|
||||
get { return Context.ClientProcess; }
|
||||
set { Context.ClientProcess = value; }
|
||||
}
|
||||
|
||||
private IClientProxy ClientProxy
|
||||
{
|
||||
get { return Context.ClientProxy; }
|
||||
set { Context.ClientProxy = value; }
|
||||
}
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public ClientOperation(
|
||||
ILogger logger,
|
||||
IProcessFactory processFactory,
|
||||
IProxyFactory proxyFactory,
|
||||
IRuntimeHost runtimeHost,
|
||||
SessionContext sessionContext,
|
||||
int timeout_ms) : base(sessionContext)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.processFactory = processFactory;
|
||||
this.proxyFactory = proxyFactory;
|
||||
this.runtimeHost = runtimeHost;
|
||||
this.timeout_ms = timeout_ms;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_StartClient);
|
||||
|
||||
var success = TryStartClient();
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Successfully started new client instance.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to start new client instance! Aborting procedure...");
|
||||
}
|
||||
|
||||
return success ? OperationResult.Success : OperationResult.Failed;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
return Perform();
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
var success = true;
|
||||
|
||||
if (ClientProcess != null && !ClientProcess.HasTerminated)
|
||||
{
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_StopClient);
|
||||
success = TryStopClient();
|
||||
}
|
||||
|
||||
return success ? OperationResult.Success : OperationResult.Failed;
|
||||
}
|
||||
|
||||
private bool TryStartClient()
|
||||
{
|
||||
var authenticationToken = Context.Next.ClientAuthenticationToken.ToString("D");
|
||||
var executablePath = Context.Next.AppConfig.ClientExecutablePath;
|
||||
var logFilePath = $"{'"' + Convert.ToBase64String(Encoding.UTF8.GetBytes(Context.Next.AppConfig.ClientLogFilePath)) + '"'}";
|
||||
var logLevel = Context.Next.Settings.LogLevel.ToString();
|
||||
var runtimeHostUri = Context.Next.AppConfig.RuntimeAddress;
|
||||
var uiMode = Context.Next.Settings.UserInterface.Mode.ToString();
|
||||
|
||||
var clientReady = false;
|
||||
var clientReadyEvent = new AutoResetEvent(false);
|
||||
var clientReadyEventHandler = new CommunicationEventHandler(() => clientReadyEvent.Set());
|
||||
|
||||
var clientTerminated = false;
|
||||
var clientTerminatedEventHandler = new ProcessTerminatedEventHandler(_ => { clientTerminated = true; clientReadyEvent.Set(); });
|
||||
|
||||
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...");
|
||||
clientReady = clientReadyEvent.WaitOne();
|
||||
|
||||
runtimeHost.AllowConnection = false;
|
||||
runtimeHost.AuthenticationToken = default(Guid?);
|
||||
runtimeHost.ClientReady -= clientReadyEventHandler;
|
||||
ClientProcess.Terminated -= clientTerminatedEventHandler;
|
||||
|
||||
if (clientReady && !clientTerminated)
|
||||
{
|
||||
return TryStartCommunication();
|
||||
}
|
||||
|
||||
if (!clientReady)
|
||||
{
|
||||
logger.Error($"Failed to start client!");
|
||||
}
|
||||
|
||||
if (clientTerminated)
|
||||
{
|
||||
logger.Error("Client instance terminated unexpectedly during initialization!");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryStartCommunication()
|
||||
{
|
||||
var success = false;
|
||||
|
||||
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...");
|
||||
|
||||
var communication = ClientProxy.RequestAuthentication();
|
||||
var response = communication.Value;
|
||||
|
||||
success = communication.Success && ClientProcess.Id == response?.ProcessId;
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Authentication of client has been successful, client is ready to operate.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to verify client integrity!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to connect to client!");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TryStopClient()
|
||||
{
|
||||
var success = false;
|
||||
|
||||
var disconnected = false;
|
||||
var disconnectedEvent = new AutoResetEvent(false);
|
||||
var disconnectedEventHandler = new CommunicationEventHandler(() => disconnectedEvent.Set());
|
||||
|
||||
var terminated = false;
|
||||
var terminatedEvent = new AutoResetEvent(false);
|
||||
var terminatedEventHandler = new ProcessTerminatedEventHandler((_) => terminatedEvent.Set());
|
||||
|
||||
if (ClientProxy != null)
|
||||
{
|
||||
runtimeHost.ClientDisconnected += disconnectedEventHandler;
|
||||
ClientProcess.Terminated += terminatedEventHandler;
|
||||
|
||||
logger.Info("Instructing client to initiate shutdown procedure.");
|
||||
ClientProxy.InitiateShutdown();
|
||||
|
||||
logger.Info("Disconnecting from client communication host.");
|
||||
ClientProxy.Disconnect();
|
||||
|
||||
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.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!");
|
||||
}
|
||||
|
||||
runtimeHost.ClientDisconnected -= disconnectedEventHandler;
|
||||
ClientProcess.Terminated -= terminatedEventHandler;
|
||||
}
|
||||
|
||||
if (disconnected && terminated)
|
||||
{
|
||||
logger.Info("Client has been successfully terminated.");
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Attempting to kill client process since graceful termination failed!");
|
||||
success = TryKillClient();
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
ClientProcess = null;
|
||||
ClientProxy = null;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TryKillClient()
|
||||
{
|
||||
const int MAX_ATTEMPTS = 5;
|
||||
|
||||
for (var attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)
|
||||
{
|
||||
logger.Info($"Attempt {attempt}/{MAX_ATTEMPTS} to kill client process with ID = {ClientProcess.Id}.");
|
||||
|
||||
if (ClientProcess.TryKill(500))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ClientProcess.HasTerminated)
|
||||
{
|
||||
logger.Info("Client process has terminated.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to kill client process within {MAX_ATTEMPTS} attempts!");
|
||||
}
|
||||
|
||||
return ClientProcess.HasTerminated;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Hosts;
|
||||
using SafeExamBrowser.Communication.Contracts.Proxies;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
internal class ClientTerminationOperation : ClientOperation
|
||||
{
|
||||
public ClientTerminationOperation(
|
||||
ILogger logger,
|
||||
IProcessFactory processFactory,
|
||||
IProxyFactory proxyFactory,
|
||||
IRuntimeHost runtimeHost,
|
||||
SessionContext sessionContext,
|
||||
int timeout_ms) : base(logger, processFactory, proxyFactory, runtimeHost, sessionContext, timeout_ms)
|
||||
{
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
return base.Revert();
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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
|
||||
}
|
||||
}
|
||||
}
|
452
SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs
Normal file
452
SafeExamBrowser.Runtime/Operations/ConfigurationOperation.cs
Normal file
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.IO;
|
||||
using SafeExamBrowser.Communication.Contracts.Data;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
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;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
internal class ConfigurationOperation : ConfigurationBaseOperation
|
||||
{
|
||||
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,
|
||||
IFileSystem fileSystem,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
ILogger logger,
|
||||
SessionContext sessionContext) : base(commandLineArgs, configuration, sessionContext)
|
||||
{
|
||||
this.fileSystem = fileSystem;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
logger.Info("Initializing application configuration...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration);
|
||||
|
||||
var result = OperationResult.Failed;
|
||||
var isValidUri = TryInitializeSettingsUri(out var uri, out var source);
|
||||
|
||||
if (isValidUri)
|
||||
{
|
||||
result = LoadSettingsForStartup(uri, source);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = LoadDefaultSettings();
|
||||
}
|
||||
|
||||
LogOperationResult(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
logger.Info("Initializing new application configuration...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration);
|
||||
|
||||
var result = OperationResult.Failed;
|
||||
var isValidUri = TryValidateSettingsUri(Context.ReconfigurationFilePath, out var uri);
|
||||
|
||||
if (isValidUri)
|
||||
{
|
||||
result = LoadSettingsForReconfiguration(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"The resource specified for reconfiguration does not exist or is not valid!");
|
||||
}
|
||||
|
||||
LogOperationResult(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
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();
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private OperationResult LoadSettingsForStartup(Uri uri, UriSource source)
|
||||
{
|
||||
var currentPassword = default(string);
|
||||
var passwordParams = default(PasswordParameters);
|
||||
var settings = default(AppSettings);
|
||||
var status = default(LoadStatus?);
|
||||
|
||||
if (source == UriSource.CommandLine)
|
||||
{
|
||||
var hasAppDataFile = File.Exists(AppDataFilePath);
|
||||
var hasProgramDataFile = File.Exists(ProgramDataFilePath);
|
||||
|
||||
if (hasProgramDataFile)
|
||||
{
|
||||
status = TryLoadSettings(new Uri(ProgramDataFilePath, UriKind.Absolute), UriSource.ProgramData, out _, out settings);
|
||||
}
|
||||
else if (hasAppDataFile)
|
||||
{
|
||||
status = TryLoadSettings(new Uri(AppDataFilePath, UriKind.Absolute), UriSource.AppData, out _, out settings);
|
||||
}
|
||||
|
||||
if ((!hasProgramDataFile && !hasAppDataFile) || status == LoadStatus.Success)
|
||||
{
|
||||
currentPassword = settings?.Security.AdminPasswordHash;
|
||||
status = TryLoadSettings(uri, source, out passwordParams, out settings, currentPassword);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status = TryLoadSettings(uri, source, out passwordParams, out settings);
|
||||
}
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
return DetermineLoadResult(uri, source, settings, status.Value, passwordParams, currentPassword);
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperationResult.Aborted;
|
||||
}
|
||||
}
|
||||
|
||||
private OperationResult LoadSettingsForReconfiguration(Uri uri)
|
||||
{
|
||||
var currentPassword = Context.Current.Settings.Security.AdminPasswordHash;
|
||||
var source = UriSource.Reconfiguration;
|
||||
var status = TryLoadSettings(uri, source, out var passwordParams, out var settings, currentPassword);
|
||||
var result = OperationResult.Failed;
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
result = DetermineLoadResult(uri, source, settings, status.Value, passwordParams, currentPassword);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Aborted;
|
||||
}
|
||||
|
||||
if (result == OperationResult.Success && Context.Current.IsBrowserResource)
|
||||
{
|
||||
HandleReconfigurationByBrowserResource();
|
||||
}
|
||||
|
||||
fileSystem.Delete(uri.LocalPath);
|
||||
logger.Info($"Deleted temporary configuration file '{uri}'.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private OperationResult DetermineLoadResult(Uri uri, UriSource source, AppSettings settings, LoadStatus status, PasswordParameters passwordParams, string currentPassword = default)
|
||||
{
|
||||
var result = OperationResult.Failed;
|
||||
|
||||
if (status == LoadStatus.LoadWithBrowser || status == LoadStatus.Success)
|
||||
{
|
||||
var isNewConfiguration = source == UriSource.CommandLine || source == UriSource.Reconfiguration;
|
||||
|
||||
Context.Next.Settings = settings;
|
||||
|
||||
if (status == LoadStatus.LoadWithBrowser)
|
||||
{
|
||||
result = HandleBrowserResource(uri);
|
||||
}
|
||||
else if (isNewConfiguration && settings.ConfigurationMode == ConfigurationMode.ConfigureClient)
|
||||
{
|
||||
result = HandleClientConfiguration(uri, passwordParams, currentPassword);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Success;
|
||||
}
|
||||
|
||||
HandleStartUrlQuery(uri, source);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowFailureMessage(status, uri);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private OperationResult HandleBrowserResource(Uri uri)
|
||||
{
|
||||
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.Security.AllowReconfiguration = true;
|
||||
Context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Allow;
|
||||
Context.Next.Settings.Service.IgnoreService = true;
|
||||
|
||||
logger.Info($"The configuration resource needs authentication or is a webpage, using '{uri}' as start URL for the browser.");
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private OperationResult HandleClientConfiguration(Uri uri, PasswordParameters passwordParams, string currentPassword = default)
|
||||
{
|
||||
var isFirstSession = Context.Current == null;
|
||||
var success = TryConfigureClient(uri, passwordParams, currentPassword);
|
||||
var result = OperationResult.Failed;
|
||||
|
||||
if (!success.HasValue || (success == true && isFirstSession && AbortAfterClientConfiguration()))
|
||||
{
|
||||
result = OperationResult.Aborted;
|
||||
}
|
||||
else if (success == true)
|
||||
{
|
||||
result = OperationResult.Success;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
private void HandleStartUrlQuery(Uri uri, UriSource source)
|
||||
{
|
||||
if (source == UriSource.Reconfiguration && Uri.TryCreate(Context.ReconfigurationUrl, UriKind.Absolute, out var reconfigurationUri))
|
||||
{
|
||||
uri = reconfigurationUri;
|
||||
}
|
||||
|
||||
if (uri != default && uri.Query.LastIndexOf('?') > 0)
|
||||
{
|
||||
Context.Next.Settings.Browser.StartUrlQuery = uri.Query.Substring(uri.Query.LastIndexOf('?'));
|
||||
}
|
||||
}
|
||||
|
||||
private bool? TryConfigureClient(Uri uri, PasswordParameters passwordParams, string currentPassword = default)
|
||||
{
|
||||
var mustAuthenticate = IsRequiredToAuthenticateForClientConfiguration(passwordParams, currentPassword);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info("Authentication is not required.");
|
||||
}
|
||||
|
||||
var status = configuration.ConfigureClientWith(uri, passwordParams);
|
||||
var success = status == SaveStatus.Success;
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Client configuration was successful.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Client configuration failed with status '{status}'!");
|
||||
ActionRequired?.Invoke(new ClientConfigurationErrorMessageArgs());
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool IsRequiredToAuthenticateForClientConfiguration(PasswordParameters passwordParams, string currentPassword = default)
|
||||
{
|
||||
var mustAuthenticate = currentPassword != default;
|
||||
|
||||
if (mustAuthenticate)
|
||||
{
|
||||
var nextPassword = Context.Next.Settings.Security.AdminPasswordHash;
|
||||
var hasSettingsPassword = passwordParams.Password != null;
|
||||
var sameAdminPassword = currentPassword.Equals(nextPassword, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (sameAdminPassword)
|
||||
{
|
||||
mustAuthenticate = false;
|
||||
}
|
||||
else if (hasSettingsPassword)
|
||||
{
|
||||
var settingsPassword = passwordParams.IsHash ? passwordParams.Password : hashAlgorithm.GenerateHashFor(passwordParams.Password);
|
||||
var knowsAdminPassword = currentPassword.Equals(settingsPassword, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
mustAuthenticate = !knowsAdminPassword;
|
||||
}
|
||||
}
|
||||
|
||||
return mustAuthenticate;
|
||||
}
|
||||
|
||||
private bool? AuthenticateForClientConfiguration(string currentPassword)
|
||||
{
|
||||
var authenticated = false;
|
||||
|
||||
for (var attempts = 0; attempts < 5 && !authenticated; attempts++)
|
||||
{
|
||||
var success = TryGetPassword(PasswordRequestPurpose.LocalAdministrator, out var password);
|
||||
|
||||
if (success)
|
||||
{
|
||||
authenticated = currentPassword.Equals(hashAlgorithm.GenerateHashFor(password), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
private bool AbortAfterClientConfiguration()
|
||||
{
|
||||
var args = new ConfigurationCompletedEventArgs();
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} startup after successful client configuration.");
|
||||
|
||||
return args.AbortStartup;
|
||||
}
|
||||
|
||||
private void ShowFailureMessage(LoadStatus status, Uri uri)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case LoadStatus.PasswordNeeded:
|
||||
ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
|
||||
break;
|
||||
case LoadStatus.InvalidData:
|
||||
ActionRequired?.Invoke(new InvalidDataMessageArgs(uri.ToString()));
|
||||
break;
|
||||
case LoadStatus.NotSupported:
|
||||
ActionRequired?.Invoke(new NotSupportedMessageArgs(uri.ToString()));
|
||||
break;
|
||||
case LoadStatus.UnexpectedError:
|
||||
ActionRequired?.Invoke(new UnexpectedErrorMessageArgs(uri.ToString()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryInitializeSettingsUri(out Uri uri, out UriSource source)
|
||||
{
|
||||
var isValidUri = false;
|
||||
|
||||
uri = default;
|
||||
source = default;
|
||||
|
||||
if (commandLineArgs?.Length > 1)
|
||||
{
|
||||
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")}.");
|
||||
}
|
||||
|
||||
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}'.");
|
||||
}
|
||||
|
||||
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}'.");
|
||||
}
|
||||
|
||||
return isValidUri;
|
||||
}
|
||||
|
||||
private bool TryValidateSettingsUri(string path, out Uri uri)
|
||||
{
|
||||
var isValidUri = Uri.TryCreate(path, UriKind.Absolute, out uri);
|
||||
|
||||
isValidUri &= uri != null && uri.IsFile;
|
||||
isValidUri &= File.Exists(path);
|
||||
|
||||
return isValidUri;
|
||||
}
|
||||
|
||||
private void LogOperationResult(OperationResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case OperationResult.Aborted:
|
||||
logger.Info("The configuration was aborted by the user.");
|
||||
break;
|
||||
case OperationResult.Failed:
|
||||
logger.Warn("The configuration has failed!");
|
||||
break;
|
||||
case OperationResult.Success:
|
||||
logger.Info("The configuration was successful.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
86
SafeExamBrowser.Runtime/Operations/DisclaimerOperation.cs
Normal file
86
SafeExamBrowser.Runtime/Operations/DisclaimerOperation.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Runtime.Operations.Events;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
if (Context.Next.Settings.Proctoring.ScreenProctoring.Enabled)
|
||||
{
|
||||
result = ShowScreenProctoringDisclaimer();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
if (Context.Next.Settings.Proctoring.ScreenProctoring.Enabled)
|
||||
{
|
||||
result = ShowScreenProctoringDisclaimer();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
logger.Info("The user confirmed the screen proctoring disclaimer.");
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("The user did not confirm the screen proctoring disclaimer! Aborting session initialization...");
|
||||
|
||||
return OperationResult.Aborted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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>();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
183
SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs
Normal file
183
SafeExamBrowser.Runtime/Operations/KioskModeOperation.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Settings.Security;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
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(
|
||||
IDesktopFactory desktopFactory,
|
||||
IDesktopMonitor desktopMonitor,
|
||||
IExplorerShell explorerShell,
|
||||
ILogger logger,
|
||||
IProcessFactory processFactory,
|
||||
SessionContext sessionContext) : base(sessionContext)
|
||||
{
|
||||
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}'...");
|
||||
/*
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeKioskMode);
|
||||
|
||||
activeMode = Context.Next.Settings.Security.KioskMode;
|
||||
|
||||
switch (Context.Next.Settings.Security.KioskMode)
|
||||
{
|
||||
case KioskMode.CreateNewDesktop:
|
||||
CreateCustomDesktop();
|
||||
break;
|
||||
case KioskMode.DisableExplorerShell:
|
||||
TerminateExplorerShell();
|
||||
break;
|
||||
}
|
||||
*/
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
var newMode = Context.Next.Settings.Security.KioskMode;
|
||||
|
||||
if (activeMode == newMode)
|
||||
{
|
||||
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}'...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeKioskMode);
|
||||
|
||||
switch (activeMode)
|
||||
{
|
||||
case KioskMode.CreateNewDesktop:
|
||||
CloseCustomDesktop();
|
||||
break;
|
||||
case KioskMode.DisableExplorerShell:
|
||||
RestartExplorerShell();
|
||||
break;
|
||||
}
|
||||
|
||||
activeMode = newMode;
|
||||
|
||||
switch (newMode)
|
||||
{
|
||||
case KioskMode.CreateNewDesktop:
|
||||
CreateCustomDesktop();
|
||||
break;
|
||||
case KioskMode.DisableExplorerShell:
|
||||
TerminateExplorerShell();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
logger.Info($"Reverting kiosk mode '{activeMode}'...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_RevertKioskMode);
|
||||
|
||||
switch (activeMode)
|
||||
{
|
||||
case KioskMode.CreateNewDesktop:
|
||||
CloseCustomDesktop();
|
||||
break;
|
||||
case KioskMode.DisableExplorerShell:
|
||||
RestartExplorerShell();
|
||||
break;
|
||||
}
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private void CreateCustomDesktop()
|
||||
{
|
||||
originalDesktop = desktopFactory.GetCurrent();
|
||||
logger.Info($"Current desktop is {originalDesktop}.");
|
||||
|
||||
customDesktop = desktopFactory.CreateRandom();
|
||||
logger.Info($"Created custom desktop {customDesktop}.");
|
||||
|
||||
customDesktop.Activate();
|
||||
processFactory.StartupDesktop = customDesktop;
|
||||
logger.Info("Successfully activated custom desktop.");
|
||||
|
||||
desktopMonitor.Start(customDesktop);
|
||||
}
|
||||
|
||||
private void CloseCustomDesktop()
|
||||
{
|
||||
desktopMonitor.Stop();
|
||||
|
||||
if (originalDesktop != default)
|
||||
{
|
||||
originalDesktop.Activate();
|
||||
processFactory.StartupDesktop = originalDesktop;
|
||||
logger.Info($"Switched back to original desktop {originalDesktop}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"No original desktop found to activate!");
|
||||
}
|
||||
|
||||
if (customDesktop != default)
|
||||
{
|
||||
customDesktop.Close();
|
||||
logger.Info($"Closed custom desktop {customDesktop}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"No custom desktop found to close!");
|
||||
}
|
||||
}
|
||||
|
||||
private void TerminateExplorerShell()
|
||||
{
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerTermination);
|
||||
|
||||
explorerShell.HideAllWindows();
|
||||
explorerShell.Terminate();
|
||||
}
|
||||
|
||||
private void RestartExplorerShell()
|
||||
{
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerStartup);
|
||||
|
||||
explorerShell.Start();
|
||||
explorerShell.RestoreAllWindows();
|
||||
}
|
||||
}
|
||||
}
|
71
SafeExamBrowser.Runtime/Operations/RemoteSessionOperation.cs
Normal file
71
SafeExamBrowser.Runtime/Operations/RemoteSessionOperation.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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;
|
||||
}
|
||||
}
|
||||
}
|
292
SafeExamBrowser.Runtime/Operations/ServerOperation.cs
Normal file
292
SafeExamBrowser.Runtime/Operations/ServerOperation.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 System.Linq;
|
||||
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;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
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,
|
||||
IFileSystem fileSystem,
|
||||
ILogger logger,
|
||||
SessionContext context,
|
||||
IServerProxy server) : base(commandLineArgs, configuration, context)
|
||||
{
|
||||
this.fileSystem = fileSystem;
|
||||
this.logger = logger;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
if (Context.Next.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
var browserExamKey = default(string);
|
||||
var exam = default(Exam);
|
||||
var exams = default(IEnumerable<Exam>);
|
||||
var uri = default(Uri);
|
||||
|
||||
logger.Info("Initializing server...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer);
|
||||
|
||||
server.Initialize(Context.Next.Settings.Server);
|
||||
|
||||
var (abort, fallback, success) = TryPerformWithFallback(() => server.Connect());
|
||||
|
||||
if (success)
|
||||
{
|
||||
(abort, fallback, success) = TryPerformWithFallback(() => server.GetAvailableExams(Context.Next.Settings.Server.ExamId), out exams);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
success = TrySelectExam(exams, out exam);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
(abort, fallback, success) = TryPerformWithFallback(() => server.GetConfigurationFor(exam), out uri);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
result = TryLoadServerSettings(exam, uri);
|
||||
}
|
||||
|
||||
if (success && result == OperationResult.Success)
|
||||
{
|
||||
(abort, fallback, success) = TryPerformWithFallback(() => server.SendSelectedExam(exam), out browserExamKey);
|
||||
}
|
||||
|
||||
if (browserExamKey != default)
|
||||
{
|
||||
Context.Next.Settings.Browser.CustomBrowserExamKey = browserExamKey;
|
||||
}
|
||||
|
||||
if (abort)
|
||||
{
|
||||
result = OperationResult.Aborted;
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
if (Context.Current.Settings.SessionMode == SessionMode.Server && Context.Next.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
result = AbortServerReconfiguration();
|
||||
}
|
||||
else if (Context.Current.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
result = Revert();
|
||||
}
|
||||
else if (Context.Next.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
result = Perform();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
if (Context.Current?.Settings.SessionMode == SessionMode.Server || Context.Next?.Settings.SessionMode == SessionMode.Server)
|
||||
{
|
||||
logger.Info("Finalizing server...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServer);
|
||||
|
||||
var disconnect = server.Disconnect();
|
||||
|
||||
if (disconnect.Success)
|
||||
{
|
||||
result = OperationResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override void InvokeActionRequired(ActionRequiredEventArgs args)
|
||||
{
|
||||
ActionRequired?.Invoke(args);
|
||||
}
|
||||
|
||||
private OperationResult TryLoadServerSettings(Exam exam, Uri uri)
|
||||
{
|
||||
var info = server.GetConnectionInfo();
|
||||
var result = OperationResult.Failed;
|
||||
var status = TryLoadSettings(uri, UriSource.Server, out _, out var settings);
|
||||
|
||||
fileSystem.Delete(uri.LocalPath);
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
{
|
||||
var browserSettings = Context.Next.Settings.Browser;
|
||||
var serverSettings = Context.Next.Settings.Server;
|
||||
|
||||
Context.Next.AppConfig.ServerApi = info.Api;
|
||||
Context.Next.AppConfig.ServerConnectionToken = info.ConnectionToken;
|
||||
Context.Next.AppConfig.ServerExamId = exam.Id;
|
||||
Context.Next.AppConfig.ServerOauth2Token = info.Oauth2Token;
|
||||
|
||||
Context.Next.Settings = settings;
|
||||
Context.Next.Settings.Browser.StartUrl = exam.Url;
|
||||
Context.Next.Settings.Browser.StartUrlQuery = browserSettings.StartUrlQuery;
|
||||
Context.Next.Settings.Server = serverSettings;
|
||||
Context.Next.Settings.SessionMode = SessionMode.Server;
|
||||
|
||||
result = OperationResult.Success;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private (bool abort, bool fallback, bool success) TryPerformWithFallback(Func<ServerResponse> request)
|
||||
{
|
||||
var abort = false;
|
||||
var fallback = false;
|
||||
var success = false;
|
||||
|
||||
while (!success)
|
||||
{
|
||||
var response = request();
|
||||
|
||||
success = response.Success;
|
||||
|
||||
if (!success && !Retry(response.Message, out abort, out fallback))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (abort, fallback, success);
|
||||
}
|
||||
|
||||
private (bool abort, bool fallback, bool success) TryPerformWithFallback<T>(Func<ServerResponse<T>> request, out T value)
|
||||
{
|
||||
var abort = false;
|
||||
var fallback = false;
|
||||
var success = false;
|
||||
|
||||
value = default;
|
||||
|
||||
while (!success)
|
||||
{
|
||||
var response = request();
|
||||
|
||||
success = response.Success;
|
||||
value = response.Value;
|
||||
|
||||
if (!success && !Retry(response.Message, out abort, out fallback))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (abort, fallback, success);
|
||||
}
|
||||
|
||||
private bool Retry(string message, out bool abort, out bool fallback)
|
||||
{
|
||||
var args = new ServerFailureEventArgs(message, Context.Next.Settings.Server.PerformFallback);
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
|
||||
abort = args.Abort;
|
||||
fallback = args.Fallback;
|
||||
|
||||
if (args.Retry)
|
||||
{
|
||||
logger.Debug("The user chose to retry the current server request.");
|
||||
}
|
||||
|
||||
return args.Retry;
|
||||
}
|
||||
|
||||
private bool TrySelectExam(IEnumerable<Exam> exams, out Exam exam)
|
||||
{
|
||||
var success = true;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Context.Next.Settings.Server.ExamId))
|
||||
{
|
||||
var args = new ExamSelectionEventArgs(exams);
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
|
||||
exam = args.SelectedExam;
|
||||
success = args.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
exam = exams.First();
|
||||
logger.Info("Automatically selected exam as defined in configuration.");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private OperationResult AbortServerReconfiguration()
|
||||
{
|
||||
var args = new MessageEventArgs
|
||||
{
|
||||
Action = MessageBoxAction.Ok,
|
||||
Icon = MessageBoxIcon.Warning,
|
||||
Message = TextKey.MessageBox_ServerReconfigurationWarning,
|
||||
Title = TextKey.MessageBox_ServerReconfigurationWarningTitle
|
||||
};
|
||||
|
||||
logger.Warn("Server reconfiguration is currently not supported, aborting...");
|
||||
ActionRequired?.Invoke(args);
|
||||
|
||||
return OperationResult.Aborted;
|
||||
}
|
||||
}
|
||||
}
|
293
SafeExamBrowser.Runtime/Operations/ServiceOperation.cs
Normal file
293
SafeExamBrowser.Runtime/Operations/ServiceOperation.cs
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Security.AccessControl;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Communication.Contracts.Hosts;
|
||||
using SafeExamBrowser.Communication.Contracts.Proxies;
|
||||
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
|
||||
{
|
||||
internal class ServiceOperation : SessionOperation
|
||||
{
|
||||
private ILogger logger;
|
||||
private IRuntimeHost runtimeHost;
|
||||
private IServiceProxy service;
|
||||
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,
|
||||
IRuntimeHost runtimeHost,
|
||||
IServiceProxy service,
|
||||
SessionContext sessionContext,
|
||||
int timeout_ms,
|
||||
IUserInfo userInfo) : base(sessionContext)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.runtimeHost = runtimeHost;
|
||||
this.service = service;
|
||||
this.timeout_ms = timeout_ms;
|
||||
this.userInfo = userInfo;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
logger.Info($"Initializing service...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
|
||||
|
||||
var success = IgnoreService() || TryInitializeConnection();
|
||||
|
||||
if (success && service.IsConnected)
|
||||
{
|
||||
success = TryStartSession();
|
||||
}
|
||||
|
||||
return success ? OperationResult.Success : OperationResult.Failed;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
logger.Info($"Initializing service...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServiceSession);
|
||||
|
||||
var success = true;
|
||||
|
||||
if (service.IsConnected)
|
||||
{
|
||||
if (sessionId.HasValue)
|
||||
{
|
||||
success = TryStopSession();
|
||||
}
|
||||
|
||||
if (success && IgnoreService())
|
||||
{
|
||||
success = TryTerminateConnection();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = IgnoreService() || TryInitializeConnection();
|
||||
}
|
||||
|
||||
if (success && service.IsConnected)
|
||||
{
|
||||
success = TryStartSession();
|
||||
}
|
||||
|
||||
return success ? OperationResult.Success : OperationResult.Failed;
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
logger.Info("Finalizing service...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServiceSession);
|
||||
|
||||
var success = true;
|
||||
|
||||
if (service.IsConnected)
|
||||
{
|
||||
if (sessionId.HasValue)
|
||||
{
|
||||
success = TryStopSession(true);
|
||||
}
|
||||
|
||||
success &= TryTerminateConnection();
|
||||
}
|
||||
|
||||
return success ? OperationResult.Success : OperationResult.Failed;
|
||||
}
|
||||
|
||||
private bool IgnoreService()
|
||||
{
|
||||
if (Context.Next.Settings.Service.IgnoreService)
|
||||
{
|
||||
logger.Info("The service will be ignored for the next session.");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryInitializeConnection()
|
||||
{
|
||||
var mandatory = Context.Next.Settings.Service.Policy == ServicePolicy.Mandatory;
|
||||
var warn = Context.Next.Settings.Service.Policy == ServicePolicy.Warn;
|
||||
var connected = service.Connect();
|
||||
var success = connected || !mandatory;
|
||||
|
||||
if (success)
|
||||
{
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TryTerminateConnection()
|
||||
{
|
||||
var disconnected = service.Disconnect();
|
||||
|
||||
if (disconnected)
|
||||
{
|
||||
logger.Info("Successfully disconnected from service.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to disconnect from service!");
|
||||
}
|
||||
|
||||
return disconnected;
|
||||
}
|
||||
|
||||
private bool TryStartSession()
|
||||
{
|
||||
var configuration = new ServiceConfiguration
|
||||
{
|
||||
AppConfig = Context.Next.AppConfig,
|
||||
SessionId = Context.Next.SessionId,
|
||||
Settings = Context.Next.Settings,
|
||||
UserName = userInfo.GetUserName(),
|
||||
UserSid = userInfo.GetUserSid()
|
||||
};
|
||||
var started = false;
|
||||
|
||||
logger.Info("Starting new service session...");
|
||||
|
||||
var communication = service.StartSession(configuration);
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
started = TryWaitForServiceEvent(Context.Next.AppConfig.ServiceEventName);
|
||||
|
||||
if (started)
|
||||
{
|
||||
sessionId = Context.Next.SessionId;
|
||||
serviceEventName = Context.Next.AppConfig.ServiceEventName;
|
||||
logger.Info("Successfully started new service session.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to start new service session within {timeout_ms / 1000} seconds!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to communicate session start command to service!");
|
||||
}
|
||||
|
||||
return started;
|
||||
}
|
||||
|
||||
private bool TryStopSession(bool isFinalSession = false)
|
||||
{
|
||||
var success = false;
|
||||
|
||||
logger.Info("Stopping current service session...");
|
||||
|
||||
var communication = service.StopSession(sessionId.Value);
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
success = TryWaitForServiceEvent(serviceEventName);
|
||||
|
||||
if (success)
|
||||
{
|
||||
sessionId = default(Guid?);
|
||||
serviceEventName = default(string);
|
||||
logger.Info("Successfully stopped service session.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Failed to stop service session within {timeout_ms / 1000} seconds!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to communicate session stop command to service!");
|
||||
}
|
||||
|
||||
if (success && isFinalSession)
|
||||
{
|
||||
communication = service.RunSystemConfigurationUpdate();
|
||||
success = communication.Success;
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
logger.Info("Instructed service to perform system configuration update.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to communicate system configuration update command to service!");
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TryWaitForServiceEvent(string eventName)
|
||||
{
|
||||
var serviceEvent = default(EventWaitHandle);
|
||||
var startTime = DateTime.Now;
|
||||
|
||||
do
|
||||
{
|
||||
if (EventWaitHandle.TryOpenExisting(eventName, EventWaitHandleRights.Synchronize, out serviceEvent))
|
||||
{
|
||||
break;
|
||||
}
|
||||
} while (startTime.AddMilliseconds(timeout_ms) > DateTime.Now);
|
||||
|
||||
if (serviceEvent != default(EventWaitHandle))
|
||||
{
|
||||
using (serviceEvent)
|
||||
{
|
||||
return serviceEvent.WaitOne(timeout_ms);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
SwitchLogSeverity();
|
||||
ActivateNewSession();
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
SwitchLogSeverity();
|
||||
ActivateNewSession();
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private void SwitchLogSeverity()
|
||||
{
|
||||
if (logger.LogLevel != Context.Next.Settings.LogLevel)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivateNewSession()
|
||||
{
|
||||
var isFirstSession = Context.Current == null;
|
||||
|
||||
if (isFirstSession)
|
||||
{
|
||||
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}'.");
|
||||
}
|
||||
|
||||
Context.Current = Context.Next;
|
||||
Context.Next = null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.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
|
||||
{
|
||||
internal class SessionInitializationOperation : SessionOperation
|
||||
{
|
||||
private IConfigurationRepository configuration;
|
||||
private IFileSystem fileSystem;
|
||||
private ILogger logger;
|
||||
private IRuntimeHost runtimeHost;
|
||||
|
||||
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
|
||||
public override event StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public SessionInitializationOperation(
|
||||
IConfigurationRepository configuration,
|
||||
IFileSystem fileSystem,
|
||||
ILogger logger,
|
||||
IRuntimeHost runtimeHost,
|
||||
SessionContext sessionContext) : base(sessionContext)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.fileSystem = fileSystem;
|
||||
this.logger = logger;
|
||||
this.runtimeHost = runtimeHost;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
InitializeSessionConfiguration();
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
InitializeSessionConfiguration();
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
FinalizeSessionConfiguration();
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private void InitializeSessionConfiguration()
|
||||
{
|
||||
logger.Info("Initializing new session configuration...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeSession);
|
||||
|
||||
Context.Next = configuration.InitializeSessionConfiguration();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void FinalizeSessionConfiguration()
|
||||
{
|
||||
Context.Current = null;
|
||||
Context.Next = null;
|
||||
}
|
||||
}
|
||||
}
|
136
SafeExamBrowser.Runtime/Operations/SessionIntegrityOperation.cs
Normal file
136
SafeExamBrowser.Runtime/Operations/SessionIntegrityOperation.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.System;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.sentinel = sentinel;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
var success = true;
|
||||
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_VerifySessionIntegrity);
|
||||
|
||||
success &= InitializeStickyKeys();
|
||||
success &= VerifyCursors();
|
||||
success &= VerifyEaseOfAccess();
|
||||
|
||||
LogResult(success);
|
||||
|
||||
return success ? OperationResult.Success : OperationResult.Failed;
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
var success = true;
|
||||
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_VerifySessionIntegrity);
|
||||
|
||||
success &= InitializeStickyKeys();
|
||||
success &= VerifyCursors();
|
||||
success &= VerifyEaseOfAccess();
|
||||
|
||||
LogResult(success);
|
||||
|
||||
return success ? OperationResult.Success : OperationResult.Failed;
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
FinalizeStickyKeys();
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private void FinalizeStickyKeys()
|
||||
{
|
||||
sentinel.RevertStickyKeys();
|
||||
}
|
||||
|
||||
private bool InitializeStickyKeys()
|
||||
{
|
||||
var success = true;
|
||||
|
||||
sentinel.RevertStickyKeys();
|
||||
|
||||
if (!Context.Next.Settings.Security.AllowStickyKeys)
|
||||
{
|
||||
success = sentinel.DisableStickyKeys();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private void LogResult(bool success)
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Successfully ensured session integrity.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to ensure session integrity! Aborting session initialization...");
|
||||
}
|
||||
}
|
||||
|
||||
private bool VerifyCursors()
|
||||
{
|
||||
var success = true;
|
||||
|
||||
if (Context.Next.Settings.Security.VerifyCursorConfiguration)
|
||||
{
|
||||
success = sentinel.VerifyCursors();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug("Verification of cursor configuration is disabled.");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool VerifyEaseOfAccess()
|
||||
{
|
||||
var success = sentinel.VerifyEaseOfAccess();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (Context.Current?.Settings.Service.IgnoreService == false)
|
||||
{
|
||||
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.");
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
33
SafeExamBrowser.Runtime/Operations/SessionOperation.cs
Normal file
33
SafeExamBrowser.Runtime/Operations/SessionOperation.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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();
|
||||
}
|
||||
}
|
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 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;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public override OperationResult Perform()
|
||||
{
|
||||
return ValidateRestrictions();
|
||||
}
|
||||
|
||||
public override OperationResult Repeat()
|
||||
{
|
||||
return ValidateRestrictions();
|
||||
}
|
||||
|
||||
public override OperationResult Revert()
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
private OperationResult ValidateRestrictions()
|
||||
{
|
||||
var result = OperationResult.Success;
|
||||
|
||||
logger.Info("Validating version restrictions...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_ValidateVersionRestrictions);
|
||||
|
||||
if (Restrictions.Any())
|
||||
{
|
||||
var requiredVersions = $"'{string.Join("', '", Restrictions)}'";
|
||||
var version = Context.Next.AppConfig.ProgramInformationalVersion;
|
||||
|
||||
if (Restrictions.Any(r => IsFulfilled(r)))
|
||||
{
|
||||
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()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info($"There are no version restrictions for the configuration.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool IsFulfilled(VersionRestriction restriction)
|
||||
{
|
||||
var isFulfilled = true;
|
||||
var (major, minor, patch, build, isAllianceEdition) = GetVersion();
|
||||
|
||||
if (restriction.IsMinimumRestriction)
|
||||
{
|
||||
isFulfilled &= restriction.Major <= major;
|
||||
|
||||
if (restriction.Major == major)
|
||||
{
|
||||
isFulfilled &= restriction.Minor <= minor;
|
||||
|
||||
if (restriction.Minor == minor)
|
||||
{
|
||||
isFulfilled &= !restriction.Patch.HasValue || restriction.Patch <= patch;
|
||||
|
||||
if (restriction.Patch == patch)
|
||||
{
|
||||
isFulfilled &= !restriction.Build.HasValue || restriction.Build <= build;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isFulfilled &= !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;
|
||||
}
|
||||
|
||||
return isFulfilled;
|
||||
}
|
||||
|
||||
private (int major, int minor, int patch, int build, bool isAllianceEdition) GetVersion()
|
||||
{
|
||||
var parts = Context.Next.AppConfig.ProgramBuildVersion.Split('.');
|
||||
var major = int.Parse(parts[0]);
|
||||
var minor = int.Parse(parts[1]);
|
||||
var patch = int.Parse(parts[2]);
|
||||
var build = int.Parse(parts[3]);
|
||||
var isAllianceEdition = Context.Next.AppConfig.ProgramInformationalVersion.Contains("Alliance Edition");
|
||||
|
||||
return (major, minor, patch, build, isAllianceEdition);
|
||||
}
|
||||
|
||||
private string BuildRequiredVersions()
|
||||
{
|
||||
var info = new StringBuilder();
|
||||
var minimumVersionText = text.Get(TextKey.MessageBox_VersionRestrictionMinimum);
|
||||
|
||||
info.AppendLine();
|
||||
info.AppendLine();
|
||||
|
||||
foreach (var restriction in Restrictions)
|
||||
{
|
||||
var build = restriction.Build.HasValue ? $".{restriction.Build}" : "";
|
||||
var patch = restriction.Patch.HasValue ? $".{restriction.Patch}" : "";
|
||||
var allianceEdition = restriction.RequiresAllianceEdition ? " Alliance Edition" : "";
|
||||
var version = $"{restriction.Major}.{restriction.Minor}{patch}{build}{allianceEdition}";
|
||||
|
||||
if (restriction.IsMinimumRestriction)
|
||||
{
|
||||
info.AppendLine(minimumVersionText.Replace("%%_VERSION_%%", version));
|
||||
}
|
||||
else
|
||||
{
|
||||
info.AppendLine($"SEB {version}");
|
||||
}
|
||||
}
|
||||
|
||||
info.AppendLine();
|
||||
|
||||
return info.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Settings.Security;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Operations
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 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;
|
||||
}
|
||||
|
||||
return OperationResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
53
SafeExamBrowser.Runtime/Properties/AssemblyInfo.cs
Normal file
53
SafeExamBrowser.Runtime/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Safe Exam Browser")]
|
||||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("Safe Exam Browser")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: InternalsVisibleTo("SafeExamBrowser.Runtime.UnitTests")]
|
||||
|
||||
//In order to begin building localizable applications, set
|
||||
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
|
||||
//inside a <PropertyGroup>. For example, if you are using US english
|
||||
//in your source files, set the <UICulture> to en-US. Then uncomment
|
||||
//the NeutralResourceLanguage attribute below. Update the "en-US" in
|
||||
//the line below to match the UICulture setting in the project file.
|
||||
|
||||
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
|
||||
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("3.8.0.742")]
|
||||
[assembly: AssemblyFileVersion("3.8.0.742")]
|
||||
[assembly: AssemblyInformationalVersion("3.8.0.742")]
|
63
SafeExamBrowser.Runtime/Properties/Resources.Designer.cs
generated
Normal file
63
SafeExamBrowser.Runtime/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,63 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SafeExamBrowser.Runtime.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
SafeExamBrowser.Runtime/Properties/Resources.resx
Normal file
117
SafeExamBrowser.Runtime/Properties/Resources.resx
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
26
SafeExamBrowser.Runtime/Properties/Settings.Designer.cs
generated
Normal file
26
SafeExamBrowser.Runtime/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace SafeExamBrowser.Runtime.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.6.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
SafeExamBrowser.Runtime/Properties/Settings.settings
Normal file
7
SafeExamBrowser.Runtime/Properties/Settings.settings
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
737
SafeExamBrowser.Runtime/RuntimeController.cs
Normal file
737
SafeExamBrowser.Runtime/RuntimeController.cs
Normal file
@@ -0,0 +1,737 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Communication.Contracts.Data;
|
||||
using SafeExamBrowser.Communication.Contracts.Events;
|
||||
using SafeExamBrowser.Communication.Contracts.Hosts;
|
||||
using SafeExamBrowser.Communication.Contracts.Proxies;
|
||||
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.Data;
|
||||
using SafeExamBrowser.Settings.Security;
|
||||
using SafeExamBrowser.Settings.Service;
|
||||
using SafeExamBrowser.UserInterface.Contracts;
|
||||
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
|
||||
using SafeExamBrowser.UserInterface.Contracts.Windows;
|
||||
|
||||
namespace SafeExamBrowser.Runtime
|
||||
{
|
||||
internal class RuntimeController
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly ILogger logger;
|
||||
private readonly IMessageBox messageBox;
|
||||
private readonly IOperationSequence bootstrapSequence;
|
||||
private readonly IRepeatableOperationSequence sessionSequence;
|
||||
private readonly IRuntimeHost runtimeHost;
|
||||
private readonly IRuntimeWindow runtimeWindow;
|
||||
private readonly IServiceProxy service;
|
||||
private readonly SessionContext sessionContext;
|
||||
private readonly ISplashScreen splashScreen;
|
||||
private readonly Action shutdown;
|
||||
private readonly IText text;
|
||||
private readonly IUserInterfaceFactory uiFactory;
|
||||
|
||||
private SessionConfiguration Session
|
||||
{
|
||||
get { return sessionContext.Current; }
|
||||
}
|
||||
|
||||
private bool SessionIsRunning
|
||||
{
|
||||
get { return Session != null; }
|
||||
}
|
||||
|
||||
internal RuntimeController(
|
||||
AppConfig appConfig,
|
||||
ILogger logger,
|
||||
IMessageBox messageBox,
|
||||
IOperationSequence bootstrapSequence,
|
||||
IRepeatableOperationSequence sessionSequence,
|
||||
IRuntimeHost runtimeHost,
|
||||
IRuntimeWindow runtimeWindow,
|
||||
IServiceProxy service,
|
||||
SessionContext sessionContext,
|
||||
Action shutdown,
|
||||
ISplashScreen splashScreen,
|
||||
IText text,
|
||||
IUserInterfaceFactory uiFactory)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.bootstrapSequence = bootstrapSequence;
|
||||
this.logger = logger;
|
||||
this.messageBox = messageBox;
|
||||
this.runtimeHost = runtimeHost;
|
||||
this.runtimeWindow = runtimeWindow;
|
||||
this.sessionSequence = sessionSequence;
|
||||
this.service = service;
|
||||
this.sessionContext = sessionContext;
|
||||
this.shutdown = shutdown;
|
||||
this.splashScreen = splashScreen;
|
||||
this.text = text;
|
||||
this.uiFactory = uiFactory;
|
||||
}
|
||||
|
||||
internal bool TryStart()
|
||||
{
|
||||
logger.Info("Initiating startup procedure...");
|
||||
|
||||
bootstrapSequence.ProgressChanged += BootstrapSequence_ProgressChanged;
|
||||
bootstrapSequence.StatusChanged += BootstrapSequence_StatusChanged;
|
||||
sessionSequence.ActionRequired += SessionSequence_ActionRequired;
|
||||
sessionSequence.ProgressChanged += SessionSequence_ProgressChanged;
|
||||
sessionSequence.StatusChanged += SessionSequence_StatusChanged;
|
||||
|
||||
// We need to show the runtime window here already, this way implicitly setting it as the runtime application's main window.
|
||||
// Otherwise, the splash screen is considered as the main window and thus the operating system and/or WPF does not correctly
|
||||
// activate the runtime window once bootstrapping has finished, which in turn leads to undesired UI behavior.
|
||||
runtimeWindow.Show();
|
||||
runtimeWindow.BringToForeground();
|
||||
runtimeWindow.SetIndeterminate();
|
||||
|
||||
splashScreen.Show();
|
||||
splashScreen.BringToForeground();
|
||||
|
||||
var initialized = bootstrapSequence.TryPerform() == OperationResult.Success;
|
||||
|
||||
if (initialized)
|
||||
{
|
||||
RegisterEvents();
|
||||
|
||||
logger.Info("Application successfully initialized.");
|
||||
logger.Log(string.Empty);
|
||||
logger.Subscribe(runtimeWindow);
|
||||
splashScreen.Hide();
|
||||
StartSession();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info("Application startup aborted!");
|
||||
logger.Log(string.Empty);
|
||||
|
||||
messageBox.Show(AppendLogFilePaths(TextKey.MessageBox_StartupError), text.Get(TextKey.MessageBox_StartupErrorTitle), icon: MessageBoxIcon.Error, parent: splashScreen);
|
||||
}
|
||||
|
||||
return initialized && SessionIsRunning;
|
||||
}
|
||||
|
||||
internal void Terminate()
|
||||
{
|
||||
DeregisterEvents();
|
||||
|
||||
if (SessionIsRunning)
|
||||
{
|
||||
StopSession();
|
||||
}
|
||||
|
||||
logger.Unsubscribe(runtimeWindow);
|
||||
runtimeWindow.Close();
|
||||
|
||||
splashScreen.Show();
|
||||
splashScreen.BringToForeground();
|
||||
|
||||
logger.Log(string.Empty);
|
||||
logger.Info("Initiating shutdown procedure...");
|
||||
|
||||
var success = bootstrapSequence.TryRevert() == OperationResult.Success;
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info("Application successfully finalized.");
|
||||
logger.Log(string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info("Shutdown procedure failed!");
|
||||
logger.Log(string.Empty);
|
||||
|
||||
messageBox.Show(AppendLogFilePaths(TextKey.MessageBox_ShutdownError), text.Get(TextKey.MessageBox_ShutdownErrorTitle), icon: MessageBoxIcon.Error, parent: splashScreen);
|
||||
}
|
||||
|
||||
splashScreen.Close();
|
||||
}
|
||||
|
||||
private void StartSession()
|
||||
{
|
||||
runtimeWindow.Show();
|
||||
runtimeWindow.BringToForeground();
|
||||
runtimeWindow.ShowProgressBar = true;
|
||||
logger.Info(AppendDivider("Session Start Procedure"));
|
||||
|
||||
if (SessionIsRunning)
|
||||
{
|
||||
DeregisterSessionEvents();
|
||||
}
|
||||
|
||||
var result = SessionIsRunning ? sessionSequence.TryRepeat() : sessionSequence.TryPerform();
|
||||
|
||||
if (result == OperationResult.Success)
|
||||
{
|
||||
logger.Info(AppendDivider("Session Running"));
|
||||
|
||||
HandleSessionStartSuccess();
|
||||
}
|
||||
else if (result == OperationResult.Failed)
|
||||
{
|
||||
logger.Info(AppendDivider("Session Start Failed"));
|
||||
|
||||
HandleSessionStartFailure();
|
||||
}
|
||||
else if (result == OperationResult.Aborted)
|
||||
{
|
||||
logger.Info(AppendDivider("Session Start Aborted"));
|
||||
|
||||
HandleSessionStartAbortion();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSessionStartSuccess()
|
||||
{
|
||||
RegisterSessionEvents();
|
||||
|
||||
runtimeWindow.ShowProgressBar = false;
|
||||
runtimeWindow.ShowLog = Session.Settings.Security.AllowApplicationLogAccess;
|
||||
runtimeWindow.TopMost = false;
|
||||
runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning);
|
||||
runtimeWindow.Hide();
|
||||
}
|
||||
|
||||
private void HandleSessionStartFailure()
|
||||
{
|
||||
if (SessionIsRunning)
|
||||
{
|
||||
StopSession();
|
||||
|
||||
messageBox.Show(AppendLogFilePaths(TextKey.MessageBox_SessionStartError), text.Get(TextKey.MessageBox_SessionStartErrorTitle), icon: MessageBoxIcon.Error, parent: runtimeWindow);
|
||||
|
||||
logger.Info("Terminating application...");
|
||||
shutdown.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
messageBox.Show(AppendLogFilePaths(TextKey.MessageBox_SessionStartError), text.Get(TextKey.MessageBox_SessionStartErrorTitle), icon: MessageBoxIcon.Error, parent: runtimeWindow);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSessionStartAbortion()
|
||||
{
|
||||
if (SessionIsRunning)
|
||||
{
|
||||
RegisterSessionEvents();
|
||||
|
||||
runtimeWindow.ShowProgressBar = false;
|
||||
runtimeWindow.UpdateStatus(TextKey.RuntimeWindow_ApplicationRunning);
|
||||
runtimeWindow.TopMost = Session.Settings.Security.KioskMode != KioskMode.None;
|
||||
|
||||
if (Session.Settings.Security.KioskMode == KioskMode.DisableExplorerShell)
|
||||
{
|
||||
runtimeWindow.Hide();
|
||||
}
|
||||
|
||||
sessionContext.ClientProxy.InformReconfigurationAborted();
|
||||
}
|
||||
}
|
||||
|
||||
private void StopSession()
|
||||
{
|
||||
runtimeWindow.Show();
|
||||
runtimeWindow.BringToForeground();
|
||||
runtimeWindow.ShowProgressBar = true;
|
||||
logger.Info(AppendDivider("Session Stop Procedure"));
|
||||
|
||||
DeregisterSessionEvents();
|
||||
|
||||
var success = sessionSequence.TryRevert() == OperationResult.Success;
|
||||
|
||||
if (success)
|
||||
{
|
||||
logger.Info(AppendDivider("Session Terminated"));
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Info(AppendDivider("Session Stop Failed"));
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterEvents()
|
||||
{
|
||||
runtimeHost.ClientConfigurationNeeded += RuntimeHost_ClientConfigurationNeeded;
|
||||
runtimeHost.ReconfigurationRequested += RuntimeHost_ReconfigurationRequested;
|
||||
runtimeHost.ShutdownRequested += RuntimeHost_ShutdownRequested;
|
||||
}
|
||||
|
||||
private void DeregisterEvents()
|
||||
{
|
||||
runtimeHost.ClientConfigurationNeeded -= RuntimeHost_ClientConfigurationNeeded;
|
||||
runtimeHost.ReconfigurationRequested -= RuntimeHost_ReconfigurationRequested;
|
||||
runtimeHost.ShutdownRequested -= RuntimeHost_ShutdownRequested;
|
||||
}
|
||||
|
||||
private void RegisterSessionEvents()
|
||||
{
|
||||
service.ConnectionLost += ServiceProxy_ConnectionLost;
|
||||
sessionContext.ClientProcess.Terminated += ClientProcess_Terminated;
|
||||
sessionContext.ClientProxy.ConnectionLost += ClientProxy_ConnectionLost;
|
||||
}
|
||||
|
||||
private void DeregisterSessionEvents()
|
||||
{
|
||||
service.ConnectionLost -= ServiceProxy_ConnectionLost;
|
||||
|
||||
if (sessionContext.ClientProcess != null)
|
||||
{
|
||||
sessionContext.ClientProcess.Terminated -= ClientProcess_Terminated;
|
||||
}
|
||||
|
||||
if (sessionContext.ClientProxy != null)
|
||||
{
|
||||
sessionContext.ClientProxy.ConnectionLost -= ClientProxy_ConnectionLost;
|
||||
}
|
||||
}
|
||||
|
||||
private void BootstrapSequence_ProgressChanged(ProgressChangedEventArgs args)
|
||||
{
|
||||
MapProgress(splashScreen, args);
|
||||
}
|
||||
|
||||
private void BootstrapSequence_StatusChanged(TextKey status)
|
||||
{
|
||||
splashScreen.UpdateStatus(status, true);
|
||||
}
|
||||
|
||||
private void ClientProcess_Terminated(int exitCode)
|
||||
{
|
||||
logger.Error($"Client application has unexpectedly terminated with exit code {exitCode}!");
|
||||
|
||||
if (SessionIsRunning)
|
||||
{
|
||||
StopSession();
|
||||
}
|
||||
|
||||
//messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow);
|
||||
shutdown.Invoke();
|
||||
}
|
||||
|
||||
private void ClientProxy_ConnectionLost()
|
||||
{
|
||||
logger.Error("Lost connection to the client application!");
|
||||
|
||||
if (SessionIsRunning)
|
||||
{
|
||||
StopSession();
|
||||
}
|
||||
|
||||
messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow);
|
||||
shutdown.Invoke();
|
||||
}
|
||||
|
||||
private void RuntimeHost_ClientConfigurationNeeded(ClientConfigurationEventArgs args)
|
||||
{
|
||||
args.ClientConfiguration = new ClientConfiguration
|
||||
{
|
||||
AppConfig = sessionContext.Next.AppConfig,
|
||||
SessionId = sessionContext.Next.SessionId,
|
||||
Settings = sessionContext.Next.Settings
|
||||
};
|
||||
}
|
||||
|
||||
private void RuntimeHost_ReconfigurationRequested(ReconfigurationEventArgs args)
|
||||
{
|
||||
logger.Info($"Accepted request for reconfiguration with '{args.ConfigurationPath}'.");
|
||||
|
||||
sessionContext.ReconfigurationFilePath = args.ConfigurationPath;
|
||||
sessionContext.ReconfigurationUrl = args.ResourceUrl;
|
||||
|
||||
StartSession();
|
||||
}
|
||||
|
||||
private void RuntimeHost_ShutdownRequested()
|
||||
{
|
||||
logger.Info("Received shutdown request from the client application.");
|
||||
shutdown.Invoke();
|
||||
}
|
||||
|
||||
private void ServiceProxy_ConnectionLost()
|
||||
{
|
||||
if (SessionIsRunning && Session.Settings.Service.Policy == ServicePolicy.Mandatory)
|
||||
{
|
||||
logger.Error("Lost connection to the service component!");
|
||||
StopSession();
|
||||
messageBox.Show(TextKey.MessageBox_ApplicationError, TextKey.MessageBox_ApplicationErrorTitle, icon: MessageBoxIcon.Error, parent: runtimeWindow);
|
||||
shutdown.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Lost connection to the service component!");
|
||||
}
|
||||
}
|
||||
|
||||
private void SessionSequence_ActionRequired(ActionRequiredEventArgs args)
|
||||
{
|
||||
switch (args)
|
||||
{
|
||||
case ConfigurationCompletedEventArgs a:
|
||||
AskIfConfigurationSufficient(a);
|
||||
break;
|
||||
case ExamSelectionEventArgs a:
|
||||
AskForExamSelection(a);
|
||||
break;
|
||||
case MessageEventArgs m:
|
||||
ShowMessageBox(m);
|
||||
break;
|
||||
case PasswordRequiredEventArgs p:
|
||||
AskForPassword(p);
|
||||
break;
|
||||
case ServerFailureEventArgs a:
|
||||
AskForServerFailureAction(a);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AskForExamSelection(ExamSelectionEventArgs args)
|
||||
{
|
||||
var isStartup = !SessionIsRunning;
|
||||
var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.Security.KioskMode == KioskMode.DisableExplorerShell;
|
||||
|
||||
if (isStartup || isRunningOnDefaultDesktop)
|
||||
{
|
||||
TryAskForExamSelectionViaDialog(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAskForExamSelectionViaClient(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void AskForServerFailureAction(ServerFailureEventArgs args)
|
||||
{
|
||||
var isStartup = !SessionIsRunning;
|
||||
var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.Security.KioskMode == KioskMode.DisableExplorerShell;
|
||||
|
||||
if (isStartup || isRunningOnDefaultDesktop)
|
||||
{
|
||||
TryAskForServerFailureActionViaDialog(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAskForServerFailureActionViaClient(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void AskIfConfigurationSufficient(ConfigurationCompletedEventArgs args)
|
||||
{
|
||||
var message = TextKey.MessageBox_ClientConfigurationQuestion;
|
||||
var title = TextKey.MessageBox_ClientConfigurationQuestionTitle;
|
||||
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, runtimeWindow);
|
||||
|
||||
args.AbortStartup = result == MessageBoxResult.Yes;
|
||||
}
|
||||
|
||||
private void AskForPassword(PasswordRequiredEventArgs args)
|
||||
{
|
||||
var isStartup = !SessionIsRunning;
|
||||
var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.Security.KioskMode == KioskMode.DisableExplorerShell;
|
||||
|
||||
if (isStartup || isRunningOnDefaultDesktop)
|
||||
{
|
||||
TryGetPasswordViaDialog(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryGetPasswordViaClient(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowMessageBox(MessageEventArgs args)
|
||||
{
|
||||
var isStartup = !SessionIsRunning;
|
||||
var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.Security.KioskMode == KioskMode.DisableExplorerShell;
|
||||
var message = text.Get(args.Message);
|
||||
var title = text.Get(args.Title);
|
||||
|
||||
foreach (var placeholder in args.MessagePlaceholders)
|
||||
{
|
||||
message = message.Replace(placeholder.Key, placeholder.Value);
|
||||
}
|
||||
|
||||
foreach (var placeholder in args.TitlePlaceholders)
|
||||
{
|
||||
title = title.Replace(placeholder.Key, placeholder.Value);
|
||||
}
|
||||
|
||||
if (isStartup || isRunningOnDefaultDesktop)
|
||||
{
|
||||
args.Result = messageBox.Show(message, title, args.Action, args.Icon, runtimeWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Result = ShowMessageBoxViaClient(message, title, args.Action, args.Icon);
|
||||
}
|
||||
}
|
||||
|
||||
private MessageBoxResult ShowMessageBoxViaClient(string message, string title, MessageBoxAction action, MessageBoxIcon icon)
|
||||
{
|
||||
var requestId = Guid.NewGuid();
|
||||
var result = MessageBoxResult.None;
|
||||
var response = default(MessageBoxReplyEventArgs);
|
||||
var responseEvent = new AutoResetEvent(false);
|
||||
var responseEventHandler = new CommunicationEventHandler<MessageBoxReplyEventArgs>((args) =>
|
||||
{
|
||||
if (args.RequestId == requestId)
|
||||
{
|
||||
response = args;
|
||||
responseEvent.Set();
|
||||
}
|
||||
});
|
||||
|
||||
runtimeHost.MessageBoxReplyReceived += responseEventHandler;
|
||||
|
||||
var communication = sessionContext.ClientProxy.ShowMessage(message, title, (int) action, (int) icon, requestId);
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
responseEvent.WaitOne();
|
||||
result = (MessageBoxResult) response.Result;
|
||||
}
|
||||
|
||||
runtimeHost.MessageBoxReplyReceived -= responseEventHandler;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void TryAskForExamSelectionViaDialog(ExamSelectionEventArgs args)
|
||||
{
|
||||
var dialog = uiFactory.CreateExamSelectionDialog(args.Exams);
|
||||
var result = dialog.Show(runtimeWindow);
|
||||
|
||||
args.SelectedExam = result.SelectedExam;
|
||||
args.Success = result.Success;
|
||||
}
|
||||
|
||||
private void TryAskForExamSelectionViaClient(ExamSelectionEventArgs args)
|
||||
{
|
||||
var exams = args.Exams.Select(e => (e.Id, e.LmsName, e.Name, e.Url));
|
||||
var requestId = Guid.NewGuid();
|
||||
var response = default(ExamSelectionReplyEventArgs);
|
||||
var responseEvent = new AutoResetEvent(false);
|
||||
var responseEventHandler = new CommunicationEventHandler<ExamSelectionReplyEventArgs>((a) =>
|
||||
{
|
||||
if (a.RequestId == requestId)
|
||||
{
|
||||
response = a;
|
||||
responseEvent.Set();
|
||||
}
|
||||
});
|
||||
|
||||
runtimeHost.ExamSelectionReceived += responseEventHandler;
|
||||
|
||||
var communication = sessionContext.ClientProxy.RequestExamSelection(exams, requestId);
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
responseEvent.WaitOne();
|
||||
args.SelectedExam = args.Exams.First(e => e.Id == response.SelectedExamId);
|
||||
args.Success = response.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.SelectedExam = default(Exam);
|
||||
args.Success = false;
|
||||
}
|
||||
|
||||
runtimeHost.ExamSelectionReceived -= responseEventHandler;
|
||||
}
|
||||
|
||||
private void TryAskForServerFailureActionViaDialog(ServerFailureEventArgs args)
|
||||
{
|
||||
var dialog = uiFactory.CreateServerFailureDialog(args.Message, args.ShowFallback);
|
||||
var result = dialog.Show(runtimeWindow);
|
||||
|
||||
args.Abort = result.Abort;
|
||||
args.Fallback = result.Fallback;
|
||||
args.Retry = result.Retry;
|
||||
}
|
||||
|
||||
private void TryAskForServerFailureActionViaClient(ServerFailureEventArgs args)
|
||||
{
|
||||
var requestId = Guid.NewGuid();
|
||||
var response = default(ServerFailureActionReplyEventArgs);
|
||||
var responseEvent = new AutoResetEvent(false);
|
||||
var responseEventHandler = new CommunicationEventHandler<ServerFailureActionReplyEventArgs>((a) =>
|
||||
{
|
||||
if (a.RequestId == requestId)
|
||||
{
|
||||
response = a;
|
||||
responseEvent.Set();
|
||||
}
|
||||
});
|
||||
|
||||
runtimeHost.ServerFailureActionReceived += responseEventHandler;
|
||||
|
||||
var communication = sessionContext.ClientProxy.RequestServerFailureAction(args.Message, args.ShowFallback, requestId);
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
responseEvent.WaitOne();
|
||||
args.Abort = response.Abort;
|
||||
args.Fallback = response.Fallback;
|
||||
args.Retry = response.Retry;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Abort = true;
|
||||
args.Fallback = false;
|
||||
args.Retry = false;
|
||||
}
|
||||
|
||||
runtimeHost.ServerFailureActionReceived -= responseEventHandler;
|
||||
}
|
||||
|
||||
private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args)
|
||||
{
|
||||
var message = default(TextKey);
|
||||
var title = default(TextKey);
|
||||
|
||||
switch (args.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;
|
||||
}
|
||||
|
||||
var dialog = uiFactory.CreatePasswordDialog(text.Get(message), text.Get(title));
|
||||
var result = dialog.Show(runtimeWindow);
|
||||
|
||||
args.Password = result.Password;
|
||||
args.Success = result.Success;
|
||||
}
|
||||
|
||||
private void TryGetPasswordViaClient(PasswordRequiredEventArgs args)
|
||||
{
|
||||
var requestId = Guid.NewGuid();
|
||||
var response = default(PasswordReplyEventArgs);
|
||||
var responseEvent = new AutoResetEvent(false);
|
||||
var responseEventHandler = new CommunicationEventHandler<PasswordReplyEventArgs>((a) =>
|
||||
{
|
||||
if (a.RequestId == requestId)
|
||||
{
|
||||
response = a;
|
||||
responseEvent.Set();
|
||||
}
|
||||
});
|
||||
|
||||
runtimeHost.PasswordReceived += responseEventHandler;
|
||||
|
||||
var communication = sessionContext.ClientProxy.RequestPassword(args.Purpose, requestId);
|
||||
|
||||
if (communication.Success)
|
||||
{
|
||||
responseEvent.WaitOne();
|
||||
args.Password = response.Password;
|
||||
args.Success = response.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Password = default(string);
|
||||
args.Success = false;
|
||||
}
|
||||
|
||||
runtimeHost.PasswordReceived -= responseEventHandler;
|
||||
}
|
||||
|
||||
private void SessionSequence_ProgressChanged(ProgressChangedEventArgs args)
|
||||
{
|
||||
MapProgress(runtimeWindow, args);
|
||||
}
|
||||
|
||||
private void SessionSequence_StatusChanged(TextKey status)
|
||||
{
|
||||
runtimeWindow?.UpdateStatus(status, true);
|
||||
}
|
||||
|
||||
private void MapProgress(IProgressIndicator progressIndicator, ProgressChangedEventArgs args)
|
||||
{
|
||||
if (args.CurrentValue.HasValue)
|
||||
{
|
||||
progressIndicator?.SetValue(args.CurrentValue.Value);
|
||||
}
|
||||
|
||||
if (args.IsIndeterminate == true)
|
||||
{
|
||||
progressIndicator?.SetIndeterminate();
|
||||
}
|
||||
|
||||
if (args.MaxValue.HasValue)
|
||||
{
|
||||
progressIndicator?.SetMaxValue(args.MaxValue.Value);
|
||||
}
|
||||
|
||||
if (args.Progress == true)
|
||||
{
|
||||
progressIndicator?.Progress();
|
||||
}
|
||||
|
||||
if (args.Regress == true)
|
||||
{
|
||||
progressIndicator?.Regress();
|
||||
}
|
||||
}
|
||||
|
||||
private string AppendDivider(string message)
|
||||
{
|
||||
var dashesLeft = new String('-', 48 - message.Length / 2 - message.Length % 2);
|
||||
var dashesRight = new String('-', 48 - message.Length / 2);
|
||||
|
||||
return $"### {dashesLeft} {message} {dashesRight} ###";
|
||||
}
|
||||
|
||||
private string AppendLogFilePaths(TextKey key)
|
||||
{
|
||||
var message = text.Get(key);
|
||||
|
||||
if (File.Exists(appConfig.BrowserLogFilePath))
|
||||
{
|
||||
message += $"{Environment.NewLine}{Environment.NewLine}{appConfig.BrowserLogFilePath}";
|
||||
}
|
||||
|
||||
if (File.Exists(appConfig.ClientLogFilePath))
|
||||
{
|
||||
message += $"{Environment.NewLine}{Environment.NewLine}{appConfig.ClientLogFilePath}";
|
||||
}
|
||||
|
||||
if (File.Exists(appConfig.RuntimeLogFilePath))
|
||||
{
|
||||
message += $"{Environment.NewLine}{Environment.NewLine}{appConfig.RuntimeLogFilePath}";
|
||||
}
|
||||
|
||||
if (File.Exists(appConfig.ServiceLogFilePath))
|
||||
{
|
||||
message += $"{Environment.NewLine}{Environment.NewLine}{appConfig.ServiceLogFilePath}";
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
282
SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj
Normal file
282
SafeExamBrowser.Runtime/SafeExamBrowser.Runtime.csproj
Normal file
@@ -0,0 +1,282 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{E3AED2F8-B5DF-45D1-AC19-48066923D6D8}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>SafeExamBrowser.Runtime</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>3.0.0.%2a</ApplicationVersion>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>SafeExamBrowser.Runtime.App</StartupObject>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>SafeExamBrowser.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="App.cs" />
|
||||
<Compile Include="Operations\ClientOperation.cs" />
|
||||
<Compile Include="Operations\ClientTerminationOperation.cs" />
|
||||
<Compile Include="Operations\ConfigurationBaseOperation.cs" />
|
||||
<Compile Include="Operations\ConfigurationOperation.cs" />
|
||||
<Compile Include="Operations\DisclaimerOperation.cs" />
|
||||
<Compile Include="Operations\DisplayMonitorOperation.cs" />
|
||||
<Compile Include="Operations\Events\ClientConfigurationErrorMessageArgs.cs" />
|
||||
<Compile Include="Operations\Events\ConfigurationCompletedEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\ExamSelectionEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\InvalidDataMessageArgs.cs" />
|
||||
<Compile Include="Operations\Events\InvalidPasswordMessageArgs.cs" />
|
||||
<Compile Include="Operations\Events\MessageEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\NotSupportedMessageArgs.cs" />
|
||||
<Compile Include="Operations\Events\PasswordRequiredEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\ServerFailureEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\UnexpectedErrorMessageArgs.cs" />
|
||||
<Compile Include="Operations\ApplicationIntegrityOperation.cs" />
|
||||
<Compile Include="Operations\Events\VersionRestrictionMessageArgs.cs" />
|
||||
<Compile Include="Operations\KioskModeOperation.cs" />
|
||||
<Compile Include="Operations\RemoteSessionOperation.cs" />
|
||||
<Compile Include="Operations\ServerOperation.cs" />
|
||||
<Compile Include="Operations\ServiceOperation.cs" />
|
||||
<Compile Include="Operations\SessionActivationOperation.cs" />
|
||||
<Compile Include="Operations\SessionIntegrityOperation.cs" />
|
||||
<Compile Include="Operations\SessionOperation.cs" />
|
||||
<Compile Include="Operations\SessionInitializationOperation.cs" />
|
||||
<Compile Include="Communication\RuntimeHost.cs" />
|
||||
<Compile Include="CompositionRoot.cs" />
|
||||
<Compile Include="Operations\VersionRestrictionOperation.cs" />
|
||||
<Compile Include="Operations\VirtualMachineOperation.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
<Compile Include="RuntimeController.cs" />
|
||||
<Compile Include="SessionContext.cs" />
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<None Include="app.manifest" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Communication.Contracts\SafeExamBrowser.Communication.Contracts.csproj">
|
||||
<Project>{0cd2c5fe-711a-4c32-afe0-bb804fe8b220}</Project>
|
||||
<Name>SafeExamBrowser.Communication.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Communication\SafeExamBrowser.Communication.csproj">
|
||||
<Project>{c9416a62-0623-4d38-96aa-92516b32f02f}</Project>
|
||||
<Name>SafeExamBrowser.Communication</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
|
||||
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
|
||||
<Name>SafeExamBrowser.Configuration.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Configuration\SafeExamBrowser.Configuration.csproj">
|
||||
<Project>{c388c4dd-a159-457d-af92-89f7ad185109}</Project>
|
||||
<Name>SafeExamBrowser.Configuration</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj">
|
||||
<Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project>
|
||||
<Name>SafeExamBrowser.Core.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Core\SafeExamBrowser.Core.csproj">
|
||||
<Project>{3d6fdbb6-a4af-4626-bb2b-bf329d44f9cc}</Project>
|
||||
<Name>SafeExamBrowser.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.I18n.Contracts\SafeExamBrowser.I18n.Contracts.csproj">
|
||||
<Project>{1858ddf3-bc2a-4bff-b663-4ce2ffeb8b7d}</Project>
|
||||
<Name>SafeExamBrowser.I18n.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.I18n\SafeExamBrowser.I18n.csproj">
|
||||
<Project>{10c62628-8e6a-45aa-9d97-339b119ad21d}</Project>
|
||||
<Name>SafeExamBrowser.I18n</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
|
||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Logging\SafeExamBrowser.Logging.csproj">
|
||||
<Project>{e107026c-2011-4552-a7d8-3a0d37881df6}</Project>
|
||||
<Name>SafeExamBrowser.Logging</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Monitoring.Contracts\SafeExamBrowser.Monitoring.Contracts.csproj">
|
||||
<Project>{6d563a30-366d-4c35-815b-2c9e6872278b}</Project>
|
||||
<Name>SafeExamBrowser.Monitoring.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Monitoring\SafeExamBrowser.Monitoring.csproj">
|
||||
<Project>{ef563531-4eb5-44b9-a5ec-d6d6f204469b}</Project>
|
||||
<Name>SafeExamBrowser.Monitoring</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
|
||||
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
|
||||
<Name>SafeExamBrowser.Server.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Server\SafeExamBrowser.Server.csproj">
|
||||
<Project>{46edbde0-58b4-4725-9783-0c55c3d49c0c}</Project>
|
||||
<Name>SafeExamBrowser.Server</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.SystemComponents.Contracts\SafeExamBrowser.SystemComponents.Contracts.csproj">
|
||||
<Project>{903129c6-e236-493b-9ad6-c6a57f647a3a}</Project>
|
||||
<Name>SafeExamBrowser.SystemComponents.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.SystemComponents\SafeExamBrowser.SystemComponents.csproj">
|
||||
<Project>{acee2ef1-14d2-4b52-8994-5c053055bb51}</Project>
|
||||
<Name>SafeExamBrowser.SystemComponents</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Contracts\SafeExamBrowser.UserInterface.Contracts.csproj">
|
||||
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
|
||||
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Desktop\SafeExamBrowser.UserInterface.Desktop.csproj">
|
||||
<Project>{a502df54-7169-4647-94bd-18b192924866}</Project>
|
||||
<Name>SafeExamBrowser.UserInterface.Desktop</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
|
||||
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
|
||||
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.WindowsApi\SafeExamBrowser.WindowsApi.csproj">
|
||||
<Project>{73724659-4150-4792-A94E-42F5F3C1B696}</Project>
|
||||
<Name>SafeExamBrowser.WindowsApi</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Microsoft .NET Framework 4.5.2 %28x86 and x64%29</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="SafeExamBrowser.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>IF EXIST "C:\SEB\seb_$(PlatformName).dll" (
|
||||
robocopy "C:\SEB" "$(TargetDir)\" "seb_$(PlatformName).dll" /np
|
||||
|
||||
IF %25ERRORLEVEL%25 GEQ 8 (
|
||||
EXIT 1
|
||||
)
|
||||
) ELSE (
|
||||
ECHO WARNING: Integrity module not included!
|
||||
)
|
||||
|
||||
EXIT 0</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PreBuildEvent>
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Target Name="AfterClean" AfterTargets="Clean">
|
||||
<RemoveDir Directories="$(TargetDir)" />
|
||||
</Target>
|
||||
<Import Project="..\packages\OpenCover.4.7.1221\build\OpenCover.targets" Condition="Exists('..\packages\OpenCover.4.7.1221\build\OpenCover.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\OpenCover.4.7.1221\build\OpenCover.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\OpenCover.4.7.1221\build\OpenCover.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
BIN
SafeExamBrowser.Runtime/SafeExamBrowser.ico
Normal file
BIN
SafeExamBrowser.Runtime/SafeExamBrowser.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 361 KiB |
50
SafeExamBrowser.Runtime/SessionContext.cs
Normal file
50
SafeExamBrowser.Runtime/SessionContext.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.Proxies;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds all configuration and runtime data required for the session handling.
|
||||
/// </summary>
|
||||
internal class SessionContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The currently running client process.
|
||||
/// </summary>
|
||||
internal IProcess ClientProcess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The communication proxy for the currently running client process.
|
||||
/// </summary>
|
||||
internal IClientProxy ClientProxy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The configuration of the currently active session.
|
||||
/// </summary>
|
||||
internal SessionConfiguration Current { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The configuration of the next session to be activated.
|
||||
/// </summary>
|
||||
internal SessionConfiguration Next { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path of the configuration file to be used for reconfiguration.
|
||||
/// </summary>
|
||||
internal string ReconfigurationFilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The original URL from where the configuration file was downloaded.
|
||||
/// </summary>
|
||||
internal string ReconfigurationUrl { get; set; }
|
||||
}
|
||||
}
|
26
SafeExamBrowser.Runtime/app.manifest
Normal file
26
SafeExamBrowser.Runtime/app.manifest
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on and is
|
||||
is designed to work with. Uncomment the appropriate elements and Windows will
|
||||
automatically selected the most compatible environment. -->
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||
<!-- Windows 10 and Windows 11 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
4
SafeExamBrowser.Runtime/packages.config
Normal file
4
SafeExamBrowser.Runtime/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="OpenCover" version="4.7.1221" targetFramework="net48" />
|
||||
</packages>
|
Reference in New Issue
Block a user