Plugin: Handling custom configurations

This commit is contained in:
Zoe Roux 2021-08-13 18:46:07 +02:00
parent d1b3769f4f
commit 16eca6da8e
6 changed files with 89 additions and 17 deletions

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Autofac;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;
@ -40,7 +41,9 @@ namespace Kyoo.Authentication
/// <inheritdoc />
public Dictionary<string, Type> Configuration => new()
{
{ AuthenticationOption.Path, typeof(AuthenticationOption) }
{ AuthenticationOption.Path, typeof(AuthenticationOption) },
{ PermissionOption.Path, typeof(PermissionOption) },
{ CertificateOption.Path, typeof(CertificateOption) }
};
@ -75,6 +78,18 @@ namespace Kyoo.Authentication
_environment = environment;
}
/// <inheritdoc />
public void Configure(ContainerBuilder builder)
{
builder.RegisterType<PermissionValidatorFactory>().As<IPermissionValidator>().SingleInstance();
DefaultCorsPolicyService cors = new(_loggerFactory.CreateLogger<DefaultCorsPolicyService>())
{
AllowedOrigins = { new Uri(_configuration.GetPublicUrl()).GetLeftPart(UriPartial.Authority) }
};
builder.RegisterInstance(cors).As<ICorsPolicyService>().SingleInstance();
}
/// <inheritdoc />
public void Configure(IServiceCollection services)
{
@ -115,13 +130,6 @@ namespace Kyoo.Authentication
options.Audience = "kyoo";
options.RequireHttpsMetadata = false;
});
services.AddSingleton<IPermissionValidator, PermissionValidatorFactory>();
DefaultCorsPolicyService cors = new(_loggerFactory.CreateLogger<DefaultCorsPolicyService>())
{
AllowedOrigins = {new Uri(publicUrl).GetLeftPart(UriPartial.Authority)}
};
services.AddSingleton<ICorsPolicyService>(cors);
}
/// <inheritdoc />

View File

