diff options
Diffstat (limited to 'lib')
14 files changed, 397 insertions, 164 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj b/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj index 71656b9..6409083 100644 --- a/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj +++ b/lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj @@ -47,7 +47,7 @@ <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.23" /> + <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.24" /> </ItemGroup> <ItemGroup> diff --git a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj index d7c422e..cbd4029 100644 --- a/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj +++ b/lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj @@ -38,8 +38,8 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.23" /> - <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.23" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.24" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.24" /> <PackageReference Include="MySqlConnector" Version="2.2.7" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2" /> </ItemGroup> diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs index ccb2341..06c3ee4 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs @@ -205,11 +205,11 @@ namespace VNLib.Plugins.Extensions.Loading //Get the property if(!config.TryGetValue(property, out JsonElement el)) { - throw new KeyNotFoundException($"Missing required configuration property '{property}'"); + throw new KeyNotFoundException($"Missing required configuration property '{property}' in config {config.ScopeName}"); } //Even if the getter returns null, throw - return getter(el) ?? throw new KeyNotFoundException($"Missing required configuration property '{property}'"); + return getter(el) ?? throw new KeyNotFoundException($"Missing required configuration property '{property}' in config {config.ScopeName}"); } /// <summary> @@ -301,12 +301,21 @@ namespace VNLib.Plugins.Extensions.Loading /// <typeparam name="T"></typeparam> /// <param name="plugin"></param> /// <returns>True if the plugin config contains the require configuration property</returns> - public static bool HasConfigForType<T>(this PluginBase plugin) + public static bool HasConfigForType<T>(this PluginBase plugin) => HasConfigForType(plugin, typeof(T)); + + + /// <summary> + /// Determines if the current plugin configuration contains the require properties to initialize + /// the type + /// </summary> + /// <param name="type">The type to get the configuration for</param> + /// <param name="plugin"></param> + /// <returns>True if the plugin config contains the require configuration property</returns> + public static bool HasConfigForType(this PluginBase plugin, Type type) { - Type type = typeof(T); ConfigurationNameAttribute? configName = type.GetCustomAttribute<ConfigurationNameAttribute>(); //See if the plugin contains a configuration varables - return configName != null && ( + return configName != null && ( plugin.PluginConfig.TryGetProperty(configName.ConfigVarName, out _) || plugin.HostConfig.TryGetProperty(configName.ConfigVarName, out _) ); diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeAmbiguousMatchException.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeAmbiguousMatchException.cs new file mode 100644 index 0000000..6118b64 --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeAmbiguousMatchException.cs @@ -0,0 +1,44 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: ConcreteTypeAmbiguousMatchException.cs +* +* ConcreteTypeAmbiguousMatchException.cs is part of VNLib.Plugins.Extensions.Loading +* which is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Extensions.Loading is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Plugins.Extensions.Loading +{ + /// <summary> + /// Raised when a concrete type is found but is ambiguous because more than one + /// type implements the desired abstract type. + /// </summary> + public sealed class ConcreteTypeAmbiguousMatchException : ConcreteTypeException + { + public ConcreteTypeAmbiguousMatchException(string message) : base(message) + { } + + public ConcreteTypeAmbiguousMatchException(string message, Exception innerException) : base(message, innerException) + { } + + public ConcreteTypeAmbiguousMatchException() + { } + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeException.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeException.cs new file mode 100644 index 0000000..29f080a --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeException.cs @@ -0,0 +1,44 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: ConcreteTypeException.cs +* +* ConcreteTypeException.cs is part of VNLib.Plugins.Extensions.Loading +* which is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Extensions.Loading is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Plugins.Extensions.Loading +{ + /// <summary> + /// Base class for concrete type loading exceptions. Raised when searching + /// for a concrete type fails. + /// </summary> + public class ConcreteTypeException : TypeLoadException + { + public ConcreteTypeException() : base() + { } + + public ConcreteTypeException(string? message) : base(message) + { } + + public ConcreteTypeException(string? message, Exception? innerException) : base(message, innerException) + { } + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeNotFoundException.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeNotFoundException.cs new file mode 100644 index 0000000..523726b --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeNotFoundException.cs @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: ConcreteTypeNotFoundException.cs +* +* ConcreteTypeNotFoundException.cs is part of VNLib.Plugins.Extensions.Loading +* which is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Extensions.Loading is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Plugins.Extensions.Loading +{ + /// <summary> + /// The requested concrete type was not found in the assembly + /// </summary> + public sealed class ConcreteTypeNotFoundException : ConcreteTypeException + { + public ConcreteTypeNotFoundException(string message) : base(message) + { } + public ConcreteTypeNotFoundException(string message, Exception innerException) : base(message, innerException) + { } + public ConcreteTypeNotFoundException() + { } + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationValidationException.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationValidationException.cs index ebf4d9e..ebf4d9e 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationValidationException.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationValidationException.cs diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/MissingDependencyException.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/MissingDependencyException.cs new file mode 100644 index 0000000..b07badc --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/MissingDependencyException.cs @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: MissingDependencyException.cs +* +* MissingDependencyException.cs is part of VNLib.Plugins.Extensions.Loading +* which is part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Extensions.Loading is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Plugins.Extensions.Loading +{ + /// <summary> + /// Raised when a required dependency is missing and the program cannot continue + /// </summary> + public sealed class MissingDependencyException : Exception + { + public MissingDependencyException(string message) : base(message) + { } + public MissingDependencyException(string message, Exception innerException) : base(message, innerException) + { } + public MissingDependencyException() + { } + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ExternServiceAttribute.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ExternServiceAttribute.cs new file mode 100644 index 0000000..369b4bb --- /dev/null +++ b/lib/VNLib.Plugins.Extensions.Loading/src/ExternServiceAttribute.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Plugins.Extensions.Loading +* File: ExternServiceAttribute.cs +* +* ExternServiceAttribute.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Plugins.Extensions.Loading is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* VNLib.Plugins.Extensions.Loading is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Plugins.Extensions.Loading +{ + /// <summary> + /// Declares a class as an external service provider. + /// <para> + /// If an assembly contains multiple classes that have the same + /// base type as a desired type, this attribute can be declared + /// to indicate the type takes precedence over other types. + /// </para> + /// </summary> + [AttributeUsage(AttributeTargets.Class)] + public sealed class ExternServiceAttribute: Attribute + { + public ExternServiceAttribute() + { } + } +} diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs index 207aeed..fa39b44 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs @@ -31,57 +31,17 @@ using System.Runtime.Loader; using System.Threading.Tasks; using System.Collections.Generic; using System.ComponentModel.Design; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; using VNLib.Utils.Logging; +using VNLib.Utils.Resources; using VNLib.Utils.Extensions; namespace VNLib.Plugins.Extensions.Loading { - /// <summary> - /// Base class for concrete type loading exceptions. Raised when searching - /// for a concrete type fails. - /// </summary> - public class ConcreteTypeException : TypeLoadException - { - public ConcreteTypeException() : base() - { } - - public ConcreteTypeException(string? message) : base(message) - { } - - public ConcreteTypeException(string? message, Exception? innerException) : base(message, innerException) - { } - } - - /// <summary> - /// Raised when a concrete type is found but is ambiguous because more than one - /// type implements the desired abstract type. - /// </summary> - public sealed class ConcreteTypeAmbiguousMatchException : ConcreteTypeException - { - public ConcreteTypeAmbiguousMatchException(string message) : base(message) - { } - - public ConcreteTypeAmbiguousMatchException(string message, Exception innerException) : base(message, innerException) - { } - - public ConcreteTypeAmbiguousMatchException() - { } - } - - /// <summary> - /// The requested concrete type was not found in the assembly - /// </summary> - public sealed class ConcreteTypeNotFoundException : ConcreteTypeException - { - public ConcreteTypeNotFoundException(string message) : base(message) - { } - public ConcreteTypeNotFoundException(string message, Exception innerException) : base(message, innerException) - { } - public ConcreteTypeNotFoundException() - { } - } /// <summary> /// Provides common loading (and unloading when required) extensions for plugins @@ -102,7 +62,9 @@ namespace VNLib.Plugins.Extensions.Loading * Plugin local cache used for storing singletons for a plugin instance */ private static readonly ConditionalWeakTable<PluginBase, PluginLocalCache> _localCache = new(); - + private static readonly ConcurrentDictionary<string, ManagedLibrary> _assemblyCache = new(); + + /// <summary> /// Gets a previously cached service singleton for the desired plugin /// </summary> @@ -189,6 +151,10 @@ namespace VNLib.Plugins.Extensions.Loading //Get the plugin's load context if not explicitly supplied explictAlc ??= GetPluginLoadContext(); + if (plugin.IsDebug()) + { + plugin.Log.Verbose("Loading assembly {asm}: from file {file}", assemblyName, asmFile); + } //Load the assembly return AssemblyLoader<T>.Load(asmFile, explictAlc, plugin.UnloadToken); } @@ -375,6 +341,52 @@ namespace VNLib.Plugins.Extensions.Loading } /// <summary> + /// Creates a new instance of the desired service type from an external assembly and + /// caches the loaded assembly so it's never loaded more than once. Managed assembly + /// life cycles are managed by the plugin. Instances are treated as services and + /// their service hooks will be called like any internal service. + /// </summary> + /// <typeparam name="T">The service type, may be an interface or abstract type</typeparam> + /// <param name="plugin"></param> + /// <param name="assemblyDllName">The name of the assembly that contains the desired type to search for</param> + /// <param name="search">The directory search method</param> + /// <param name="defaultCtx">A <see cref="AssemblyLoadContext"/> to load the assembly into. Defaults to the plugins current ALC</param> + /// <returns>A new instance of the desired service type </returns> + /// <exception cref="TypeLoadException"></exception> + public static T CreateServiceExternal<T>( + this PluginBase plugin, + string assemblyDllName, + SearchOption search = SearchOption.AllDirectories, + AssemblyLoadContext? defaultCtx = null + ) + { + /* + * Get or create the library for the assembly path, but only load it once + * Loading it on the plugin will also cause it be cleaned up when the plugin + * is unloaded. + */ + ManagedLibrary manLib = _assemblyCache.GetOrAdd(assemblyDllName, (name) => plugin.LoadAssembly<T>(name, search, defaultCtx)); + Type[] matchingTypes = manLib.TryGetAllMatchingTypes<T>().ToArray(); + + //try to get the first type that has the extern attribute, or fall back to the first public & concrete type + Type? exported = matchingTypes.FirstOrDefault(t => t.GetCustomAttribute<ExternServiceAttribute>() != null) + ?? matchingTypes.Where(t => !t.IsAbstract && t.IsPublic).FirstOrDefault(); + + _ = exported ?? throw new TypeLoadException($"The desired external asset type {typeof(T).Name} is not exported as part of the assembly {manLib.Assembly.FullName}"); + + //Try to get a configuration for the exported type + if (plugin.HasConfigForType(exported)) + { + //Get the config for the type and create the service + return (T)CreateService(plugin, exported, plugin.GetConfigForType(exported)); + } + + //Create new instance of the desired type + return (T)CreateService(plugin, exported, null); + } + + + /// <summary> /// <para> /// Gets or inializes a singleton service of the desired type. /// </para> @@ -395,11 +407,7 @@ namespace VNLib.Plugins.Extensions.Loading /// <exception cref="EntryPointNotFoundException"></exception> /// <exception cref="ConcreteTypeNotFoundException"></exception> /// <exception cref="ConcreteTypeAmbiguousMatchException"></exception> - public static T GetOrCreateSingleton<T>(this PluginBase plugin) - { - //Add service to service continer - return GetOrCreateSingleton(plugin, CreateService<T>); - } + public static T GetOrCreateSingleton<T>(this PluginBase plugin) => GetOrCreateSingleton(plugin, CreateService<T>); /// <summary> /// <para> @@ -423,11 +431,8 @@ namespace VNLib.Plugins.Extensions.Loading /// <exception cref="EntryPointNotFoundException"></exception> /// <exception cref="ConcreteTypeNotFoundException"></exception> /// <exception cref="ConcreteTypeAmbiguousMatchException"></exception> - public static T GetOrCreateSingleton<T>(this PluginBase plugin, string configName) - { - //Add service to service continer - return GetOrCreateSingleton(plugin, (plugin) => CreateService<T>(plugin, configName)); - } + public static T GetOrCreateSingleton<T>(this PluginBase plugin, string configName) + => GetOrCreateSingleton(plugin, (plugin) => CreateService<T>(plugin, configName)); /// <summary> /// Configures the service asynchronously on the plugin's scheduler and returns a task @@ -534,10 +539,7 @@ namespace VNLib.Plugins.Extensions.Loading /// <exception cref="EntryPointNotFoundException"></exception> /// <exception cref="ConcreteTypeNotFoundException"></exception> /// <exception cref="ConcreteTypeAmbiguousMatchException"></exception> - public static T CreateService<T>(this PluginBase plugin, IConfigScope? config) - { - return (T)CreateService(plugin, typeof(T), config); - } + public static T CreateService<T>(this PluginBase plugin, IConfigScope? config) => (T)CreateService(plugin, typeof(T), config); /// <summary> /// <para> @@ -591,7 +593,7 @@ namespace VNLib.Plugins.Extensions.Loading ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) }); //Make sure the constructor exists - _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}"); + _ = constructor ?? throw new MissingMemberException($"No constructor found for {serviceType.Name}"); //Call constructore service = constructor.Invoke(new object[2] { plugin, config }); @@ -602,7 +604,7 @@ namespace VNLib.Plugins.Extensions.Loading ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase) }); //Make sure the constructor exists - _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}"); + _ = constructor ?? throw new MissingMemberException($"No constructor found for {serviceType.Name}"); //Call constructore service = constructor.Invoke(new object[1] { plugin }); @@ -610,7 +612,8 @@ namespace VNLib.Plugins.Extensions.Loading } catch(TargetInvocationException te) when (te.InnerException != null) { - throw te.InnerException; + FindAndThrowInnerException(te); + throw; } Task? loading = null; @@ -654,6 +657,20 @@ namespace VNLib.Plugins.Extensions.Loading return service; } + [DoesNotReturn] + internal static void FindAndThrowInnerException(Exception ex) + { + //Recursivley search for the innermost exception of a TIE + if (ex is TargetInvocationException && ex.InnerException != null) + { + FindAndThrowInnerException(ex.InnerException); + } + else + { + ExceptionDispatchInfo.Throw(ex); + } + } + private sealed class PluginLocalCache { diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs index e382681..28b3a08 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs @@ -27,6 +27,7 @@ using System.Linq; using System.Text.Json; using System.Collections.Generic; +using VNLib.Hashing; using VNLib.Utils; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; @@ -50,17 +51,10 @@ namespace VNLib.Plugins.Extensions.Loading string customAsm = el.GetString() ?? throw new KeyNotFoundException("You must specify a string file path for your custom password hashing assembly"); //Load the custom assembly - AssemblyLoader<IPasswordHashingProvider> prov = plugin.LoadAssembly<IPasswordHashingProvider>(customAsm); - - //Configure async - if (prov.Resource is IAsyncConfigurable ac) - { - //Configure async - _ = plugin.ConfigureServiceAsync(ac); - } + IPasswordHashingProvider userProvider = plugin.CreateServiceExternal<IPasswordHashingProvider>(customAsm); //Store - Passwords = new CustomPasswordHashingAsm(prov); + Passwords = new CustomPasswordHashingAsm(userProvider); } else { @@ -96,12 +90,9 @@ namespace VNLib.Plugins.Extensions.Loading sealed class CustomPasswordHashingAsm : IPasswordHashingProvider { - private readonly AssemblyLoader<IPasswordHashingProvider> _loader; + private readonly IPasswordHashingProvider _provider; - public CustomPasswordHashingAsm(AssemblyLoader<IPasswordHashingProvider> loader) - { - _loader = loader; - } + public CustomPasswordHashingAsm(IPasswordHashingProvider loader) => _provider = loader; /* * Password hashing isnt a super high performance system @@ -109,29 +100,45 @@ namespace VNLib.Plugins.Extensions.Loading * asm wrapper providing unload protection */ - public PrivateString Hash(ReadOnlySpan<char> password) => _loader.Resource.Hash(password); + public PrivateString Hash(ReadOnlySpan<char> password) => _provider.Hash(password); - public PrivateString Hash(ReadOnlySpan<byte> password) => _loader.Resource.Hash(password); + public PrivateString Hash(ReadOnlySpan<byte> password) => _provider.Hash(password); - public ERRNO Hash(ReadOnlySpan<byte> password, Span<byte> hashOutput) => _loader.Resource.Hash(password, hashOutput); + public ERRNO Hash(ReadOnlySpan<byte> password, Span<byte> hashOutput) => _provider.Hash(password, hashOutput); - public bool Verify(ReadOnlySpan<char> passHash, ReadOnlySpan<char> password) => _loader.Resource.Verify(passHash, password); + public bool Verify(ReadOnlySpan<char> passHash, ReadOnlySpan<char> password) => _provider.Verify(passHash, password); - public bool Verify(ReadOnlySpan<byte> passHash, ReadOnlySpan<byte> password) => _loader.Resource.Verify(passHash, password); + public bool Verify(ReadOnlySpan<byte> passHash, ReadOnlySpan<byte> password) => _provider.Verify(passHash, password); } private sealed class SecretProvider : VnDisposeable, ISecretProvider { private readonly IAsyncLazy<byte[]> _pepper; + public PasswordHashing Passwords { get; } + public SecretProvider(PluginBase plugin, IConfigScope config) { + IArgon2Library? safeLib = null; + + if(config.TryGetValue("lib_path", out JsonElement manualLibPath)) + { + SafeArgon2Library lib = VnArgon2.LoadCustomLibrary(manualLibPath.GetString()!, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories); + _ = plugin.RegisterForUnload(lib.Dispose); + safeLib = lib; + } + + //Load default library if the user did not explictly specify one + safeLib ??= VnArgon2.GetOrLoadSharedLib(); + + Argon2ConfigParams costParams = new(); + if (config.TryGetValue("args", out JsonElement el)) { //Convert to dict IReadOnlyDictionary<string, JsonElement> hashingArgs = el.EnumerateObject().ToDictionary(static k => k.Name, static v => v.Value); - Argon2ConfigParams p = new() + costParams = new() { HashLen = hashingArgs["hash_len"].GetUInt32(), MemoryCost = hashingArgs["memory_cost"].GetUInt32(), @@ -139,16 +146,11 @@ namespace VNLib.Plugins.Extensions.Loading SaltLen = (int)hashingArgs["salt_len"].GetUInt32(), TimeCost = hashingArgs["time_cost"].GetUInt32() }; - - //Load passwords - Passwords = PasswordHashing.Create(this, in p); - } - else - { - //Load passwords with default config - Passwords = PasswordHashing.Create(this, new Argon2ConfigParams()); } + //Create passwords with the configuration and library + Passwords = PasswordHashing.Create(safeLib, this, in costParams); + //Get the pepper from secret storage _pepper = plugin.GetSecretAsync(LoadingExtensions.PASSWORD_HASHING_KEY) .ToLazy(static sr => sr.GetFromBase64()); @@ -164,9 +166,6 @@ namespace VNLib.Plugins.Extensions.Loading .ToLazy(static sr => sr.GetFromBase64()); } - - public PasswordHashing Passwords { get; } - ///<inheritdoc/> public int BufferSize { @@ -194,8 +193,11 @@ namespace VNLib.Plugins.Extensions.Loading protected override void Free() { - //Clear the pepper if set - MemoryUtil.InitializeBlock(_pepper.Value.AsSpan()); + if (_pepper.Completed) + { + //Clear the pepper if set + MemoryUtil.InitializeBlock(_pepper.Value.AsSpan()); + } } } } diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs index 7731bcf..1061dda 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs @@ -54,58 +54,65 @@ namespace VNLib.Plugins.Extensions.Loading.Routing Type endpointType = typeof(T); T endpoint; - - //If the config attribute is not set, then ignore the config variables - if (string.IsNullOrWhiteSpace(pluginConfigPathName)) - { - ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase) }); - - _ = constructor ?? throw new EntryPointNotFoundException($"No constructor t(PluginBase p) found for {endpointType.Name}"); - - //Create the new endpoint and pass the plugin instance - endpoint = (T)constructor.Invoke(new object[] { plugin }); - - //Register event handlers for the endpoint - ScheduleIntervals(plugin, endpoint, endpointType, null); - } - else + try { - //Try to get config but allow null if not required - IConfigScope? config = plugin.TryGetConfig(pluginConfigPathName); - - if(configRequired && config == null) - { - ConfigurationExtensions.ThrowConfigNotFoundForType(endpointType); - return default; - } - - //Choose constructor based on config - if (config != null) + //If the config attribute is not set, then ignore the config variables + if (string.IsNullOrWhiteSpace(pluginConfigPathName)) { - ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) }); + ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase) }); - //Make sure the constructor exists - _ = constructor ?? throw new EntryPointNotFoundException($"No constructor t(PluginBase p, IConfigScope cs) found for {endpointType.Name}"); + _ = constructor ?? throw new EntryPointNotFoundException($"No constructor t(PluginBase p) found for {endpointType.Name}"); - //Create the new endpoint and pass the plugin instance along with the configuration object - endpoint = (T)constructor.Invoke(new object[] { plugin, config }); + //Create the new endpoint and pass the plugin instance + endpoint = (T)constructor.Invoke(new object[] { plugin }); //Register event handlers for the endpoint - ScheduleIntervals(plugin, endpoint, endpointType, config); + ScheduleIntervals(plugin, endpoint, endpointType, null); } else { - //Config does not exist, so use the default constructor - ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase) }); + //Try to get config but allow null if not required + IConfigScope? config = plugin.TryGetConfig(pluginConfigPathName); - _ = constructor ?? throw new EntryPointNotFoundException($"No constructor t(PluginBase p) found for {endpointType.Name}"); + if (configRequired && config == null) + { + ConfigurationExtensions.ThrowConfigNotFoundForType(endpointType); + return default; + } - //Create the new endpoint and pass the plugin instance - endpoint = (T)constructor.Invoke(new object[] { plugin }); + //Choose constructor based on config + if (config != null) + { + ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) }); - //Register event handlers for the endpoint - ScheduleIntervals(plugin, endpoint, endpointType, null); - } + //Make sure the constructor exists + _ = constructor ?? throw new EntryPointNotFoundException($"No constructor t(PluginBase p, IConfigScope cs) found for {endpointType.Name}"); + + //Create the new endpoint and pass the plugin instance along with the configuration object + endpoint = (T)constructor.Invoke(new object[] { plugin, config }); + + //Register event handlers for the endpoint + ScheduleIntervals(plugin, endpoint, endpointType, config); + } + else + { + //Config does not exist, so use the default constructor + ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase) }); + + _ = constructor ?? throw new EntryPointNotFoundException($"No constructor t(PluginBase p) found for {endpointType.Name}"); + + //Create the new endpoint and pass the plugin instance + endpoint = (T)constructor.Invoke(new object[] { plugin }); + + //Register event handlers for the endpoint + ScheduleIntervals(plugin, endpoint, endpointType, null); + } + } + } + catch(TargetInvocationException te) + { + LoadingExtensions.FindAndThrowInnerException(te); + throw; } //Route the endpoint diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs index c2d830f..c4c9a8a 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs @@ -78,7 +78,7 @@ namespace VNLib.Plugins.Extensions.Loading /// </summary> /// <param name="plugin"></param> /// <param name="secretName">The name of the secret propery to get</param> - /// <returns>The element from the configuration file with the given name, or null if the configuration or property does not exist</returns> + /// <returns>The element from the configuration file with the given name, raises an exception if the secret does not exist</returns> /// <exception cref="KeyNotFoundException"></exception> /// <exception cref="ObjectDisposedException"></exception> public static async Task<ISecretResult> GetSecretAsync(this PluginBase plugin, string secretName) diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/UserManager.cs b/lib/VNLib.Plugins.Extensions.Loading/src/UserManager.cs index 33f6df3..e668b3e 100644 --- a/lib/VNLib.Plugins.Extensions.Loading/src/UserManager.cs +++ b/lib/VNLib.Plugins.Extensions.Loading/src/UserManager.cs @@ -42,7 +42,6 @@ namespace VNLib.Plugins.Extensions.Loading.Users { public const string USER_CUSTOM_ASSEMBLY = "custom_assembly"; public const string DEFAULT_USER_ASM = "VNLib.Plugins.Essentials.Users.dll"; - public const string ONLOAD_METHOD_NAME = "OnPluginLoading"; private readonly IUserManager _dynamicLoader; @@ -63,28 +62,14 @@ namespace VNLib.Plugins.Extensions.Loading.Users private static IUserManager LoadUserAssembly(PluginBase plugin, string customAsm) { //Try to load a custom assembly - AssemblyLoader<IUserManager> loader = plugin.LoadAssembly<IUserManager>(customAsm); - try - { - //Try to get the onload method - Action<object>? onLoadMethod = loader.TryGetMethod<Action<object>>(ONLOAD_METHOD_NAME); - - //Call the onplugin load method - onLoadMethod?.Invoke(plugin); - - if (plugin.IsDebug()) - { - plugin.Log.Debug("Loading user manager from assembly {name}", loader.Resource.GetType().AssemblyQualifiedName); - } + IUserManager externManager = plugin.CreateServiceExternal<IUserManager>(customAsm); - //Return the loaded instance (may raise exception) - return loader.Resource; - } - catch + if (plugin.IsDebug()) { - loader.Dispose(); - throw; + plugin.Log.Debug("Loading user manager from assembly {name}", externManager.GetType().AssemblyQualifiedName); } + + return externManager; } ///<inheritdoc/> |