From 04c4e610f9688b67f32d36ac08e19fbaa0ecb95b Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Tue, 15 Sep 2026 00:59:55 +0200 Subject: [PATCH 1/8] refactor: split plugin contract into partials --- .../Abstractions/Contracts/IAuthKitPlugin.cs | 446 ------------------ .../IAuthKitPlugin.Configuration.cs | 80 ++++ .../PluginContract/IAuthKitPlugin.Health.cs | 38 ++ .../IAuthKitPlugin.HostRequirements.cs | 26 + .../IAuthKitPlugin.Lifecycle.cs | 33 ++ .../PluginContract/IAuthKitPlugin.Pipeline.cs | 71 +++ .../PluginContract/IAuthKitPlugin.Security.cs | 82 ++++ .../PluginContract/IAuthKitPlugin.cs | 156 ++++++ 8 files changed, 486 insertions(+), 446 deletions(-) delete mode 100644 src/Plugins/Abstractions/Contracts/IAuthKitPlugin.cs create mode 100644 src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Configuration.cs create mode 100644 src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Health.cs create mode 100644 src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.HostRequirements.cs create mode 100644 src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Lifecycle.cs create mode 100644 src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Pipeline.cs create mode 100644 src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Security.cs create mode 100644 src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.cs diff --git a/src/Plugins/Abstractions/Contracts/IAuthKitPlugin.cs b/src/Plugins/Abstractions/Contracts/IAuthKitPlugin.cs deleted file mode 100644 index 631b86c..0000000 --- a/src/Plugins/Abstractions/Contracts/IAuthKitPlugin.cs +++ /dev/null @@ -1,446 +0,0 @@ -using AuthKit.Plugins.Abstractions.Contracts.Plugins; -using AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; -using AuthKit.Plugins.Abstractions.Models; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using System.Reflection; - -namespace AuthKit.Plugins.Abstractions.Contracts; - -/// -/// Defines the contract implemented by an AuthKit plugin. -/// -/// -/// -/// AuthKit plugins are discovered and loaded dynamically by the host at startup. -/// A plugin does not need to be directly referenced by the host project. -/// -/// -/// The plugin contract allows an extension to contribute services, middleware, -/// health checks, and OpenAPI security scheme metadata to the host application. -/// -/// -/// Plugin implementations should keep their integration with the host limited -/// to the abstractions exposed by this contract and should register any -/// plugin-specific dependencies through . -/// -/// -public interface IAuthKitPlugin -{ - /// - /// Gets the stable, host-unique identifier of the plugin. - /// - /// - /// The ID is an author-declared identifier (e.g. "authkit.devtokens"), - /// is expected to be non-empty and stable across restarts. The host is - /// responsible for validating format and uniqueness before activation. - /// - string Id => Metadata.Id; - - /// - /// Gets the unique name of the plugin. - /// - /// - /// The name is used to identify the plugin in host diagnostics, - /// startup output, and other plugin-related metadata. - /// - string Name => Metadata.Name; - - /// - /// Gets an optional human-readable display name for UIs. - /// - /// - /// The host UI should display DisplayName ?? Name when presenting - /// the plugin to users. - /// - string? DisplayName => Metadata.DisplayName; - - /// - /// Gets an optional human-readable description of the plugin. - /// - /// - /// The host may display the description in startup output, - /// diagnostics, administrative interfaces, or other status surfaces. - /// - string Description => Metadata.Description; - - /// - /// Gets the version of the plugin as a semantic version (SemVer 2.0.0). - /// - /// - /// The Version replaces the previous string-based version and exposes - /// full semantic version semantics (parsing, equality, precedence). - /// - SemanticVersion Version => SemanticVersion.Parse(Metadata.Version); - - /// - /// Optional author metadata, visible in catalogs and diagnostics. - /// - string? Author => Metadata.Author; - - /// - /// Optional SPDX-style license string (no validation performed by host). - /// - string? License => Metadata.License; - - /// - /// Optional absolute URI pointing to the license text. - /// - string? LicenseUrl => Metadata.LicenseUrl; - - /// - /// Optional absolute HTTP/HTTPS URI pointing to a plugin homepage. - /// - string? Homepage => Metadata.Homepage; - - /// - /// Optional absolute HTTP/HTTPS URI pointing to the plugin repository. - /// - string? RepositoryUrl => Metadata.RepositoryUrl; - - /// - /// Optional classification tags for UI filtering. Defaults to empty. - /// Null or whitespace elements are invalid and should be rejected during validation. - /// Used for filtering plugins in UIs and catalogs. - /// - /// - /// Tags are case-sensitive strings without controlled vocabulary. - /// Example: ["security", "auth", "audit"]. - /// - IReadOnlyList Tags => Metadata.Tags; - - /// - /// Priority used for activation ordering among dependency-ready plugins. - /// Lower values are activated earlier, higher values later. - /// Defaults to 0. - /// - /// - /// The ordering algorithm is: topological sort where, among the set of currently - /// dependency-ready plugins, the next plugin is chosen by Priority ascending. - /// Dependency order (G7) wins over Priority. - /// Example: A (p. 100) → B (p-100, DependsOn A), C (p. 0) ⇒ order: A, C, B. - /// - int Priority => Metadata.Priority; - - /// - /// Indicates whether the plugin is enabled. Defaults to true. - /// - /// - /// If false, the plugin is skipped before loading (no consistency check runs for it). - /// For plugins accepted by the preload gate and subsequently loaded, - /// manifest.IsEnabled == instance.IsEnabled is part of consistency validation. - /// - bool IsEnabled => Metadata.IsEnabled; - - /// - /// Features/capabilities exposed by the plugin. Contract - /// requires Case-insensitive comparison. Defaults to an immutable empty set. - /// - /// - /// Used for pre-activation capability checks (via ) and - /// post-load consistency validation. Host checks capabilities using the - /// extension method. - /// Example: plugin.Supports("auth"). - /// - IReadOnlySet Capabilities => - System.Collections.Immutable.ImmutableHashSet.CreateRange(StringComparer.OrdinalIgnoreCase, Metadata.Capabilities); - - /// - /// Gets the metadata associated with the plugin. - /// - PluginMetadataAttribute Metadata => GetType().GetCustomAttribute() - ?? throw new InvalidOperationException($"Plugin {GetType().Name} is missing [PluginMetadata] attribute."); - - /// - /// Registers the plugin's services in the host dependency injection container. - /// - /// The host's dependency injection service collection. - /// The host application configuration. - /// - /// - /// This method is called while the host application is being configured, - /// before the application is built. - /// - /// - /// Plugins should register all services required by their functionality - /// through this method rather than creating their own dependency injection - /// container. - /// - /// - void ConfigureServices( - IServiceCollection services, - IConfiguration configuration) => - throw new NotSupportedException( - $"Plugin '{GetType().Name}' must implement a supported ConfigureServices overload."); - - /// - /// Configures plugin services using the host application builder. - /// - /// The host application builder used by AuthKit. - /// The application configuration. - /// - /// This overload is optional. Its default implementation delegates to the - /// legacy service collection overload for existing plugins. - /// - void ConfigureServices( - IHostApplicationBuilder builder, - IConfiguration configuration) => - ConfigureServices(builder.Services, configuration); - - /// - /// Configures plugin services with stable plugin context information. - /// - /// The service collection used by the host. - /// The context for the plugin being configured. - /// - /// This overload is optional. Its default implementation delegates to the - /// legacy service collection overload for existing plugins. - /// - void ConfigureServices(IServiceCollection services, AuthKitPluginContext context) => - ConfigureServices(services, context.Configuration); - - /// - /// Performs an optional structured health check for the plugin. - /// - /// The root service provider of the host application. - /// A token that can cancel the health check. - /// - /// One or more structured health results reported by the plugin. - /// - /// - /// - /// The host may invoke this method as part of its health endpoint. - /// Plugins can resolve the services they require from - /// to verify the availability of their - /// dependencies. - /// - /// - /// A plugin may return separate results for independent dependencies or - /// capabilities. Cancellation must be propagated to cancellable operations - /// and is not converted into a fabricated health result. - /// - /// - /// The default implementation reports the plugin as healthy. Existing - /// plugins that do not require custom health validation therefore do not - /// need to implement this member. - /// - /// - Task> CheckHealthAsync( - IServiceProvider services, - CancellationToken cancellationToken = default) => - Task.FromResult>( - [new PluginHealthResult(PluginHealthStatus.Healthy)]); - - /// - /// Gets the optional ASP.NET Core middleware type contributed by the plugin. - /// - /// - /// - /// When specified, the host inserts the middleware into its request - /// processing pipeline at the plugin middleware slot. - /// - /// - /// The middleware type must follow the conventional ASP.NET Core middleware - /// pattern, including a constructor accepting - /// and an InvokeAsync method accepting . - /// Additional dependencies may be supplied through dependency injection. - /// - /// - /// The default value is null, indicating that the plugin does not - /// contribute middleware. - /// - /// - Type? MiddlewareType => null; - - /// - /// Registers plugin-owned endpoints during host endpoint configuration. - /// - /// The application's endpoint route builder. - /// - /// This optional hook runs after host services are configured and before - /// the application starts processing requests. Exceptions are propagated. - /// - void MapEndpoints(IEndpointRouteBuilder endpoints) - { - } - - /// - /// Configures plugin application middleware on the actual host application. - /// - /// The application's a live builder. - /// - /// When implemented, this hook takes precedence over - /// to prevent accidental duplicate middleware registration. - /// - void ConfigureApplication(IApplicationBuilder application) - { - } - - /// - /// Gets the explicit pipeline position used by . - /// - PluginPipelinePosition PipelinePosition => PluginPipelinePosition.BeforeAuthentication; - - /// - /// Configures plugin middleware at the declared pipeline position. - /// - /// The application's a live builder. - /// The position currently being configured. - /// - /// The host invokes this hook once at . - /// Plugins at the same position are ordered by stable plugin ID. - /// - void ConfigurePipeline(IApplicationBuilder application, PluginPipelinePosition position) - { - } - - /// - /// Gets the minimum host version required to load this plugin. - /// - /// - /// If the host version is lower than , the plugin is rejected. - /// - SemanticVersion? MinHostVersion => string.IsNullOrEmpty(Metadata.MinHostVersion) ? null : SemanticVersion.Parse(Metadata.MinHostVersion); - - /// - /// Gets the list of plugin IDs this plugin depends on. - /// - /// - /// Each entry must be a valid plugin ID. The host validates that: - /// - Dependencies exist among discovered plugins. - /// - There are no self-dependencies. - /// - There are no duplicate dependencies. - /// - There are no dependency cycles. - /// - IReadOnlyList DependsOn => Metadata.DependsOn; - - /// - /// Gets the OpenAPI security schemes contributed by the plugin. - /// - /// - /// - /// The host exposes security schemes returned by this method as - /// part of its Swagger/OpenAPI security metadata. - /// - /// - /// Plugins that do not contribute to security schemes can rely on the default - /// empty collection. - /// - /// - /// readonly dictionary keyed by the security scheme name. - IReadOnlyDictionary GetSecuritySchemes() => - new Dictionary(); - - /// - /// Configures authentication schemes on the host-owned authentication builder. - /// - /// The actual host authentication builder. - /// - /// - /// This optional hook is invoked once per plugin while the host configures its - /// security infrastructure, before the service provider is built. Plugins are - /// processed in ascending order, so invocation order is - /// deterministic and does not depend on discovery order. - /// - /// - /// The hook receives the same used by the host, - /// so schemes registered here participate in the application authentication - /// infrastructure. Plugin services, including handler dependencies, may be - /// registered later through ConfigureServices. - /// - /// - /// This optional hook does not change the host default scheme. Scheme names are - /// globally significant; registering a name already owned by the host or by - /// another plugin fails explicitly when the authentication options are built. - /// - /// - void ConfigureAuthentication(AuthenticationBuilder builder) - { - } - - /// - /// Configures authorization policies on the host-owned authorization options. - /// - /// The actual host authorization options. - /// - /// - /// This optional hook is invoked once per plugin while the host configures its - /// security infrastructure, before the service provider is built. Plugins are - /// processed in ascending order, so invocation order is - /// deterministic and does not depend on discovery order. - /// - /// - /// Policies registered here are available through the standard ASP.NET Core - /// authorization infrastructure and can protect endpoints mapped by any plugin. - /// - /// - /// Policy names are globally significant. The host rejects duplicate ownership: - /// a policy name already registered by another plugin fails to start up with an - /// exception identifying both plugins. Plugins should use globally unique, - /// preferably namespaced policy names. - /// - /// - /// Existing default and fallback policies are not replaced by the host, and this - /// hook must not assume ownership of such global defaults. - /// - /// - void ConfigureAuthorization(AuthorizationOptions options) - { - } - - /// - /// Binds strongly typed plugin options from the plugin configuration section using - /// the standard options DI infrastructure. - /// - /// The plugin options type. - /// The host service collection. - /// The application configuration. - /// - /// - /// Typically called from ConfigureServices. The section is resolved through - /// : - /// Plugins:{Id} wins when it exists, otherwise Plugins:{Name} is used, - /// matching the scoping of . - /// - /// - /// Registered options become readable through IOptions<TOptions> once - /// the service provider is built. - /// - /// - void BindConfiguration(IServiceCollection services, IConfiguration configuration) - where TOptions : class => - services.Configure(this.GetPluginConfiguration(configuration)); - - /// - /// Initializes plugin runtime resources before the host is considered started. - /// - /// The host startup cancellation token. - /// A task that completes when initialization is complete. - Task OnStartingAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - /// - /// Notifies the plugin after the host has started successfully. - /// - /// The host lifecycle cancellation token. - /// A task that completes when post-start work is complete. - Task OnStartedAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - /// - /// Releases plugin runtime resources during a graceful host shutdown. - /// - /// The host shutdown cancellation token. - /// A task that completes when shutdown preparation is complete. - Task OnStoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - /// - /// Gets hosted services owned by this plugin. - /// - /// A non-null collection of services registered in the host DI container. - IReadOnlyList GetHostedServices() => []; -} diff --git a/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Configuration.cs b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Configuration.cs new file mode 100644 index 0000000..0d94f2a --- /dev/null +++ b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Configuration.cs @@ -0,0 +1,80 @@ +using AuthKit.Plugins.Abstractions.Contracts.Plugins; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace AuthKit.Plugins.Abstractions.Contracts.PluginContract; + +public partial interface IAuthKitPlugin +{ + /// + /// Registers the plugin's services in the host dependency injection container. + /// + /// The host's dependency injection service collection. + /// The host application configuration. + /// + /// + /// This method is called while the host application is being configured, + /// before the application is built. + /// + /// + /// Plugins should register all services required by their functionality + /// through this method rather than creating their own dependency injection + /// container. + /// + /// + void ConfigureServices( + IServiceCollection services, + IConfiguration configuration) => + throw new NotSupportedException( + $"Plugin '{GetType().Name}' must implement a supported ConfigureServices overload."); + + /// + /// Configures plugin services using the host application builder. + /// + /// The host application builder used by AuthKit. + /// The application configuration. + /// + /// This overload is optional. Its default implementation delegates to the + /// legacy service collection overload for existing plugins. + /// + void ConfigureServices( + IHostApplicationBuilder builder, + IConfiguration configuration) => + ConfigureServices(builder.Services, configuration); + + /// + /// Configures plugin services with stable plugin context information. + /// + /// The service collection used by the host. + /// The context for the plugin being configured. + /// + /// This overload is optional. Its default implementation delegates to the + /// legacy service collection overload for existing plugins. + /// + void ConfigureServices(IServiceCollection services, AuthKitPluginContext context) => + ConfigureServices(services, context.Configuration); + + /// + /// Binds strongly typed plugin options from the plugin configuration section using + /// the standard options DI infrastructure. + /// + /// The plugin options type. + /// The host service collection. + /// The application configuration. + /// + /// + /// Typically called from ConfigureServices. The section is resolved through + /// : + /// Plugins:{Id} wins when it exists, otherwise Plugins:{Name} is used, + /// matching the scoping of . + /// + /// + /// Registered options become readable through IOptions<TOptions> once + /// the service provider is built. + /// + /// + void BindConfiguration(IServiceCollection services, IConfiguration configuration) + where TOptions : class => + services.Configure(this.GetPluginConfiguration(configuration)); +} \ No newline at end of file diff --git a/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Health.cs b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Health.cs new file mode 100644 index 0000000..cb2e774 --- /dev/null +++ b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Health.cs @@ -0,0 +1,38 @@ +using AuthKit.Plugins.Abstractions.Models; + +namespace AuthKit.Plugins.Abstractions.Contracts.PluginContract; + +public partial interface IAuthKitPlugin +{ + /// + /// Performs an optional structured health check for the plugin. + /// + /// The root service provider of the host application. + /// A token that can cancel the health check. + /// + /// One or more structured health results reported by the plugin. + /// + /// + /// + /// The host may invoke this method as part of its health endpoint. + /// Plugins can resolve the services they require from + /// to verify the availability of their + /// dependencies. + /// + /// + /// A plugin may return separate results for independent dependencies or + /// capabilities. Cancellation must be propagated to cancellable operations + /// and is not converted into a fabricated health result. + /// + /// + /// The default implementation reports the plugin as healthy. Existing + /// plugins that do not require custom health validation therefore do not + /// need to implement this member. + /// + /// + Task> CheckHealthAsync( + IServiceProvider services, + CancellationToken cancellationToken = default) => + Task.FromResult>( + [new PluginHealthResult(PluginHealthStatus.Healthy)]); +} \ No newline at end of file diff --git a/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.HostRequirements.cs b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.HostRequirements.cs new file mode 100644 index 0000000..d7e8986 --- /dev/null +++ b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.HostRequirements.cs @@ -0,0 +1,26 @@ +using AuthKit.Plugins.Abstractions.Models; + +namespace AuthKit.Plugins.Abstractions.Contracts.PluginContract; + +public partial interface IAuthKitPlugin +{ + /// + /// Gets the minimum host version required to load this plugin. + /// + /// + /// If the host version is lower than , the plugin is rejected. + /// + SemanticVersion? MinHostVersion => string.IsNullOrEmpty(Metadata.MinHostVersion) ? null : SemanticVersion.Parse(Metadata.MinHostVersion); + + /// + /// Gets the list of plugin IDs this plugin depends on. + /// + /// + /// Each entry must be a valid plugin ID. The host validates that: + /// - Dependencies exist among discovered plugins. + /// - There are no self-dependencies. + /// - There are no duplicate dependencies. + /// - There are no dependency cycles. + /// + IReadOnlyList DependsOn => Metadata.DependsOn; +} \ No newline at end of file diff --git a/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Lifecycle.cs b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Lifecycle.cs new file mode 100644 index 0000000..beca798 --- /dev/null +++ b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Lifecycle.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Hosting; + +namespace AuthKit.Plugins.Abstractions.Contracts.PluginContract; + +public partial interface IAuthKitPlugin +{ + /// + /// Initializes plugin runtime resources before the host is considered started. + /// + /// The host startup cancellation token. + /// A task that completes when initialization is complete. + Task OnStartingAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + /// Notifies the plugin after the host has started successfully. + /// + /// The host lifecycle cancellation token. + /// A task that completes when post-start work is complete. + Task OnStartedAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + /// Releases plugin runtime resources during a graceful host shutdown. + /// + /// The host shutdown cancellation token. + /// A task that completes when shutdown preparation is complete. + Task OnStoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + /// Gets hosted services owned by this plugin. + /// + /// A non-null collection of services registered in the host DI container. + IReadOnlyList GetHostedServices() => []; +} \ No newline at end of file diff --git a/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Pipeline.cs b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Pipeline.cs new file mode 100644 index 0000000..3510699 --- /dev/null +++ b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Pipeline.cs @@ -0,0 +1,71 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace AuthKit.Plugins.Abstractions.Contracts.PluginContract; + +public partial interface IAuthKitPlugin +{ + /// + /// Gets the optional ASP.NET Core middleware type contributed by the plugin. + /// + /// + /// + /// When specified, the host inserts the middleware into its request + /// processing pipeline at the plugin middleware slot. + /// + /// + /// The middleware type must follow the conventional ASP.NET Core middleware + /// pattern, including a constructor accepting + /// and an InvokeAsync method accepting . + /// Additional dependencies may be supplied through dependency injection. + /// + /// + /// The default value is null, indicating that the plugin does not + /// contribute middleware. + /// + /// + Type? MiddlewareType => null; + + /// + /// Registers plugin-owned endpoints during host endpoint configuration. + /// + /// The application's endpoint route builder. + /// + /// This optional hook runs after host services are configured and before + /// the application starts processing requests. Exceptions are propagated. + /// + void MapEndpoints(IEndpointRouteBuilder endpoints) + { + } + + /// + /// Configures plugin application middleware on the actual host application. + /// + /// The application's a live builder. + /// + /// When implemented, this hook takes precedence over + /// to prevent accidental duplicate middleware registration. + /// + void ConfigureApplication(IApplicationBuilder application) + { + } + + /// + /// Gets the explicit pipeline position used by . + /// + PluginPipelinePosition PipelinePosition => PluginPipelinePosition.BeforeAuthentication; + + /// + /// Configures plugin middleware at the declared pipeline position. + /// + /// The application's a live builder. + /// The position currently being configured. + /// + /// The host invokes this hook once at . + /// Plugins at the same position are ordered by stable plugin ID. + /// + void ConfigurePipeline(IApplicationBuilder application, PluginPipelinePosition position) + { + } +} \ No newline at end of file diff --git a/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Security.cs b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Security.cs new file mode 100644 index 0000000..cfdc729 --- /dev/null +++ b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.Security.cs @@ -0,0 +1,82 @@ +using AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; + +namespace AuthKit.Plugins.Abstractions.Contracts.PluginContract; + +public partial interface IAuthKitPlugin +{ + /// + /// Gets the OpenAPI security schemes contributed by the plugin. + /// + /// + /// + /// The host exposes security schemes returned by this method as + /// part of its Swagger/OpenAPI security metadata. + /// + /// + /// Plugins that do not contribute to security schemes can rely on the default + /// empty collection. + /// + /// + /// readonly dictionary keyed by the security scheme name. + IReadOnlyDictionary GetSecuritySchemes() => + new Dictionary(); + + /// + /// Configures authentication schemes on the host-owned authentication builder. + /// + /// The actual host authentication builder. + /// + /// + /// This optional hook is invoked once per plugin while the host configures its + /// security infrastructure, before the service provider is built. Plugins are + /// processed in ascending order, so invocation order is + /// deterministic and does not depend on discovery order. + /// + /// + /// The hook receives the same used by the host, + /// so schemes registered here participate in the application authentication + /// infrastructure. Plugin services, including handler dependencies, may be + /// registered later through ConfigureServices. + /// + /// + /// This optional hook does not change the host default scheme. Scheme names are + /// globally significant; registering a name already owned by the host or by + /// another plugin fails explicitly when the authentication options are built. + /// + /// + void ConfigureAuthentication(AuthenticationBuilder builder) + { + } + + /// + /// Configures authorization policies on the host-owned authorization options. + /// + /// The actual host authorization options. + /// + /// + /// This optional hook is invoked once per plugin while the host configures its + /// security infrastructure, before the service provider is built. Plugins are + /// processed in ascending order, so invocation order is + /// deterministic and does not depend on discovery order. + /// + /// + /// Policies registered here are available through the standard ASP.NET Core + /// authorization infrastructure and can protect endpoints mapped by any plugin. + /// + /// + /// Policy names are globally significant. The host rejects duplicate ownership: + /// a policy name already registered by another plugin fails to start up with an + /// exception identifying both plugins. Plugins should use globally unique, + /// preferably namespaced policy names. + /// + /// + /// Existing default and fallback policies are not replaced by the host, and this + /// hook must not assume ownership of such global defaults. + /// + /// + void ConfigureAuthorization(AuthorizationOptions options) + { + } +} \ No newline at end of file diff --git a/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.cs b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.cs new file mode 100644 index 0000000..90f6d7d --- /dev/null +++ b/src/Plugins/Abstractions/Contracts/PluginContract/IAuthKitPlugin.cs @@ -0,0 +1,156 @@ +using System.Reflection; +using AuthKit.Plugins.Abstractions.Contracts.Plugins; +using AuthKit.Plugins.Abstractions.Models; + +namespace AuthKit.Plugins.Abstractions.Contracts.PluginContract; + +/// +/// Defines the contract implemented by an AuthKit plugin. +/// +/// +/// +/// AuthKit plugins are discovered and loaded dynamically by the host at startup. +/// A plugin does not need to be directly referenced by the host project. +/// +/// +/// The plugin contract allows an extension to contribute services, middleware, +/// health checks, and OpenAPI security scheme metadata to the host application. +/// +/// +/// Plugin implementations should keep their integration with the host limited +/// to the abstractions exposed by this contract and should register any +/// plugin-specific dependencies through . +/// +/// +/// The contract surface is split into cohesive partial declarations grouped +/// into the IAuthKitPlugin*.cs files in this folder: the base identity +/// and metadata accessors, host requirements, configuration, health, pipeline, +/// security, and lifecycle hooks. +/// +/// +public partial interface IAuthKitPlugin +{ + /// + /// Gets the stable, host-unique identifier of the plugin. + /// + /// + /// The ID is an author-declared identifier (e.g. "authkit.devtokens"), + /// is expected to be non-empty and stable across restarts. The host is + /// responsible for validating format and uniqueness before activation. + /// + string Id => Metadata.Id; + + /// + /// Gets the unique name of the plugin. + /// + /// + /// The name is used to identify the plugin in host diagnostics, + /// startup output, and other plugin-related metadata. + /// + string Name => Metadata.Name; + + /// + /// Gets an optional human-readable display name for UIs. + /// + /// + /// The host UI should display DisplayName ?? Name when presenting + /// the plugin to users. + /// + string? DisplayName => Metadata.DisplayName; + + /// + /// Gets an optional human-readable description of the plugin. + /// + /// + /// The host may display the description in startup output, + /// diagnostics, administrative interfaces, or other status surfaces. + /// + string Description => Metadata.Description; + + /// + /// Gets the version of the plugin as a semantic version (SemVer 2.0.0). + /// + /// + /// The Version replaces the previous string-based version and exposes + /// full semantic version semantics (parsing, equality, precedence). + /// + SemanticVersion Version => SemanticVersion.Parse(Metadata.Version); + + /// + /// Optional author metadata, visible in catalogs and diagnostics. + /// + string? Author => Metadata.Author; + + /// + /// Optional SPDX-style license string (no validation performed by host). + /// + string? License => Metadata.License; + + /// + /// Optional absolute URI pointing to the license text. + /// + string? LicenseUrl => Metadata.LicenseUrl; + + /// + /// Optional absolute HTTP/HTTPS URI pointing to a plugin homepage. + /// + string? Homepage => Metadata.Homepage; + + /// + /// Optional absolute HTTP/HTTPS URI pointing to the plugin repository. + /// + string? RepositoryUrl => Metadata.RepositoryUrl; + + /// + /// Optional classification tags for UI filtering. Defaults to empty. + /// Null or whitespace elements are invalid and should be rejected during validation. + /// Used for filtering plugins in UIs and catalogs. + /// + /// + /// Tags are case-sensitive strings without controlled vocabulary. + /// Example: ["security", "auth", "audit"]. + /// + IReadOnlyList Tags => Metadata.Tags; + + /// + /// Priority used for activation ordering among dependency-ready plugins. + /// Lower values are activated earlier, higher values later. + /// Defaults to 0. + /// + /// + /// The ordering algorithm is: topological sort where, among the set of currently + /// dependency-ready plugins, the next plugin is chosen by Priority ascending. + /// Dependency order (G7) wins over Priority. + /// Example: A (p. 100) → B (p-100, DependsOn A), C (p. 0) ⇒ order: A, C, B. + /// + int Priority => Metadata.Priority; + + /// + /// Indicates whether the plugin is enabled. Defaults to true. + /// + /// + /// If false, the plugin is skipped before loading (no consistency check runs for it). + /// For plugins accepted by the preload gate and subsequently loaded, + /// manifest.IsEnabled == instance.IsEnabled is part of consistency validation. + /// + bool IsEnabled => Metadata.IsEnabled; + + /// + /// Features/capabilities exposed by the plugin. Contract + /// requires Case-insensitive comparison. Defaults to an immutable empty set. + /// + /// + /// Used for pre-activation capability checks (via ) and + /// post-load consistency validation. Host checks capabilities using the + /// extension method. + /// Example: plugin.Supports("auth"). + /// + IReadOnlySet Capabilities => + System.Collections.Immutable.ImmutableHashSet.CreateRange(StringComparer.OrdinalIgnoreCase, Metadata.Capabilities); + + /// + /// Gets the metadata associated with the plugin. + /// + PluginMetadataAttribute Metadata => GetType().GetCustomAttribute() + ?? throw new InvalidOperationException($"Plugin {GetType().Name} is missing [PluginMetadata] attribute."); +} \ No newline at end of file From 7ceb75cbf1599b15a2e9d49375f1dbd92ae9e0c5 Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Tue, 15 Sep 2026 01:00:56 +0200 Subject: [PATCH 2/8] fix: resolve plugin contract namespace ambiguity --- .../Authentication/AuthorizationPolicyCollisionGuard.cs | 3 ++- src/Host/Configuration/Pipeline/AppMiddlewareConfiguration.cs | 3 ++- .../Plugins/Configuration/PluginApplicationConfiguration.cs | 1 + src/Host/Plugins/Configuration/PluginConfigurationInvoker.cs | 1 + src/Host/Plugins/Contract/PluginContractValidator.cs | 1 + src/Host/Plugins/Lifecycle/PluginLifecycleHostedService.cs | 1 + src/Host/Plugins/Loading/LoadedPlugin.cs | 1 + src/Host/Plugins/Loading/PluginLoader.cs | 1 + 8 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Host/Configuration/Authentication/AuthorizationPolicyCollisionGuard.cs b/src/Host/Configuration/Authentication/AuthorizationPolicyCollisionGuard.cs index 35be289..d5dfa35 100644 --- a/src/Host/Configuration/Authentication/AuthorizationPolicyCollisionGuard.cs +++ b/src/Host/Configuration/Authentication/AuthorizationPolicyCollisionGuard.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Reflection; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; using Host.Plugins.Loading; using Microsoft.AspNetCore.Authorization; @@ -14,7 +15,7 @@ namespace Host.Configuration.Authentication; /// silently replaces a previously registered policy when the same name is added twice, which /// would let a plugin silently overwrite another plugin's policy. To keep collisions explicit, /// the host snapshots the policy map before and after each plugin's -/// hook +/// hook /// and fails startup when a name already owned by another plugin is registered again. /// /// diff --git a/src/Host/Configuration/Pipeline/AppMiddlewareConfiguration.cs b/src/Host/Configuration/Pipeline/AppMiddlewareConfiguration.cs index dc34eb6..f0b2f63 100644 --- a/src/Host/Configuration/Pipeline/AppMiddlewareConfiguration.cs +++ b/src/Host/Configuration/Pipeline/AppMiddlewareConfiguration.cs @@ -4,6 +4,7 @@ using Host.Security.Middleware; using AuthKit.Plugins.Abstractions; using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; namespace Host.Configuration.Pipeline; @@ -19,7 +20,7 @@ namespace Host.Configuration.Pipeline; /// /// New pipeline hooks are inserted at their strongly typed /// . Plugins at the same position are -/// ordered by stable plugin ID. Legacy +/// ordered by stable plugin ID. Legacy /// middleware remains in its original slot unless the plugin opts into a new /// application or pipeline hook. /// diff --git a/src/Host/Plugins/Configuration/PluginApplicationConfiguration.cs b/src/Host/Plugins/Configuration/PluginApplicationConfiguration.cs index 30444f4..cc199f7 100644 --- a/src/Host/Plugins/Configuration/PluginApplicationConfiguration.cs +++ b/src/Host/Plugins/Configuration/PluginApplicationConfiguration.cs @@ -2,6 +2,7 @@ using AuthKit.Plugins.Abstractions; using AuthKit.Plugins.Abstractions.Contracts; using Host.Plugins.Loading; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace Host.Plugins.Configuration; diff --git a/src/Host/Plugins/Configuration/PluginConfigurationInvoker.cs b/src/Host/Plugins/Configuration/PluginConfigurationInvoker.cs index 9b34448..24f0b47 100644 --- a/src/Host/Plugins/Configuration/PluginConfigurationInvoker.cs +++ b/src/Host/Plugins/Configuration/PluginConfigurationInvoker.cs @@ -1,6 +1,7 @@ using System.Reflection; using AuthKit.Plugins.Abstractions.Contracts; using AuthKit.Plugins.Abstractions.Contracts.Plugins; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace Host.Plugins.Configuration; diff --git a/src/Host/Plugins/Contract/PluginContractValidator.cs b/src/Host/Plugins/Contract/PluginContractValidator.cs index c987ea4..3a8e905 100644 --- a/src/Host/Plugins/Contract/PluginContractValidator.cs +++ b/src/Host/Plugins/Contract/PluginContractValidator.cs @@ -2,6 +2,7 @@ using AuthKit.Plugins.Abstractions.Contracts; using AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; using Microsoft.Extensions.Logging; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace Host.Plugins.Contract; diff --git a/src/Host/Plugins/Lifecycle/PluginLifecycleHostedService.cs b/src/Host/Plugins/Lifecycle/PluginLifecycleHostedService.cs index e89f1bf..552f2ce 100644 --- a/src/Host/Plugins/Lifecycle/PluginLifecycleHostedService.cs +++ b/src/Host/Plugins/Lifecycle/PluginLifecycleHostedService.cs @@ -1,5 +1,6 @@ using AuthKit.Plugins.Abstractions.Contracts; using Host.Plugins.Loading; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace Host.Plugins.Lifecycle; diff --git a/src/Host/Plugins/Loading/LoadedPlugin.cs b/src/Host/Plugins/Loading/LoadedPlugin.cs index 7d45b49..8e2e1b9 100644 --- a/src/Host/Plugins/Loading/LoadedPlugin.cs +++ b/src/Host/Plugins/Loading/LoadedPlugin.cs @@ -1,5 +1,6 @@ using System.Reflection; using AuthKit.Plugins.Abstractions.Contracts; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace Host.Plugins.Loading; diff --git a/src/Host/Plugins/Loading/PluginLoader.cs b/src/Host/Plugins/Loading/PluginLoader.cs index c5d132b..78b8258 100644 --- a/src/Host/Plugins/Loading/PluginLoader.cs +++ b/src/Host/Plugins/Loading/PluginLoader.cs @@ -5,6 +5,7 @@ using AuthKit.Plugins.Abstractions.Models; using Host.Plugins.Contract; using Microsoft.Extensions.Logging; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace Host.Plugins.Loading; From 6dc7a02f3b79a8d7e9076bd9a3fcd26ee9a2abe9 Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Tue, 15 Sep 2026 01:02:08 +0200 Subject: [PATCH 3/8] refactor: add IAuthKitPlugin alias to host tests --- tests/Host.IntegrationTests/PluginLifecycleIntegrationTests.cs | 1 + tests/Host/PluginApplicationConfigurationTests.cs | 1 + tests/Host/PluginConfigurationInvokerTests.cs | 1 + tests/Host/PluginContractValidatorTests.cs | 1 + tests/Host/PluginHealthEndpointTests.cs | 1 + tests/Host/PluginHealthExecutorTests.cs | 1 + tests/Host/PluginInfrastructureIntegrationTests.cs | 1 + tests/Host/PluginIntegrationContractTests.cs | 1 + tests/Host/PluginLifecycleHostedServiceTests.cs | 1 + tests/Host/PluginSecurityConfigurationTests.cs | 1 + tests/Host/PluginSecurityEndpointIntegrationTests.cs | 1 + 11 files changed, 11 insertions(+) diff --git a/tests/Host.IntegrationTests/PluginLifecycleIntegrationTests.cs b/tests/Host.IntegrationTests/PluginLifecycleIntegrationTests.cs index 0f5f337..a0696c0 100644 --- a/tests/Host.IntegrationTests/PluginLifecycleIntegrationTests.cs +++ b/tests/Host.IntegrationTests/PluginLifecycleIntegrationTests.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.IntegrationTests; diff --git a/tests/Host/PluginApplicationConfigurationTests.cs b/tests/Host/PluginApplicationConfigurationTests.cs index 2083333..3673ea7 100644 --- a/tests/Host/PluginApplicationConfigurationTests.cs +++ b/tests/Host/PluginApplicationConfigurationTests.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; diff --git a/tests/Host/PluginConfigurationInvokerTests.cs b/tests/Host/PluginConfigurationInvokerTests.cs index fd33a10..3667b0a 100644 --- a/tests/Host/PluginConfigurationInvokerTests.cs +++ b/tests/Host/PluginConfigurationInvokerTests.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; diff --git a/tests/Host/PluginContractValidatorTests.cs b/tests/Host/PluginContractValidatorTests.cs index 201e1c1..9c09a4f 100644 --- a/tests/Host/PluginContractValidatorTests.cs +++ b/tests/Host/PluginContractValidatorTests.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; diff --git a/tests/Host/PluginHealthEndpointTests.cs b/tests/Host/PluginHealthEndpointTests.cs index dcd5408..cef0ec7 100644 --- a/tests/Host/PluginHealthEndpointTests.cs +++ b/tests/Host/PluginHealthEndpointTests.cs @@ -15,6 +15,7 @@ using PluginMetadataAttribute = AuthKit.Plugins.Abstractions.Contracts.Plugins.PluginMetadataAttribute; using Xunit; using Host.Plugins.Health; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; diff --git a/tests/Host/PluginHealthExecutorTests.cs b/tests/Host/PluginHealthExecutorTests.cs index bae0333..23a6059 100644 --- a/tests/Host/PluginHealthExecutorTests.cs +++ b/tests/Host/PluginHealthExecutorTests.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; diff --git a/tests/Host/PluginInfrastructureIntegrationTests.cs b/tests/Host/PluginInfrastructureIntegrationTests.cs index d571562..eb87375 100644 --- a/tests/Host/PluginInfrastructureIntegrationTests.cs +++ b/tests/Host/PluginInfrastructureIntegrationTests.cs @@ -16,6 +16,7 @@ using Swashbuckle.AspNetCore.Swagger; using Swashbuckle.AspNetCore.SwaggerGen; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; diff --git a/tests/Host/PluginIntegrationContractTests.cs b/tests/Host/PluginIntegrationContractTests.cs index 01e1556..43e6092 100644 --- a/tests/Host/PluginIntegrationContractTests.cs +++ b/tests/Host/PluginIntegrationContractTests.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; diff --git a/tests/Host/PluginLifecycleHostedServiceTests.cs b/tests/Host/PluginLifecycleHostedServiceTests.cs index 1f4e242..72431d1 100644 --- a/tests/Host/PluginLifecycleHostedServiceTests.cs +++ b/tests/Host/PluginLifecycleHostedServiceTests.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; diff --git a/tests/Host/PluginSecurityConfigurationTests.cs b/tests/Host/PluginSecurityConfigurationTests.cs index 85a4355..4bf5563 100644 --- a/tests/Host/PluginSecurityConfigurationTests.cs +++ b/tests/Host/PluginSecurityConfigurationTests.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; diff --git a/tests/Host/PluginSecurityEndpointIntegrationTests.cs b/tests/Host/PluginSecurityEndpointIntegrationTests.cs index 999ea50..4e848cc 100644 --- a/tests/Host/PluginSecurityEndpointIntegrationTests.cs +++ b/tests/Host/PluginSecurityEndpointIntegrationTests.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Host.Tests; From 0c8b9d3ed5a0a5ca43d0383f706683878edd5ba5 Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Tue, 15 Sep 2026 01:02:48 +0200 Subject: [PATCH 4/8] refactor: Align tests with contract namespaces --- tests/Plugins/Abstractions/AuthKitSecuritySchemeTypeTests.cs | 2 +- tests/Plugins/Abstractions/IAuthKitPluginCapabilitiesTests.cs | 1 + tests/Plugins/Abstractions/PluginHealthResultTests.cs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Plugins/Abstractions/AuthKitSecuritySchemeTypeTests.cs b/tests/Plugins/Abstractions/AuthKitSecuritySchemeTypeTests.cs index 5f2e166..337b3af 100644 --- a/tests/Plugins/Abstractions/AuthKitSecuritySchemeTypeTests.cs +++ b/tests/Plugins/Abstractions/AuthKitSecuritySchemeTypeTests.cs @@ -1,5 +1,5 @@ using System; -using AuthKit.Plugins.Abstractions; +using AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; using Xunit; namespace AuthKit.Plugins.Abstractions.Tests.SecuritySchemes; diff --git a/tests/Plugins/Abstractions/IAuthKitPluginCapabilitiesTests.cs b/tests/Plugins/Abstractions/IAuthKitPluginCapabilitiesTests.cs index 1f04fa0..2f8ca70 100644 --- a/tests/Plugins/Abstractions/IAuthKitPluginCapabilitiesTests.cs +++ b/tests/Plugins/Abstractions/IAuthKitPluginCapabilitiesTests.cs @@ -1,6 +1,7 @@ using AuthKit.Plugins.Abstractions.Contracts; using AuthKit.Plugins.Abstractions.Contracts.Plugins; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Plugins.Abstractions.Tests; diff --git a/tests/Plugins/Abstractions/PluginHealthResultTests.cs b/tests/Plugins/Abstractions/PluginHealthResultTests.cs index 318cda8..c5c2de2 100644 --- a/tests/Plugins/Abstractions/PluginHealthResultTests.cs +++ b/tests/Plugins/Abstractions/PluginHealthResultTests.cs @@ -3,6 +3,7 @@ using AuthKit.Plugins.Abstractions.Contracts.Plugins; using AuthKit.Plugins.Abstractions.Models; using Xunit; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.Plugins.Abstractions.Tests; From ca54d68177668b589f97ab08ea8b48e696e27ff2 Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Tue, 15 Sep 2026 01:03:33 +0200 Subject: [PATCH 5/8] refactor: disambiguate IAuthKitPlugin references --- .../AuthKit.PluginContractValidator/PluginAssemblyLoader.cs | 1 + .../src/Core/IPluginLoader.cs | 5 +++-- .../AuthKit.PluginContractValidator/src/Core/LoadedPlugin.cs | 1 + .../AuthKit.PluginContractValidator/src/Rules/HealthRule.cs | 3 ++- .../src/Rules/MetadataRule.cs | 5 +++-- .../src/Rules/MiddlewareRule.cs | 3 ++- .../src/Rules/RegistrationRule.cs | 3 ++- .../src/Rules/SecuritySchemesRule.cs | 3 ++- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/tools/AuthKit.PluginContractValidator/PluginAssemblyLoader.cs b/tools/AuthKit.PluginContractValidator/PluginAssemblyLoader.cs index a5cf4b0..54a0861 100644 --- a/tools/AuthKit.PluginContractValidator/PluginAssemblyLoader.cs +++ b/tools/AuthKit.PluginContractValidator/PluginAssemblyLoader.cs @@ -6,6 +6,7 @@ using System.Threading; using AuthKit.PluginContractValidator.Core; using AuthKit.Plugins.Abstractions.Contracts; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.PluginContractValidator; diff --git a/tools/AuthKit.PluginContractValidator/src/Core/IPluginLoader.cs b/tools/AuthKit.PluginContractValidator/src/Core/IPluginLoader.cs index f0892d8..4a22da8 100644 --- a/tools/AuthKit.PluginContractValidator/src/Core/IPluginLoader.cs +++ b/tools/AuthKit.PluginContractValidator/src/Core/IPluginLoader.cs @@ -1,15 +1,16 @@ using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; namespace AuthKit.PluginContractValidator.Core; /// -/// Loads plugin entry assembly and instantiates its implementation. +/// Loads plugin entry assembly and instantiates its implementation. /// public interface IPluginLoader { /// /// Loads the plugin entry assembly located at and activates its - /// implementation. + /// implementation. /// /// The full path to the plugin's entry assembly (named after its directory). /// diff --git a/tools/AuthKit.PluginContractValidator/src/Core/LoadedPlugin.cs b/tools/AuthKit.PluginContractValidator/src/Core/LoadedPlugin.cs index 4c16fa1..3d63c92 100644 --- a/tools/AuthKit.PluginContractValidator/src/Core/LoadedPlugin.cs +++ b/tools/AuthKit.PluginContractValidator/src/Core/LoadedPlugin.cs @@ -1,5 +1,6 @@ using System.Reflection; using AuthKit.Plugins.Abstractions.Contracts; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace AuthKit.PluginContractValidator.Core; diff --git a/tools/AuthKit.PluginContractValidator/src/Rules/HealthRule.cs b/tools/AuthKit.PluginContractValidator/src/Rules/HealthRule.cs index d70e05f..4259e53 100644 --- a/tools/AuthKit.PluginContractValidator/src/Rules/HealthRule.cs +++ b/tools/AuthKit.PluginContractValidator/src/Rules/HealthRule.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using AuthKit.PluginContractValidator.Core; using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -16,7 +17,7 @@ namespace AuthKit.PluginContractValidator.Rules; /// /// /// The rule builds an isolated service provider from the services registered -/// by the plugin and invokes . +/// by the plugin and invokes . /// Basic framework services the host would provide, such as logging, are /// registered so plugins can rely on them. Any exception during service /// configuration or health checking is reported as a contract violation. diff --git a/tools/AuthKit.PluginContractValidator/src/Rules/MetadataRule.cs b/tools/AuthKit.PluginContractValidator/src/Rules/MetadataRule.cs index bdcb933..bca9d8c 100644 --- a/tools/AuthKit.PluginContractValidator/src/Rules/MetadataRule.cs +++ b/tools/AuthKit.PluginContractValidator/src/Rules/MetadataRule.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using AuthKit.PluginContractValidator.Core; using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; namespace AuthKit.PluginContractValidator.Rules; @@ -10,8 +11,8 @@ namespace AuthKit.PluginContractValidator.Rules; /// Ensures plugin metadata (Name, Version) is present and well-formed. /// /// -/// The rule verifies that is non-empty and that -/// follows the SemVer +/// The rule verifies that is non-empty and that +/// follows the SemVer /// (major.minor.patch[-prerelease]) format expected by the host. /// public sealed class MetadataRule : IPluginContractRule diff --git a/tools/AuthKit.PluginContractValidator/src/Rules/MiddlewareRule.cs b/tools/AuthKit.PluginContractValidator/src/Rules/MiddlewareRule.cs index 25a4166..44d8227 100644 --- a/tools/AuthKit.PluginContractValidator/src/Rules/MiddlewareRule.cs +++ b/tools/AuthKit.PluginContractValidator/src/Rules/MiddlewareRule.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using AuthKit.PluginContractValidator.Core; using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; using Microsoft.AspNetCore.Http; namespace AuthKit.PluginContractValidator.Rules; @@ -25,7 +26,7 @@ public sealed class MiddlewareRule : IPluginContractRule /// /// Validates the middleware type contributed by - /// , if any. + /// , if any. /// public Task> ValidateAsync( LoadedPlugin plugin, diff --git a/tools/AuthKit.PluginContractValidator/src/Rules/RegistrationRule.cs b/tools/AuthKit.PluginContractValidator/src/Rules/RegistrationRule.cs index 5563a50..d4ca554 100644 --- a/tools/AuthKit.PluginContractValidator/src/Rules/RegistrationRule.cs +++ b/tools/AuthKit.PluginContractValidator/src/Rules/RegistrationRule.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using AuthKit.PluginContractValidator.Core; using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -19,7 +20,7 @@ public sealed class RegistrationRule : IPluginContractRule public string Name => "Registration"; /// - /// Invokes against a fresh service collection + /// Invokes against a fresh service collection /// and then builds the to confirm the dependency graph is /// constructible. /// diff --git a/tools/AuthKit.PluginContractValidator/src/Rules/SecuritySchemesRule.cs b/tools/AuthKit.PluginContractValidator/src/Rules/SecuritySchemesRule.cs index c9c3474..8a4303f 100644 --- a/tools/AuthKit.PluginContractValidator/src/Rules/SecuritySchemesRule.cs +++ b/tools/AuthKit.PluginContractValidator/src/Rules/SecuritySchemesRule.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using AuthKit.PluginContractValidator.Core; using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; using AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; namespace AuthKit.PluginContractValidator.Rules; @@ -13,7 +14,7 @@ namespace AuthKit.PluginContractValidator.Rules; /// /// /// The rule verifies that each key in the dictionary returned by -/// matches the Name of its +/// matches the Name of its /// . /// public sealed class SecuritySchemesRule : IPluginContractRule From 437526792d7588586bbe108fdb2e9263ebd9f444 Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Tue, 15 Sep 2026 01:04:26 +0200 Subject: [PATCH 6/8] refactor: align plugin contract namespaces and references --- src/Plugins/Abstractions/AuthKitApiKeyLocation.cs | 2 ++ src/Plugins/Abstractions/Contracts/PluginValidator.cs | 4 ++-- .../Abstractions/Contracts/Plugins/PluginExtensions.cs | 2 +- .../Contracts/Plugins/PluginMetadataExtensions.cs | 2 +- .../Contracts/SecuritySchemes/AuthKitSecuritySchemeType.cs | 4 +--- .../Contracts/SecuritySchemes/SecuritySchemeAttribute.cs | 4 +++- src/Plugins/Abstractions/Models/PluginManifest.cs | 3 ++- src/Plugins/Abstractions/Models/PluginMetadata.cs | 3 ++- 8 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Plugins/Abstractions/AuthKitApiKeyLocation.cs b/src/Plugins/Abstractions/AuthKitApiKeyLocation.cs index 0b7af4f..3d7c3b4 100644 --- a/src/Plugins/Abstractions/AuthKitApiKeyLocation.cs +++ b/src/Plugins/Abstractions/AuthKitApiKeyLocation.cs @@ -1,3 +1,5 @@ +using AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; + namespace AuthKit.Plugins.Abstractions; /// diff --git a/src/Plugins/Abstractions/Contracts/PluginValidator.cs b/src/Plugins/Abstractions/Contracts/PluginValidator.cs index 18eb7fd..26a60e1 100644 --- a/src/Plugins/Abstractions/Contracts/PluginValidator.cs +++ b/src/Plugins/Abstractions/Contracts/PluginValidator.cs @@ -77,7 +77,7 @@ public static void ValidateDependsOn(IReadOnlyList dependsOn, string plu } } - // Check for self dependency + // Check for self-dependency if (dependsOn.Contains(pluginId, StringComparer.OrdinalIgnoreCase)) { throw new InvalidOperationException("Plugin cannot depend on itself."); @@ -91,7 +91,7 @@ public static void ValidateDependsOn(IReadOnlyList dependsOn, string plu /// The plugin instance. /// Thrown if either manifest or plugin is null. /// Thrown if the manifest and instance are inconsistent. - public static void ValidateConsistency(PluginManifest manifest, IAuthKitPlugin plugin) + public static void ValidateConsistency(PluginManifest manifest, PluginContract.IAuthKitPlugin plugin) { if (manifest == null) { diff --git a/src/Plugins/Abstractions/Contracts/Plugins/PluginExtensions.cs b/src/Plugins/Abstractions/Contracts/Plugins/PluginExtensions.cs index 51e07f7..ed7fb5d 100644 --- a/src/Plugins/Abstractions/Contracts/Plugins/PluginExtensions.cs +++ b/src/Plugins/Abstractions/Contracts/Plugins/PluginExtensions.cs @@ -10,7 +10,7 @@ namespace AuthKit.Plugins.Abstractions.Contracts.Plugins; public static class PluginExtensions { /// The plugin whose configuration section is used. - extension(IAuthKitPlugin plugin) + extension(PluginContract.IAuthKitPlugin plugin) { /// /// Binds strongly typed options from the plugin's configuration section. diff --git a/src/Plugins/Abstractions/Contracts/Plugins/PluginMetadataExtensions.cs b/src/Plugins/Abstractions/Contracts/Plugins/PluginMetadataExtensions.cs index 63faedc..b10684d 100644 --- a/src/Plugins/Abstractions/Contracts/Plugins/PluginMetadataExtensions.cs +++ b/src/Plugins/Abstractions/Contracts/Plugins/PluginMetadataExtensions.cs @@ -14,7 +14,7 @@ public static class PluginMetadataExtensions /// The plugin instance. /// An immutable record. /// Thrown if the plugin is null. - public static PluginMetadata GetMetadata(this IAuthKitPlugin plugin) + public static PluginMetadata GetMetadata(this PluginContract.IAuthKitPlugin plugin) { ArgumentNullException.ThrowIfNull(plugin); diff --git a/src/Plugins/Abstractions/Contracts/SecuritySchemes/AuthKitSecuritySchemeType.cs b/src/Plugins/Abstractions/Contracts/SecuritySchemes/AuthKitSecuritySchemeType.cs index 64e273b..e10d42d 100644 --- a/src/Plugins/Abstractions/Contracts/SecuritySchemes/AuthKitSecuritySchemeType.cs +++ b/src/Plugins/Abstractions/Contracts/SecuritySchemes/AuthKitSecuritySchemeType.cs @@ -1,6 +1,4 @@ -using AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; - -namespace AuthKit.Plugins.Abstractions; +namespace AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; /// /// Specifies the authentication mechanism represented by an AuthKit security scheme. diff --git a/src/Plugins/Abstractions/Contracts/SecuritySchemes/SecuritySchemeAttribute.cs b/src/Plugins/Abstractions/Contracts/SecuritySchemes/SecuritySchemeAttribute.cs index 3c50fb8..47ae52d 100644 --- a/src/Plugins/Abstractions/Contracts/SecuritySchemes/SecuritySchemeAttribute.cs +++ b/src/Plugins/Abstractions/Contracts/SecuritySchemes/SecuritySchemeAttribute.cs @@ -1,3 +1,5 @@ +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; + namespace AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; /// @@ -12,7 +14,7 @@ namespace AuthKit.Plugins.Abstractions.Contracts.SecuritySchemes; /// /// /// The scheme name must match a key returned by an enabled plugin's -/// ; otherwise the host rejects +/// ; otherwise the host rejects /// requests to the endpoint as a configuration error. /// /// diff --git a/src/Plugins/Abstractions/Models/PluginManifest.cs b/src/Plugins/Abstractions/Models/PluginManifest.cs index 85872a0..df3c0c2 100644 --- a/src/Plugins/Abstractions/Models/PluginManifest.cs +++ b/src/Plugins/Abstractions/Models/PluginManifest.cs @@ -1,4 +1,5 @@ using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; using AuthKit.Plugins.Abstractions.Contracts.Plugins; namespace AuthKit.Plugins.Abstractions.Models; @@ -13,7 +14,7 @@ namespace AuthKit.Plugins.Abstractions.Models; /// /// /// The manifest mirrors selected runtime metadata exposed by -/// while remaining independent of a plugin instance. +/// while remaining independent of a plugin instance. /// This allows the host to perform pre-activation validation without loading /// or activating the plugin. /// diff --git a/src/Plugins/Abstractions/Models/PluginMetadata.cs b/src/Plugins/Abstractions/Models/PluginMetadata.cs index 52d74bf..0469275 100644 --- a/src/Plugins/Abstractions/Models/PluginMetadata.cs +++ b/src/Plugins/Abstractions/Models/PluginMetadata.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.PluginContract; namespace AuthKit.Plugins.Abstractions.Models; @@ -7,7 +8,7 @@ namespace AuthKit.Plugins.Abstractions.Models; /// Represents an immutable runtime snapshot of plugin metadata. /// /// -/// This record aggregates all metadata from +/// This record aggregates all metadata from /// and . It is created after the plugin is loaded /// and provides a consistent, immutable view of the plugin's metadata. /// From f545e22baf7ff4a15f990dc7f75edaf61c95d0d8 Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Tue, 15 Sep 2026 01:05:36 +0200 Subject: [PATCH 7/8] fix: use plugin context for plugin configuration --- src/Host/appsettings.json | 6 ++++-- src/Plugins/Solutions/DevTokens/DevTokensPlugin.cs | 8 ++++---- src/Plugins/Solutions/DevTools/DevToolsPlugin.cs | 7 ++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Host/appsettings.json b/src/Host/appsettings.json index b7cf050..d5bf9c6 100644 --- a/src/Host/appsettings.json +++ b/src/Host/appsettings.json @@ -2,8 +2,10 @@ "ConnectionStrings": { "Marten": "Host=authdev-db;Port=5432;Database=AuthDev;Username=postgres;Password=postgres" }, - "AuthKit": { - "MaxDeveloperTokens": 3 + "Plugins": { + "authkit.devtokens": { + "MaxDeveloperTokens": 3 + } }, "Encryption": { "AES_MASTER_KEY": "Tww4kOE+310tZvqXvw1tg0779qccuy8t4vrYthcPP7c=" diff --git a/src/Plugins/Solutions/DevTokens/DevTokensPlugin.cs b/src/Plugins/Solutions/DevTokens/DevTokensPlugin.cs index d628210..28f1234 100644 --- a/src/Plugins/Solutions/DevTokens/DevTokensPlugin.cs +++ b/src/Plugins/Solutions/DevTokens/DevTokensPlugin.cs @@ -14,8 +14,8 @@ using DevTokens.UseCase.Commands.Validations; using Marten; using Microsoft.AspNetCore.Authorization; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace DevTokens; @@ -56,10 +56,10 @@ public sealed class DevTokensPlugin : IAuthKitPlugin /// and authorization components in the dependency injection container. /// /// The used to register plugin services. - /// Application configuration used to configure . - public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + /// Stable plugin context including the plugin-scoped configuration section. + public void ConfigureServices(IServiceCollection services, AuthKitPluginContext context) { - services.Configure(configuration.GetSection("AuthKit")); + services.Configure(context.Configuration); services.AddScoped(); services.AddScoped(); diff --git a/src/Plugins/Solutions/DevTools/DevToolsPlugin.cs b/src/Plugins/Solutions/DevTools/DevToolsPlugin.cs index 341f1ee..3e6d922 100644 --- a/src/Plugins/Solutions/DevTools/DevToolsPlugin.cs +++ b/src/Plugins/Solutions/DevTools/DevToolsPlugin.cs @@ -8,6 +8,7 @@ using DevTools.Runtime; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; namespace DevTools; @@ -51,10 +52,10 @@ public sealed class DevToolsPlugin : IAuthKitPlugin /// serving components in the dependency injection container. /// /// The used to register plugin services. - /// Application configuration used to configure . - public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + /// Stable plugin context including the plugin-scoped configuration section. + public void ConfigureServices(IServiceCollection services, AuthKitPluginContext context) { - services.Configure(configuration.GetSection("DevTools")); + services.Configure(context.Configuration); services.AddSingleton(); services.AddSingleton(); From f8aa767f29593a88b2d57ce4cf51ea9874f7ef7b Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Tue, 15 Sep 2026 01:17:42 +0200 Subject: [PATCH 8/8] feat: centralize plugin configuration invocation --- Directory.Packages.props | 1 + .../AuthKit.PluginContractValidator.csproj | 1 + .../src/Core/PluginConfigurationInvoker.cs | 89 +++++++++++++++++++ .../src/Rules/HealthRule.cs | 3 +- .../src/Rules/RegistrationRule.cs | 3 +- 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 tools/AuthKit.PluginContractValidator/src/Core/PluginConfigurationInvoker.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 09cd0b0..b1dd289 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,6 +21,7 @@ + diff --git a/tools/AuthKit.PluginContractValidator/AuthKit.PluginContractValidator.csproj b/tools/AuthKit.PluginContractValidator/AuthKit.PluginContractValidator.csproj index 0889449..feed372 100644 --- a/tools/AuthKit.PluginContractValidator/AuthKit.PluginContractValidator.csproj +++ b/tools/AuthKit.PluginContractValidator/AuthKit.PluginContractValidator.csproj @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/tools/AuthKit.PluginContractValidator/src/Core/PluginConfigurationInvoker.cs b/tools/AuthKit.PluginContractValidator/src/Core/PluginConfigurationInvoker.cs new file mode 100644 index 0000000..8f6d168 --- /dev/null +++ b/tools/AuthKit.PluginContractValidator/src/Core/PluginConfigurationInvoker.cs @@ -0,0 +1,89 @@ +using System; +using System.Reflection; +using AuthKit.Plugins.Abstractions.Contracts; +using AuthKit.Plugins.Abstractions.Contracts.Plugins; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using IAuthKitPlugin = AuthKit.Plugins.Abstractions.Contracts.PluginContract.IAuthKitPlugin; + +namespace AuthKit.PluginContractValidator.Core; + +/// +/// Selects and invokes the most specific ConfigureServices overload a plugin +/// implements, mirroring the selection performed by the AuthKit host. +/// +/// +/// +/// Plugins may implement any single supported ConfigureServices overload. The +/// invoker picks the most specific one actually overridden by the plugin instead of +/// requiring all plugins to adopt a single signature. +/// +/// +/// The default interface implementations of IAuthKitPlugin.ConfigureServices +/// forward to one another, but the concrete method a plugin implements is only visible +/// on the concrete type, so the overload is resolved through reflection exactly as the +/// host resolver does. +/// +/// +internal static class PluginConfigurationInvoker +{ + /// + /// Invokes the plugin's ConfigureServices, returning the service collection the + /// plugin registered into. + /// + /// The loaded plugin to configure. + /// The service collection the plugin should register into. + /// The application configuration used to build the plugin context. + /// The service collection containing the plugin's registrations. + public static IServiceCollection Configure( + IAuthKitPlugin plugin, + IServiceCollection services, + IConfiguration configuration) + { + var pluginType = plugin.GetType(); + + if (HasImplementation(pluginType, typeof(IServiceCollection), typeof(AuthKitPluginContext))) + { + plugin.ConfigureServices( + services, + new AuthKitPluginContext( + plugin.Id, + plugin.Name, + plugin.GetPluginConfiguration(configuration), + configuration)); + return services; + } + + if (HasImplementation(pluginType, typeof(IHostApplicationBuilder), typeof(IConfiguration))) + { + var builder = Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings()); + plugin.ConfigureServices(builder, configuration); + return builder.Services; + } + + plugin.ConfigureServices(services, configuration); + return services; + } + + /// + /// Determines whether a plugin provides a concrete implementation of the + /// ConfigureServices overload identified by the given parameter types. + /// + /// The plugin type to inspect. + /// The parameter types that identify the overload. + /// + /// true when the plugin overrides the overload; otherwise, false. + /// + private static bool HasImplementation(Type pluginType, params Type[] parameterTypes) + { + var method = pluginType.GetMethod( + nameof(IAuthKitPlugin.ConfigureServices), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + types: parameterTypes, + modifiers: null); + + return method is not null && method.DeclaringType != typeof(IAuthKitPlugin); + } +} \ No newline at end of file diff --git a/tools/AuthKit.PluginContractValidator/src/Rules/HealthRule.cs b/tools/AuthKit.PluginContractValidator/src/Rules/HealthRule.cs index 4259e53..cc02ee8 100644 --- a/tools/AuthKit.PluginContractValidator/src/Rules/HealthRule.cs +++ b/tools/AuthKit.PluginContractValidator/src/Rules/HealthRule.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using AuthKit.PluginContractValidator.Core; using AuthKit.Plugins.Abstractions.Contracts; -using AuthKit.Plugins.Abstractions.Contracts.PluginContract; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -49,7 +48,7 @@ public async Task> ValidateAsync( try { - plugin.Instance.ConfigureServices(services, configuration); + PluginConfigurationInvoker.Configure(plugin.Instance, services, configuration); } catch (Exception ex) { diff --git a/tools/AuthKit.PluginContractValidator/src/Rules/RegistrationRule.cs b/tools/AuthKit.PluginContractValidator/src/Rules/RegistrationRule.cs index d4ca554..944e1c9 100644 --- a/tools/AuthKit.PluginContractValidator/src/Rules/RegistrationRule.cs +++ b/tools/AuthKit.PluginContractValidator/src/Rules/RegistrationRule.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using AuthKit.PluginContractValidator.Core; using AuthKit.Plugins.Abstractions.Contracts; -using AuthKit.Plugins.Abstractions.Contracts.PluginContract; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -40,7 +39,7 @@ public Task> ValidateAsync( try { - plugin.Instance.ConfigureServices(services, configuration); + PluginConfigurationInvoker.Configure(plugin.Instance, services, configuration); } catch (Exception ex) {