/* * Copyright (c) 2025 ETH Zürich, IT Services * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ using System; using System.Collections.Generic; using SafeExamBrowser.Communication.Contracts; using SafeExamBrowser.Communication.Contracts.Hosts; using SafeExamBrowser.Communication.Contracts.Proxies; using SafeExamBrowser.Communication.Hosts; using SafeExamBrowser.Communication.Proxies; using SafeExamBrowser.Configuration; using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts.Integrity; 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.Operations; using SafeExamBrowser.Core.ResponsibilityModel; 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.Bootstrap; using SafeExamBrowser.Runtime.Operations.Session; using SafeExamBrowser.Runtime.Responsibilities; using SafeExamBrowser.Server; using SafeExamBrowser.Settings.Logging; using SafeExamBrowser.SystemComponents; using SafeExamBrowser.SystemComponents.Contracts; using SafeExamBrowser.SystemComponents.Contracts.Registry; using SafeExamBrowser.SystemComponents.Registry; using SafeExamBrowser.UserInterface.Contracts; using SafeExamBrowser.UserInterface.Contracts.MessageBox; using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Desktop; using SafeExamBrowser.WindowsApi; using SafeExamBrowser.WindowsApi.Desktops; using SafeExamBrowser.WindowsApi.Processes; namespace SafeExamBrowser.Runtime { internal class CompositionRoot { private const int FIVE_SECONDS = 5000; private const int THIRTY_SECONDS = 30000; private AppConfig appConfig; private IConfigurationRepository repository; private ILogger logger; private ISystemInfo systemInfo; private IText text; internal RuntimeController RuntimeController { get; private set; } internal void BuildObjectGraph(Action shutdown) { logger = new Logger(); InitializeConfiguration(); InitializeLogging(); InitializeText(); var uiFactory = new UserInterfaceFactory(text); var context = new RuntimeContext(); var integrityModule = new IntegrityModule(appConfig, ModuleLogger(nameof(IntegrityModule))); var messageBox = new MessageBoxFactory(text); var registry = new Registry(ModuleLogger(nameof(Registry))); var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS); var runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig); var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime); var splashScreen = uiFactory.CreateSplashScreen(appConfig); systemInfo = new SystemInfo(registry); var bootstrapSequence = BuildBootstrapOperations(integrityModule, runtimeHost, splashScreen); var sessionSequence = BuildSessionOperations(integrityModule, messageBox, registry, runtimeHost, runtimeWindow, serviceProxy, context, uiFactory); var responsibilities = BuildResponsibilities(messageBox, runtimeHost, runtimeWindow, serviceProxy, context, sessionSequence, shutdown, splashScreen); context.Responsibilities = responsibilities; RuntimeController = new RuntimeController(logger, bootstrapSequence, responsibilities, context, runtimeWindow, splashScreen); } private BootstrapOperationSequence BuildBootstrapOperations(IIntegrityModule integrityModule, IRuntimeHost runtimeHost, ISplashScreen splashScreen) { var operations = new Queue(); operations.Enqueue(new I18nOperation(logger, text)); operations.Enqueue(new CommunicationHostOperation(runtimeHost, logger)); operations.Enqueue(new ApplicationIntegrityOperation(integrityModule, logger)); return new BootstrapOperationSequence(logger, operations, splashScreen); } private ResponsibilityCollection BuildResponsibilities( IMessageBox messageBox, IRuntimeHost runtimeHost, IRuntimeWindow runtimeWindow, IServiceProxy serviceProxy, RuntimeContext runtimeContext, SessionOperationSequence sessionSequence, Action shutdown, ISplashScreen splashScreen) { var responsibilities = new Queue(); responsibilities.Enqueue(new ClientResponsibility(ModuleLogger(nameof(ClientResponsibility)), messageBox, runtimeContext, runtimeWindow, shutdown)); responsibilities.Enqueue(new CommunicationResponsibility(ModuleLogger(nameof(CommunicationResponsibility)), runtimeContext, runtimeHost, shutdown)); responsibilities.Enqueue(new ErrorMessageResponsibility(appConfig, ModuleLogger(nameof(ErrorMessageResponsibility)), messageBox, runtimeContext, splashScreen, text)); responsibilities.Enqueue(new ServiceResponsibility(ModuleLogger(nameof(ServiceResponsibility)), messageBox, runtimeContext, runtimeWindow, serviceProxy, shutdown)); responsibilities.Enqueue(new SessionResponsibility(appConfig, ModuleLogger(nameof(SessionResponsibility)), messageBox, runtimeContext, runtimeWindow, sessionSequence, shutdown, text)); return new ResponsibilityCollection(logger, responsibilities); } private SessionOperationSequence BuildSessionOperations( IIntegrityModule integrityModule, IMessageBox messageBox, IRegistry registry, IRuntimeHost runtimeHost, IRuntimeWindow runtimeWindow, IServiceProxy serviceProxy, RuntimeContext runtimeContext, IUserInterfaceFactory uiFactory) { var args = Environment.GetCommandLineArgs(); var fileSystem = new FileSystem(); var nativeMethods = new NativeMethods(); var operations = new Queue(); var userInfo = new UserInfo(ModuleLogger(nameof(UserInfo))); var clientBridge = new ClientBridge(runtimeHost, runtimeContext); var dependencies = new Dependencies(clientBridge, logger, messageBox, runtimeWindow, runtimeContext, text); 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 keyGenerator = new KeyGenerator(appConfig, integrityModule, ModuleLogger(nameof(KeyGenerator))); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), ModuleLogger(nameof(ProxyFactory))); var remoteSessionDetector = new RemoteSessionDetector(ModuleLogger(nameof(RemoteSessionDetector))); var sentinel = new SystemSentinel(ModuleLogger(nameof(SystemSentinel)), nativeMethods, registry); var server = new ServerProxy(appConfig, keyGenerator, ModuleLogger(nameof(ServerProxy)), systemInfo, userInfo); var virtualMachineDetector = new VirtualMachineDetector(ModuleLogger(nameof(VirtualMachineDetector)), registry, systemInfo); operations.Enqueue(new SessionInitializationOperation(dependencies, fileSystem, repository)); operations.Enqueue(new ConfigurationOperation(args, dependencies, new FileSystem(), new HashAlgorithm(), repository, uiFactory)); operations.Enqueue(new ServerOperation(dependencies, fileSystem, repository, server, uiFactory)); operations.Enqueue(new VersionRestrictionOperation(dependencies)); operations.Enqueue(new DisclaimerOperation(dependencies)); operations.Enqueue(new RemoteSessionOperation(dependencies, remoteSessionDetector)); operations.Enqueue(new SessionIntegrityOperation(dependencies, sentinel)); operations.Enqueue(new VirtualMachineOperation(dependencies, virtualMachineDetector)); operations.Enqueue(new DisplayMonitorOperation(dependencies, displayMonitor)); operations.Enqueue(new ServiceOperation(dependencies, runtimeHost, serviceProxy, THIRTY_SECONDS, userInfo)); operations.Enqueue(new ClientTerminationOperation(dependencies, processFactory, proxyFactory, runtimeHost, THIRTY_SECONDS)); operations.Enqueue(new KioskModeOperation(dependencies, desktopFactory, desktopMonitor, explorerShell, processFactory)); operations.Enqueue(new ClientOperation(dependencies, processFactory, proxyFactory, runtimeHost, THIRTY_SECONDS)); operations.Enqueue(new SessionActivationOperation(dependencies)); return new SessionOperationSequence(logger, operations, runtimeWindow); } 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))); repository = new ConfigurationRepository(certificateStore, repositoryLogger); appConfig = repository.InitializeAppConfig(); repository.Register(new BinaryParser( compressor, new HashAlgorithm(), ModuleLogger(nameof(BinaryParser)), passwordEncryption, publicKeyEncryption, symmetricEncryption, xmlParser)); repository.Register(new BinarySerializer( compressor, ModuleLogger(nameof(BinarySerializer)), passwordEncryption, publicKeyEncryption, symmetricEncryption, xmlSerializer)); repository.Register(new XmlParser(compressor, ModuleLogger(nameof(XmlParser)))); repository.Register(new XmlSerializer(ModuleLogger(nameof(XmlSerializer)))); repository.Register(new FileResourceLoader(ModuleLogger(nameof(FileResourceLoader)))); repository.Register(new FileResourceSaver(ModuleLogger(nameof(FileResourceSaver)))); repository.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); } } }