aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-11-02 01:50:05 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-11-02 01:50:05 -0400
commit9768aa7713826d49a6c2ea1025caa4b87d62e5e8 (patch)
tree327525a94b1fbe8b11591eeb1b3d2941a6e58c06
parentc5b9f9b64933a288362fc08bea8a461e5e260b16 (diff)
also carried away
-rw-r--r--lib/VNLib.Plugins.Extensions.Data/src/VNLib.Plugins.Extensions.Data.csproj2
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading.Sql/src/VNLib.Plugins.Extensions.Loading.Sql.csproj4
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs19
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeAmbiguousMatchException.cs44
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeException.cs44
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConcreteTypeNotFoundException.cs41
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/ConfigurationValidationException.cs (renamed from lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationValidationException.cs)0
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Exceptions/MissingDependencyException.cs41
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/ExternServiceAttribute.cs43
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs141
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/ManagedPasswordHashing.cs68
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs87
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs2
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/UserManager.cs25
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/>