/* * 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.IO; using System.Linq; using SafeExamBrowser.Configuration.ConfigurationData; using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts.Cryptography; using SafeExamBrowser.Configuration.Contracts.DataFormats; using SafeExamBrowser.Configuration.Contracts.DataResources; using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Settings; namespace SafeExamBrowser.Configuration { public class ConfigurationRepository : IConfigurationRepository { private readonly ICertificateStore certificateStore; private readonly IList dataParsers; private readonly IList dataSerializers; private readonly DataMapper dataMapper; private readonly DataProcessor dataProcessor; private readonly DataValues dataValues; private readonly ILogger logger; private readonly IList resourceLoaders; private readonly IList resourceSavers; public ConfigurationRepository(ICertificateStore certificateStore, IModuleLogger logger) { this.certificateStore = certificateStore; this.logger = logger; dataParsers = new List(); dataSerializers = new List(); dataMapper = new DataMapper(); dataProcessor = new DataProcessor(); dataValues = new DataValues(); resourceLoaders = new List(); resourceSavers = new List(); } public SaveStatus ConfigureClientWith(Uri resource, PasswordParameters password = null) { logger.Info($"Attempting to configure local client with '{resource}'..."); try { TryLoadData(resource, out var data); using (data) { TryParseData(data, out var encryption, out var format, out var rawData, password); certificateStore.ExtractAndImportIdentities(rawData); encryption = DetermineEncryptionForClientConfiguration(rawData, encryption); var status = TrySerializeData(rawData, format, out var serialized, encryption); using (serialized) { if (status == SaveStatus.Success) { status = TrySaveData(new Uri(dataValues.GetAppDataFilePath()), serialized); } return status; } } } catch (Exception e) { logger.Error($"Unexpected error while trying to configure local client with '{resource}'!", e); return SaveStatus.UnexpectedError; } } public AppConfig InitializeAppConfig() { return dataValues.InitializeAppConfig(); } public SessionConfiguration InitializeSessionConfiguration() { return dataValues.InitializeSessionConfiguration(); } public AppSettings LoadDefaultSettings() { return dataValues.LoadDefaultSettings(); } public void Register(IDataParser parser) { dataParsers.Add(parser); } public void Register(IDataSerializer serializer) { dataSerializers.Add(serializer); } public void Register(IResourceLoader loader) { resourceLoaders.Add(loader); } public void Register(IResourceSaver saver) { resourceSavers.Add(saver); } public LoadStatus TryLoadSettings(Uri resource, out AppSettings settings, PasswordParameters password = null) { var status = default(LoadStatus); settings = LoadDefaultSettings(); dataProcessor.ProcessDefault(settings); logger.Info($"Initialized default settings, now attempting to load '{resource}'..."); try { status = TryLoadData(resource, out var stream); using (stream) { if (status == LoadStatus.Success) { status = TryParseData(stream, out _, out _, out var data, password); if (status == LoadStatus.Success) { dataMapper.Map(data, settings); dataProcessor.Process(data, settings); } } } } catch (Exception e) { status = LoadStatus.UnexpectedError; logger.Error($"Unexpected error while trying to load '{resource}'!", e); } return status; } private EncryptionParameters DetermineEncryptionForClientConfiguration(IDictionary data, EncryptionParameters encryption) { var hasKey = data.TryGetValue(Keys.ConfigurationFile.KeepClientConfigEncryption, out var value); var useDefaultEncryption = value is bool keepEncryption && !keepEncryption; if (!hasKey || (hasKey && useDefaultEncryption)) { encryption = new PasswordParameters { Password = string.Empty, IsHash = true }; } return encryption; } private LoadStatus TryLoadData(Uri resource, out Stream data) { var status = LoadStatus.NotSupported; var resourceLoader = resourceLoaders.FirstOrDefault(l => l.CanLoad(resource)); data = default; if (resourceLoader != null) { status = resourceLoader.TryLoad(resource, out data); logger.Info($"Tried to load data from '{resource}' using {resourceLoader.GetType().Name} -> Result: {status}."); } else { logger.Warn($"No resource loader found for '{resource}'!"); } return status; } private LoadStatus TryParseData(Stream data, out EncryptionParameters encryption, out FormatType format, out IDictionary rawData, PasswordParameters password = null) { var parser = dataParsers.FirstOrDefault(p => p.CanParse(data)); var status = LoadStatus.NotSupported; encryption = default; format = default; rawData = default(Dictionary); if (parser != null) { var result = parser.TryParse(data, password); encryption = result.Encryption; format = result.Format; rawData = result.RawData; status = result.Status; logger.Info($"Tried to parse data from '{data}' using {parser.GetType().Name} -> Result: {status}."); } else { logger.Warn($"No data parser found which can parse '{data}'!"); } return status; } private SaveStatus TrySaveData(Uri destination, Stream data) { var status = SaveStatus.NotSupported; var resourceSaver = resourceSavers.FirstOrDefault(s => s.CanSave(destination)); if (resourceSaver != null) { status = resourceSaver.TrySave(destination, data); logger.Info($"Tried to save data as '{destination}' using {resourceSaver.GetType().Name} -> Result: {status}."); } else { logger.Warn($"No resource saver found for '{destination}'!"); } return status; } private SaveStatus TrySerializeData(IDictionary data, FormatType format, out Stream serialized, EncryptionParameters encryption = null) { var serializer = dataSerializers.FirstOrDefault(s => s.CanSerialize(format)); var status = SaveStatus.NotSupported; serialized = default; if (serializer != null) { var result = serializer.TrySerialize(data, encryption); serialized = result.Data; status = result.Status; logger.Info($"Tried to serialize data as '{format}' using {serializer.GetType().Name} -> Result: {status}."); } else { logger.Error($"No data serializer found which can serialize '{format}'!"); } return status; } } }