/* * dbMango * * Copyright 2025 Deutsche Bank AG * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; namespace Rms.Service.Bootstrap.Security; public static class ServiceCollectionExtensions { public static IServiceCollection ConfigureProtected(this IServiceCollection services, IConfigurationSection section) where TOptions : class, new() => services.AddSingleton(provider => { var logger = provider.GetRequiredService>(); var passwordManager = provider.GetRequiredService(); var configuration = provider.GetRequiredService(); section = new ProtectedConfigurationSection(passwordManager, configuration, section, logger); var options = section.Get() ?? new(); return Options.Create(options); }); private class ProtectedConfigurationSection( IPasswordManager passwordManager, IConfiguration config, IConfigurationSection section, ILogger logger) : IConfigurationSection { public IConfigurationSection GetSection(string key) => new ProtectedConfigurationSection(passwordManager, config, section.GetSection(key), logger); public IEnumerable GetChildren() => section.GetChildren() .Select(x => new ProtectedConfigurationSection(passwordManager, config, x, logger)); public IChangeToken GetReloadToken() => section.GetReloadToken(); public string? this[string key] { get => GetProtectedValue(section[key], key); set => section[key] = value; } public string Key => section.Key; public string Path => section.Path; public string? Value { get => GetProtectedValue(section.Value, Key); set => section.Value = value; } private string? GetProtectedValue(string? value, string key) { if (value == null) return null; // redirect first as parameter value may still be encrypted while (value.StartsWith("config:")) { var paramName = value["config:".Length..]; if (config[paramName] is { } configValue) { //logger.LogDebug($"Redirecting configuration item: Key=\"{key}\" Value=\"{configValue}\""); value = configValue; } else { logger.LogWarning($"Configuration item is not found: Key=\"{key}\" ParamName=\"{paramName}\""); break; } } if (value.StartsWith('*') || value.StartsWith('@') || value.StartsWith('#')) { logger.LogDebug($"Decrypting configuration item: Key=\"{key}\" Length={value.Length}"); return passwordManager.DecryptPassword(value); } return value; } } }