diff --git a/src/ContentRepository/Configuration/SettingsConfigurationProvider.cs b/src/ContentRepository/Configuration/SettingsConfigurationProvider.cs new file mode 100644 index 000000000..a29125f96 --- /dev/null +++ b/src/ContentRepository/Configuration/SettingsConfigurationProvider.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using SenseNet.ContentRepository; + +namespace SenseNet.Configuration +{ + internal class SettingsConfigurationProvider : JsonConfigurationProvider + { + public SettingsConfigurationProvider(SettingsConfigurationSource source): base(source) + {} + + public override void Load() + { + if (!(Providers.Instance?.SearchManager?.ContentQueryIsAllowed ?? false)) + { + Data = new Dictionary(); + return; + } + + var settings = SettingsCache.Instance.GetSettings(); + + // build a JSON file containing all settings inside a 'sensenet' property + var allSettingsObject = new JObject(); + var snObject = new JObject(); + allSettingsObject.Add("sensenet", snObject); + + //UNDONE: filter settings that must not be treated as configuration for security reasons. Whitelist? + foreach (var setting in settings) + { + var jo = setting.BinaryAsJObject; + if (jo == null) + continue; + + var propertyName = setting.Name.Replace(".settings", string.Empty); + snObject.Add(propertyName, jo); + } + + var allSettingsText = JsonConvert.SerializeObject(allSettingsObject, Formatting.Indented); + + using var settingsStream = RepositoryTools.GetStreamFromString(allSettingsText); + base.Load(settingsStream); + + OnReload(); + } + } + + internal class SettingsConfigurationSource : JsonConfigurationSource + { + public override IConfigurationProvider Build(IConfigurationBuilder builder) + { + EnsureDefaults(builder); + return new SettingsConfigurationProvider(this); + } + } + + //UNDONE: temp class for testing the feature + public class PortalSettingsOptions + { + public int BinaryHandlerClientCacheMaxAge { get; set; } + public string PermittedAppsWithoutOpenPermission { get; set; } + public string[] AllowedOriginDomains { get; set; } + } + + public static class ConfigurationBuilderExtensions + { + public static IConfigurationBuilder AddSenseNetSettingsConfiguration( + this IConfigurationBuilder builder) + { + // access other config values if necessary + + //var tempConfig = builder.Build(); + //var connectionString = + // tempConfig.GetConnectionString("WidgetConnectionString"); + + return builder.Add(new SettingsConfigurationSource()); + } + } +} diff --git a/src/ContentRepository/SenseNet.ContentRepository.csproj b/src/ContentRepository/SenseNet.ContentRepository.csproj index 886807775..a594b8381 100644 --- a/src/ContentRepository/SenseNet.ContentRepository.csproj +++ b/src/ContentRepository/SenseNet.ContentRepository.csproj @@ -36,6 +36,7 @@ + diff --git a/src/ContentRepository/Settings.cs b/src/ContentRepository/Settings.cs index 268297d82..b519766aa 100644 --- a/src/ContentRepository/Settings.cs +++ b/src/ContentRepository/Settings.cs @@ -164,7 +164,7 @@ protected XmlDocument BinaryAsXml /// /// Gets data as a if it can be parsed, or null. /// - protected JObject BinaryAsJObject + protected internal JObject BinaryAsJObject { get { diff --git a/src/ContentRepository/SettingsCache.cs b/src/ContentRepository/SettingsCache.cs index f9550722d..855a7890f 100644 --- a/src/ContentRepository/SettingsCache.cs +++ b/src/ContentRepository/SettingsCache.cs @@ -1,6 +1,9 @@ using SenseNet.ContentRepository.Storage; using System; using System.Collections.Generic; +using System.Linq; +using SenseNet.Configuration; +using SenseNet.ContentRepository.Storage.Events; using SenseNet.Diagnostics; namespace SenseNet.ContentRepository @@ -59,6 +62,33 @@ public static T GetSettingsByName(string settingsName, string contextPath) wh return null; } + internal Settings[] GetSettings() + { + return Items.Keys.Select(Node.Load).ToArray(); + } + protected override void Invalidate() + { + base.Invalidate(); + + Providers.Instance.OnSettingsReloaded(); + } + + protected override void OnNodeModified(object sender, NodeEventArgs e) + { + //UNDONE: make sure settings-config invalidation happens + // only once but it happens on all web nodes. + base.OnNodeModified(sender, e); + + if (e.SourceNode is not Settings) + return; + + // invalidate only if the base method did not do it to avoid double reload + if (e.OriginalSourcePath == e.SourceNode.Path || !IsSubtreeContaining(e.OriginalSourcePath)) + { + Providers.Instance.OnSettingsReloaded(); + } + } + protected override List LoadItems() { var settings = Tools.LoadItemsByContentType("Settings"); diff --git a/src/Services.Core/ServicesExtensions.cs b/src/Services.Core/ServicesExtensions.cs index 95c24e0ed..6005aaa31 100644 --- a/src/Services.Core/ServicesExtensions.cs +++ b/src/Services.Core/ServicesExtensions.cs @@ -65,7 +65,10 @@ internal static IServiceCollection ConfigureSenseNet(this IServiceCollection ser services.Configure(configuration.GetSection("sensenet:security:messaging")); services.Configure(configuration.GetSection("sensenet:cryptography")); services.Configure(options => {}); - + + // usage: GetService> + services.Configure(configuration.GetSection("sensenet:Portal")); + services.ConfigureConnectionStrings(configuration); return services; diff --git a/src/Storage/Configuration/Providers.cs b/src/Storage/Configuration/Providers.cs index ed740472e..f1906bc45 100644 --- a/src/Storage/Configuration/Providers.cs +++ b/src/Storage/Configuration/Providers.cs @@ -13,6 +13,7 @@ using SenseNet.Security.Messaging; using SenseNet.Tools; using System.Linq; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using SenseNet.ContentRepository.Search.Indexing; using SenseNet.ContentRepository.Storage.AppModel; @@ -81,6 +82,13 @@ public Providers(IServiceProvider services) SearchManager = services.GetService(); IndexManager = services.GetService(); IndexPopulator = services.GetService(); + + //UNDONE: find our service correctly (marker interface?) + var conf = services.GetService() as ConfigurationRoot; + var settingsConf = + conf.Providers.LastOrDefault(c => c.GetType().FullName.EndsWith("SettingsConfigurationProvider")); + + SettingsReloaded += (sender, args) => settingsConf.Load(); } /// @@ -119,6 +127,13 @@ public Providers(IServiceProvider services) public IIndexManager IndexManager { get; } public IIndexPopulator IndexPopulator { get; } + private event EventHandler SettingsReloaded; + + public void OnSettingsReloaded() + { + SettingsReloaded?.Invoke(this, EventArgs.Empty); + } + /* ========================================================= Need to refactor */ public IEventLogger EventLogger { get; set; } diff --git a/src/WebApps/SnWebApplication.Api.Sql.TokenAuth/Program.cs b/src/WebApps/SnWebApplication.Api.Sql.TokenAuth/Program.cs index 65464f8c8..6b038ae19 100644 --- a/src/WebApps/SnWebApplication.Api.Sql.TokenAuth/Program.cs +++ b/src/WebApps/SnWebApplication.Api.Sql.TokenAuth/Program.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using SenseNet.Configuration; using Serilog; namespace SnWebApplication.Api.Sql.TokenAuth @@ -14,6 +15,10 @@ public static void Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((_, configuration) => + { + configuration.AddSenseNetSettingsConfiguration(); + }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup()