/* * 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.Diagnostics; using System.Text; using SafeExamBrowser.Applications; using SafeExamBrowser.Browser; using SafeExamBrowser.Client.Communication; using SafeExamBrowser.Client.Notifications; using SafeExamBrowser.Client.Operations; using SafeExamBrowser.Communication.Contracts; using SafeExamBrowser.Communication.Contracts.Proxies; using SafeExamBrowser.Communication.Hosts; using SafeExamBrowser.Communication.Proxies; using SafeExamBrowser.Configuration.Cryptography; 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.Applications; using SafeExamBrowser.Monitoring.Display; using SafeExamBrowser.Monitoring.Keyboard; using SafeExamBrowser.Monitoring.Mouse; using SafeExamBrowser.Monitoring.System; using SafeExamBrowser.Proctoring; using SafeExamBrowser.Server; using SafeExamBrowser.Settings.Logging; using SafeExamBrowser.Settings.UserInterface; using SafeExamBrowser.SystemComponents; using SafeExamBrowser.SystemComponents.Audio; using SafeExamBrowser.SystemComponents.Contracts; using SafeExamBrowser.SystemComponents.Contracts.Network; using SafeExamBrowser.SystemComponents.Contracts.PowerSupply; using SafeExamBrowser.SystemComponents.Keyboard; using SafeExamBrowser.SystemComponents.Network; using SafeExamBrowser.SystemComponents.PowerSupply; using SafeExamBrowser.SystemComponents.Registry; using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.Shell; using SafeExamBrowser.UserInterface.Shared.Activators; using SafeExamBrowser.WindowsApi; using SafeExamBrowser.WindowsApi.Contracts; using SafeExamBrowser.WindowsApi.Processes; using Desktop = SafeExamBrowser.UserInterface.Desktop; using Mobile = SafeExamBrowser.UserInterface.Mobile; namespace SafeExamBrowser.Client { internal class CompositionRoot { private const int TWO_SECONDS = 2000; private const int FIVE_SECONDS = 5000; private Guid authenticationToken; private ClientContext context; private string logFilePath; private LogLevel logLevel; private string runtimeHostUri; private UserInterfaceMode uiMode; private IActionCenter actionCenter; private ApplicationMonitor applicationMonitor; private ILogger logger; private IMessageBox messageBox; private INativeMethods nativeMethods; private INetworkAdapter networkAdapter; private IPowerSupply powerSupply; private IRuntimeProxy runtimeProxy; private ISystemInfo systemInfo; private ITaskbar taskbar; private ITaskview taskview; private IUserInfo userInfo; private IText text; private IUserInterfaceFactory uiFactory; internal ClientController ClientController { get; private set; } internal void BuildObjectGraph(Action shutdown) { ValidateCommandLineArguments(); InitializeLogging(); InitializeText(); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); var registry = new Registry(ModuleLogger(nameof(Registry))); uiFactory = BuildUserInterfaceFactory(); actionCenter = uiFactory.CreateActionCenter(); context = new ClientContext(); messageBox = BuildMessageBox(); nativeMethods = new NativeMethods(); applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, processFactory); networkAdapter = new NetworkAdapter(ModuleLogger(nameof(NetworkAdapter)), nativeMethods); runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client); systemInfo = new SystemInfo(registry); taskbar = uiFactory.CreateTaskbar(ModuleLogger("Taskbar")); taskview = uiFactory.CreateTaskview(); userInfo = new UserInfo(ModuleLogger(nameof(UserInfo))); var applicationFactory = new ApplicationFactory(applicationMonitor, ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory, new Registry(ModuleLogger(nameof(Registry)))); var clipboard = new Clipboard(ModuleLogger(nameof(Clipboard)), nativeMethods); var coordinator = new Coordinator(); var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo); var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods); var fileSystemDialog = BuildFileSystemDialog(); var hashAlgorithm = new HashAlgorithm(); var splashScreen = uiFactory.CreateSplashScreen(); var systemMonitor = new SystemMonitor(ModuleLogger(nameof(SystemMonitor))); var operations = new Queue(); operations.Enqueue(new I18nOperation(logger, text)); operations.Enqueue(new RuntimeConnectionOperation(context, logger, runtimeProxy, authenticationToken)); operations.Enqueue(new ConfigurationOperation(context, logger, runtimeProxy)); operations.Enqueue(new DelegateOperation(UpdateAppConfig)); operations.Enqueue(new DelegateOperation(BuildIntegrityModule)); operations.Enqueue(new DelegateOperation(BuildPowerSupply)); operations.Enqueue(new LazyInitializationOperation(BuildClientHostOperation)); operations.Enqueue(new ClientHostDisconnectionOperation(context, logger, FIVE_SECONDS)); operations.Enqueue(new LazyInitializationOperation(BuildKeyboardInterceptorOperation)); operations.Enqueue(new LazyInitializationOperation(BuildMouseInterceptorOperation)); operations.Enqueue(new ApplicationOperation(context, applicationFactory, applicationMonitor, logger, text)); operations.Enqueue(new DisplayMonitorOperation(context, displayMonitor, logger, taskbar)); operations.Enqueue(new SystemMonitorOperation(context, systemMonitor, logger)); operations.Enqueue(new LazyInitializationOperation(BuildShellOperation)); operations.Enqueue(new LazyInitializationOperation(BuildBrowserOperation)); operations.Enqueue(new LazyInitializationOperation(BuildServerOperation)); operations.Enqueue(new LazyInitializationOperation(BuildProctoringOperation)); operations.Enqueue(new ClipboardOperation(context, clipboard, logger)); var sequence = new OperationSequence(logger, operations); ClientController = new ClientController( actionCenter, applicationMonitor, context, coordinator, displayMonitor, explorerShell, fileSystemDialog, hashAlgorithm, logger, messageBox, networkAdapter, sequence, registry, runtimeProxy, shutdown, splashScreen, systemMonitor, taskbar, text, uiFactory); } internal void LogStartupInformation() { logger.Log($"# New client instance started at {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}"); logger.Log(string.Empty); } internal void LogShutdownInformation() { logger?.Log($"# Client instance terminated at {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}"); } private void ValidateCommandLineArguments() { var args = Environment.GetCommandLineArgs(); var hasFive = args?.Length >= 5; var valid = false; if (hasFive) { var logFilePath = Encoding.UTF8.GetString(Convert.FromBase64String(args[1])); var hasLogFilePath = Uri.TryCreate(logFilePath, UriKind.Absolute, out var filePath) && filePath.IsFile; var hasLogLevel = Enum.TryParse(args[2], out LogLevel logLevel); var hasHostUri = Uri.TryCreate(args[3], UriKind.Absolute, out var runtimeHostUri) && runtimeHostUri.IsWellFormedOriginalString(); var hasAuthenticationToken = Guid.TryParse(args[4], out var authenticationToken); if (hasLogFilePath && hasLogLevel && hasHostUri && hasAuthenticationToken) { this.authenticationToken = authenticationToken; this.logFilePath = logFilePath; this.logLevel = logLevel; this.runtimeHostUri = args[3]; this.uiMode = args.Length == 6 && Enum.TryParse(args[5], out uiMode) ? uiMode : UserInterfaceMode.Desktop; valid = true; } } if (!valid) { throw new ArgumentException("Invalid arguments! Required: SafeExamBrowser.Client.exe "); } } private void InitializeLogging() { var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), logFilePath); logFileWriter.Initialize(); logger = new Logger(); logger.LogLevel = logLevel; logger.Subscribe(logFileWriter); } private void InitializeText() { text = new Text(ModuleLogger(nameof(Text))); } private IOperation BuildBrowserOperation() { var fileSystemDialog = BuildFileSystemDialog(); var keyGenerator = new KeyGenerator(context.AppConfig, context.IntegrityModule, ModuleLogger(nameof(KeyGenerator))); var moduleLogger = ModuleLogger(nameof(BrowserApplication)); var browser = new BrowserApplication( context.AppConfig, context.Settings.Browser, fileSystemDialog, new HashAlgorithm(), keyGenerator, messageBox, moduleLogger, nativeMethods, context.Settings.SessionMode, text, uiFactory); var operation = new BrowserOperation(actionCenter, context, logger, taskbar, taskview, uiFactory); context.Browser = browser; return operation; } private IOperation BuildClientHostOperation() { var processId = Process.GetCurrentProcess().Id; var factory = new HostObjectFactory(); var clientHost = new ClientHost(context.AppConfig.ClientAddress, factory, ModuleLogger(nameof(ClientHost)), processId, FIVE_SECONDS); var operation = new CommunicationHostOperation(clientHost, logger); context.ClientHost = clientHost; context.ClientHost.AuthenticationToken = authenticationToken; return operation; } private void BuildIntegrityModule() { context.IntegrityModule = new IntegrityModule(context.AppConfig, ModuleLogger(nameof(IntegrityModule))); } private void BuildPowerSupply() { powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply)), context.Settings.PowerSupply); } private IOperation BuildKeyboardInterceptorOperation() { var keyboardInterceptor = new KeyboardInterceptor(ModuleLogger(nameof(KeyboardInterceptor)), nativeMethods, context.Settings.Keyboard); var operation = new KeyboardInterceptorOperation(context, keyboardInterceptor, logger); return operation; } private IOperation BuildMouseInterceptorOperation() { var mouseInterceptor = new MouseInterceptor(ModuleLogger(nameof(MouseInterceptor)), nativeMethods, context.Settings.Mouse); var operation = new MouseInterceptorOperation(context, logger, mouseInterceptor); return operation; } private IOperation BuildProctoringOperation() { var controller = new ProctoringController( context.AppConfig, applicationMonitor, context.Browser, new FileSystem(), ModuleLogger(nameof(ProctoringController)), nativeMethods, context.Server, text, uiFactory); var operation = new ProctoringOperation(actionCenter, context, controller, logger, taskbar, uiFactory); context.Proctoring = controller; return operation; } private IOperation BuildServerOperation() { var keyGenerator = new KeyGenerator(context.AppConfig, context.IntegrityModule, ModuleLogger(nameof(KeyGenerator))); var server = new ServerProxy(context.AppConfig, keyGenerator, ModuleLogger(nameof(ServerProxy)), systemInfo, userInfo, powerSupply, networkAdapter); var operation = new ServerOperation(context, logger, server); context.Server = server; return operation; } private IOperation BuildShellOperation() { var aboutNotification = new AboutNotification(context.AppConfig, text, uiFactory); var audio = new Audio(context.Settings.Audio, ModuleLogger(nameof(Audio))); var keyboard = new Keyboard(ModuleLogger(nameof(Keyboard))); var logNotification = new LogNotification(logger, text, uiFactory); var operation = new ShellOperation( actionCenter, audio, aboutNotification, context, keyboard, logger, logNotification, nativeMethods, networkAdapter, powerSupply, systemInfo, taskbar, taskview, text, uiFactory); context.Activators.Add(new ActionCenterKeyboardActivator(ModuleLogger(nameof(ActionCenterKeyboardActivator)), nativeMethods)); context.Activators.Add(new ActionCenterTouchActivator(ModuleLogger(nameof(ActionCenterTouchActivator)), nativeMethods)); context.Activators.Add(new TaskbarKeyboardActivator(ModuleLogger(nameof(TaskbarKeyboardActivator)), nativeMethods)); context.Activators.Add(new TaskviewKeyboardActivator(ModuleLogger(nameof(TaskviewKeyboardActivator)), nativeMethods)); context.Activators.Add(new TerminationActivator(ModuleLogger(nameof(TerminationActivator)), nativeMethods)); return operation; } private IFileSystemDialog BuildFileSystemDialog() { switch (uiMode) { case UserInterfaceMode.Mobile: return new Mobile.FileSystemDialogFactory(systemInfo, text); default: return new Desktop.FileSystemDialogFactory(systemInfo, text); } } private IMessageBox BuildMessageBox() { switch (uiMode) { case UserInterfaceMode.Mobile: return new Mobile.MessageBoxFactory(text); default: return new Desktop.MessageBoxFactory(text); } } private IUserInterfaceFactory BuildUserInterfaceFactory() { switch (uiMode) { case UserInterfaceMode.Mobile: return new Mobile.UserInterfaceFactory(text); default: return new Desktop.UserInterfaceFactory(text); } } private void UpdateAppConfig() { ClientController.UpdateAppConfig(); } private IModuleLogger ModuleLogger(string moduleInfo) { return new ModuleLogger(logger, moduleInfo); } } }