@ -34,7 +34,7 @@ namespace Kyoo.Authentication
X509Certificate2 certificate = GetCertificate(options);
builder.AddSigningCredential(certificate);
if (certificate.NotAfter.AddDays(7) <= DateTime.UtcNow)
if (certificate.NotAfter.AddDays(-7) <= DateTime.UtcNow)
{
Console.WriteLine("Signin certificate will expire soon, renewing it.");
if (File.Exists(options.OldFile))
@ -67,7 +67,7 @@ namespace Kyoo.Authentication
/// <returns>The loaded certificate</returns>
private static X509Certificate2 GetExistingCredential(string file, string password)
{
return new(file, password,
return new X509Certificate2(file, password,
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable

View File

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Models;
using Kyoo.Models.Exceptions;
@ -27,6 +28,15 @@ namespace Kyoo.Controllers
/// <param name="path">The root path of the editable configuration. It should not be a nested type.</param>
void AddUntyped(string path);
/// <summary>
/// An helper method of <see cref="AddTyped{T}"/> and <see cref="AddUntyped"/>.
/// This register a typed value if <paramref name="type"/> is not <c>null</c> and registers an untyped type
/// if <paramref name="type"/> is <c>null</c>.
/// </summary>
/// <param name="path">The root path of the editable configuration. It should not be a nested type.</param>
/// <param name="type">The type of the configuration or null.</param>
void Register(string path, [CanBeNull] Type type);
/// <summary>
/// Get the value of a setting using it's path.
/// </summary>

View File

@ -44,8 +44,12 @@ namespace Kyoo.Controllers
/// <summary>
/// A list of types that will be available via the IOptions interfaces and will be listed inside
/// an IConfiguration. If a field should be loosely typed, <see cref="Dictionary{TKey,TValue}"/> or <c>null</c>
/// an IConfiguration.
///
/// If a field should be loosely typed, <see cref="Dictionary{TKey,TValue}"/> or <c>null</c>
/// can be specified.
/// WARNING: null means an unmanaged type that won't be editable. This can be used
/// for external libraries or variable arguments.
/// </summary>
/// <remarks>
/// All use of the configuration must be specified here and not registered elsewhere, if a type is registered

View File

@ -51,6 +51,21 @@ namespace Kyoo.Controllers
_references.Add(config.Path, config.Type);
}
/// <inheritdoc />
public void Register(string path, Type type)
{
if (type == null)
{
ConfigurationReference config = ConfigurationReference.CreateUntyped(path);
_references.Add(config.Path, config.Type);
}
else
{
foreach (ConfigurationReference confRef in ConfigurationReference.CreateReference(path, type))
_references.Add(confRef.Path, confRef.Type);
}
}
/// <summary>
/// Get the type of the resource at the given path
/// </summary>

View File

@ -33,13 +33,22 @@ namespace Kyoo
/// </summary>
private readonly IPluginManager _plugins;
/// <summary>
/// The configuration used to register <see cref="IOptions{TOptions}"/> and so on for plugin's specified types.
/// </summary>
private readonly IConfiguration _configuration;
/// <summary>
/// Created from the DI container, those services are needed to load information and instantiate plugins.s
/// </summary>
/// <param name="plugins">The plugin manager to use to load new plugins and configure the host.</param>
public PluginsStartup(IPluginManager plugins)
/// <param name="configuration">
/// The configuration used to register <see cref="IOptions{TOptions}"/> and so on for plugin's specified types.
/// </param>
public PluginsStartup(IPluginManager plugins, IConfiguration configuration)
{
_plugins = plugins;
_configuration = configuration;
_plugins.LoadPlugins(
typeof(CoreModule),
typeof(AuthenticationModule),
@ -58,9 +67,9 @@ namespace Kyoo
{
services.AddMvc().AddControllersAsServices();
services.AddSpaStaticFiles(configuration =>
services.AddSpaStaticFiles(x =>
{
configuration.RootPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot");
x.RootPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "wwwroot");
});
services.AddResponseCompression(x =>
{
@ -70,6 +79,18 @@ namespace Kyoo
services.AddHttpClient();
_plugins.ConfigureServices(services);
IEnumerable<KeyValuePair<string, Type>> configTypes = _plugins.GetAllPlugins()
.SelectMany(x => x.Configuration)
.Where(x => x.Value != null);
foreach ((string path, Type type) in configTypes)
{
Utility.RunGenericMethod<object>(
typeof(OptionsConfigurationServiceCollectionExtensions),
nameof(OptionsConfigurationServiceCollectionExtensions.Configure),
type,
services, _configuration.GetSection(path)
);
}
}
/// <summary>
@ -89,7 +110,7 @@ namespace Kyoo
/// </summary>
/// <param name="app">The asp net host to configure</param>
/// <param name="env">The host environment (is the app in development mode?)</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider provider)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IConfigurationManager config, ILifetimeScope container)
{
if (!env.IsDevelopment())
app.UseSpaStaticFiles();
@ -111,9 +132,23 @@ namespace Kyoo
IEnumerable<IStartupAction> steps = _plugins.GetAllPlugins()
.SelectMany(x => x.ConfigureSteps)
.OrderByDescending(x => x.Priority);
using ILifetimeScope scope = container.BeginLifetimeScope(x =>
x.RegisterInstance(app).SingleInstance().ExternallyOwned());
IServiceProvider provider = scope.Resolve<IServiceProvider>();
foreach (IStartupAction step in steps)
step.Run(provider);
IEnumerable<KeyValuePair<string, Type>> pluginConfig = _plugins.GetAllPlugins()
.SelectMany(x => x.Configuration)
.GroupBy(x => x.Key.Split(':').First())
.Select(x => x
.OrderBy(y => y.Key.Length)
.First()
);
foreach ((string path, Type type) in pluginConfig)
config.Register(path, type);
app.UseSpa(spa =>
{
spa.Options.SourcePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Kyoo.WebApp");
@ -148,7 +183,7 @@ namespace Kyoo
Options.Create(host.Configuration.GetSection(BasicOptions.Path).Get<BasicOptions>()),
logger.CreateLogger<PluginManager>()
);
return new PluginsStartup(plugins);
return new PluginsStartup(plugins, host.Configuration);
}
/// <summary>