sprints work

This commit is contained in:
StellaOps Bot
2025-12-25 12:19:12 +02:00
parent 223843f1d1
commit 2a06f780cf
224 changed files with 41796 additions and 1515 deletions

View File

@@ -0,0 +1,92 @@
// -----------------------------------------------------------------------------
// OidcClaimsEnricher.cs
// Claims enricher for OIDC-authenticated principals.
// -----------------------------------------------------------------------------
using System.Security.Claims;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Authority.Plugins.Abstractions;
namespace StellaOps.Authority.Plugin.Oidc.Claims;
/// <summary>
/// Enriches claims for OIDC-authenticated users.
/// </summary>
internal sealed class OidcClaimsEnricher : IClaimsEnricher
{
private readonly string pluginName;
private readonly IOptionsMonitor<OidcPluginOptions> optionsMonitor;
private readonly ILogger<OidcClaimsEnricher> logger;
public OidcClaimsEnricher(
string pluginName,
IOptionsMonitor<OidcPluginOptions> optionsMonitor,
ILogger<OidcClaimsEnricher> logger)
{
this.pluginName = pluginName ?? throw new ArgumentNullException(nameof(pluginName));
this.optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public ValueTask EnrichAsync(
ClaimsIdentity identity,
AuthorityClaimsEnrichmentContext context,
CancellationToken cancellationToken)
{
if (identity == null)
{
throw new ArgumentNullException(nameof(identity));
}
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var options = optionsMonitor.Get(pluginName);
// Add OIDC-specific claims
AddClaimIfMissing(identity, "idp", "oidc");
AddClaimIfMissing(identity, "auth_method", "oidc");
// Add user attributes as claims
if (context.User != null)
{
foreach (var attr in context.User.Attributes)
{
if (!string.IsNullOrWhiteSpace(attr.Value))
{
AddClaimIfMissing(identity, $"oidc_{attr.Key}", attr.Value);
}
}
// Ensure roles are added
foreach (var role in context.User.Roles)
{
var roleClaim = identity.Claims.FirstOrDefault(c =>
c.Type == ClaimTypes.Role && string.Equals(c.Value, role, StringComparison.OrdinalIgnoreCase));
if (roleClaim == null)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
}
}
logger.LogDebug(
"Enriched OIDC claims for identity {Name}. Total claims: {Count}",
identity.Name ?? "unknown",
identity.Claims.Count());
return ValueTask.CompletedTask;
}
private static void AddClaimIfMissing(ClaimsIdentity identity, string type, string value)
{
if (!identity.HasClaim(c => string.Equals(c.Type, type, StringComparison.OrdinalIgnoreCase)))
{
identity.AddClaim(new Claim(type, value));
}
}
}

View File

@@ -0,0 +1,251 @@
// -----------------------------------------------------------------------------
// OidcCredentialStore.cs
// Credential store for validating OIDC tokens.
// -----------------------------------------------------------------------------
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Cryptography.Audit;
namespace StellaOps.Authority.Plugin.Oidc.Credentials;
/// <summary>
/// Credential store that validates OIDC access tokens and ID tokens.
/// </summary>
internal sealed class OidcCredentialStore : IUserCredentialStore
{
private readonly string pluginName;
private readonly IOptionsMonitor<OidcPluginOptions> optionsMonitor;
private readonly IMemoryCache sessionCache;
private readonly ILogger<OidcCredentialStore> logger;
private readonly ConfigurationManager<OpenIdConnectConfiguration> configurationManager;
private readonly JwtSecurityTokenHandler tokenHandler;
public OidcCredentialStore(
string pluginName,
IOptionsMonitor<OidcPluginOptions> optionsMonitor,
IMemoryCache sessionCache,
ILogger<OidcCredentialStore> logger)
{
this.pluginName = pluginName ?? throw new ArgumentNullException(nameof(pluginName));
this.optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
this.sessionCache = sessionCache ?? throw new ArgumentNullException(nameof(sessionCache));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
var options = optionsMonitor.Get(pluginName);
var metadataAddress = $"{options.Authority.TrimEnd('/')}/.well-known/openid-configuration";
configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
metadataAddress,
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever { RequireHttps = options.RequireHttpsMetadata })
{
RefreshInterval = options.MetadataRefreshInterval,
AutomaticRefreshInterval = options.AutomaticRefreshInterval
};
tokenHandler = new JwtSecurityTokenHandler
{
MapInboundClaims = false
};
}
public async ValueTask<AuthorityCredentialVerificationResult> VerifyPasswordAsync(
string username,
string password,
CancellationToken cancellationToken)
{
// OIDC plugin validates tokens, not passwords.
// The "password" field contains the access token or ID token.
var token = password;
if (string.IsNullOrWhiteSpace(token))
{
return AuthorityCredentialVerificationResult.Failure(
AuthorityCredentialFailureCode.InvalidCredentials,
"Token is required for OIDC authentication.");
}
try
{
var options = optionsMonitor.Get(pluginName);
var configuration = await configurationManager.GetConfigurationAsync(cancellationToken).ConfigureAwait(false);
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = options.ValidateIssuer,
ValidIssuer = configuration.Issuer,
ValidateAudience = options.ValidateAudience,
ValidAudience = options.Audience ?? options.ClientId,
ValidateLifetime = options.ValidateLifetime,
ClockSkew = options.ClockSkew,
IssuerSigningKeys = configuration.SigningKeys,
ValidateIssuerSigningKey = true,
NameClaimType = options.UsernameClaimType,
RoleClaimType = options.RoleClaimTypes.FirstOrDefault() ?? "roles"
};
var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
var jwtToken = validatedToken as JwtSecurityToken;
if (jwtToken == null)
{
return AuthorityCredentialVerificationResult.Failure(
AuthorityCredentialFailureCode.InvalidCredentials,
"Invalid token format.");
}
var subjectId = GetClaimValue(principal.Claims, options.SubjectClaimType) ?? jwtToken.Subject;
var usernameValue = GetClaimValue(principal.Claims, options.UsernameClaimType) ?? username;
var displayName = GetClaimValue(principal.Claims, options.DisplayNameClaimType);
var email = GetClaimValue(principal.Claims, options.EmailClaimType);
if (string.IsNullOrWhiteSpace(subjectId))
{
return AuthorityCredentialVerificationResult.Failure(
AuthorityCredentialFailureCode.InvalidCredentials,
"Token does not contain a valid subject claim.");
}
var roles = ExtractRoles(principal.Claims, options);
var attributes = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["email"] = email,
["issuer"] = jwtToken.Issuer,
["audience"] = string.Join(",", jwtToken.Audiences),
["token_type"] = GetClaimValue(principal.Claims, "token_type") ?? "access_token"
};
var user = new AuthorityUserDescriptor(
subjectId: subjectId,
username: usernameValue,
displayName: displayName,
requiresPasswordReset: false,
roles: roles.ToArray(),
attributes: attributes);
// Cache the session
var cacheKey = $"oidc:session:{subjectId}";
sessionCache.Set(cacheKey, user, options.SessionCacheDuration);
logger.LogInformation(
"OIDC token validated for user {Username} (subject: {SubjectId}) from issuer {Issuer}",
usernameValue, subjectId, jwtToken.Issuer);
return AuthorityCredentialVerificationResult.Success(
user,
"Token validated successfully.",
new[]
{
new AuthEventProperty { Name = "oidc_issuer", Value = ClassifiedString.Public(jwtToken.Issuer) },
new AuthEventProperty { Name = "token_valid_until", Value = ClassifiedString.Public(jwtToken.ValidTo.ToString("O")) }
});
}
catch (SecurityTokenExpiredException ex)
{
logger.LogWarning(ex, "OIDC token expired for user {Username}", username);
return AuthorityCredentialVerificationResult.Failure(
AuthorityCredentialFailureCode.InvalidCredentials,
"Token has expired.");
}
catch (SecurityTokenInvalidSignatureException ex)
{
logger.LogWarning(ex, "OIDC token signature invalid for user {Username}", username);
return AuthorityCredentialVerificationResult.Failure(
AuthorityCredentialFailureCode.InvalidCredentials,
"Token signature is invalid.");
}
catch (SecurityTokenException ex)
{
logger.LogWarning(ex, "OIDC token validation failed for user {Username}", username);
return AuthorityCredentialVerificationResult.Failure(
AuthorityCredentialFailureCode.InvalidCredentials,
$"Token validation failed: {ex.Message}");
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected error during OIDC token validation for user {Username}", username);
return AuthorityCredentialVerificationResult.Failure(
AuthorityCredentialFailureCode.UnknownError,
"An unexpected error occurred during token validation.");
}
}
public ValueTask<AuthorityPluginOperationResult<AuthorityUserDescriptor>> UpsertUserAsync(
AuthorityUserRegistration registration,
CancellationToken cancellationToken)
{
// OIDC is a federated identity provider - users are managed externally.
// We only cache session data, not user records.
logger.LogDebug("UpsertUserAsync called on OIDC plugin - operation not supported for federated IdP.");
return ValueTask.FromResult(
AuthorityPluginOperationResult<AuthorityUserDescriptor>.Failure(
"not_supported",
"OIDC plugin does not support user provisioning - users are managed by the external identity provider."));
}
public ValueTask<AuthorityUserDescriptor?> FindBySubjectAsync(
string subjectId,
CancellationToken cancellationToken)
{
var cacheKey = $"oidc:session:{subjectId}";
if (sessionCache.TryGetValue<AuthorityUserDescriptor>(cacheKey, out var cached))
{
return ValueTask.FromResult<AuthorityUserDescriptor?>(cached);
}
return ValueTask.FromResult<AuthorityUserDescriptor?>(null);
}
private static string? GetClaimValue(IEnumerable<Claim> claims, string claimType)
{
return claims
.FirstOrDefault(c => string.Equals(c.Type, claimType, StringComparison.OrdinalIgnoreCase))
?.Value;
}
private static List<string> ExtractRoles(IEnumerable<Claim> claims, OidcPluginOptions options)
{
var roles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
// Add default roles
foreach (var defaultRole in options.RoleMapping.DefaultRoles)
{
roles.Add(defaultRole);
}
// Extract roles from configured claim types
foreach (var claimType in options.RoleClaimTypes)
{
var roleClaims = claims.Where(c =>
string.Equals(c.Type, claimType, StringComparison.OrdinalIgnoreCase));
foreach (var claim in roleClaims)
{
var roleValue = claim.Value;
// Try to map the role
if (options.RoleMapping.Enabled &&
options.RoleMapping.Mappings.TryGetValue(roleValue, out var mappedRole))
{
roles.Add(mappedRole);
}
else if (options.RoleMapping.IncludeUnmappedRoles || !options.RoleMapping.Enabled)
{
roles.Add(roleValue);
}
}
}
return roles.ToList();
}
}

View File

@@ -0,0 +1,126 @@
// -----------------------------------------------------------------------------
// OidcIdentityProviderPlugin.cs
// OIDC identity provider plugin implementation.
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Oidc.Claims;
using StellaOps.Authority.Plugin.Oidc.Credentials;
namespace StellaOps.Authority.Plugin.Oidc;
/// <summary>
/// OIDC identity provider plugin for federated authentication.
/// </summary>
internal sealed class OidcIdentityProviderPlugin : IIdentityProviderPlugin
{
private readonly AuthorityPluginContext pluginContext;
private readonly OidcCredentialStore credentialStore;
private readonly OidcClaimsEnricher claimsEnricher;
private readonly IOptionsMonitor<OidcPluginOptions> optionsMonitor;
private readonly ILogger<OidcIdentityProviderPlugin> logger;
private readonly AuthorityIdentityProviderCapabilities capabilities;
public OidcIdentityProviderPlugin(
AuthorityPluginContext pluginContext,
OidcCredentialStore credentialStore,
OidcClaimsEnricher claimsEnricher,
IOptionsMonitor<OidcPluginOptions> optionsMonitor,
ILogger<OidcIdentityProviderPlugin> logger)
{
this.pluginContext = pluginContext ?? throw new ArgumentNullException(nameof(pluginContext));
this.credentialStore = credentialStore ?? throw new ArgumentNullException(nameof(credentialStore));
this.claimsEnricher = claimsEnricher ?? throw new ArgumentNullException(nameof(claimsEnricher));
this.optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
// Validate configuration on startup
var options = optionsMonitor.Get(pluginContext.Manifest.Name);
options.Validate();
// OIDC supports password (token validation) but not client provisioning
// (since users are managed by the external IdP)
var manifestCapabilities = AuthorityIdentityProviderCapabilities.FromCapabilities(
pluginContext.Manifest.Capabilities);
capabilities = new AuthorityIdentityProviderCapabilities(
SupportsPassword: true,
SupportsMfa: manifestCapabilities.SupportsMfa,
SupportsClientProvisioning: false,
SupportsBootstrap: false);
logger.LogInformation(
"OIDC plugin '{PluginName}' initialized with authority: {Authority}",
pluginContext.Manifest.Name,
options.Authority);
}
public string Name => pluginContext.Manifest.Name;
public string Type => pluginContext.Manifest.Type;
public AuthorityPluginContext Context => pluginContext;
public IUserCredentialStore Credentials => credentialStore;
public IClaimsEnricher ClaimsEnricher => claimsEnricher;
public IClientProvisioningStore? ClientProvisioning => null;
public AuthorityIdentityProviderCapabilities Capabilities => capabilities;
public async ValueTask<AuthorityPluginHealthResult> CheckHealthAsync(CancellationToken cancellationToken)
{
try
{
var options = optionsMonitor.Get(Name);
var metadataAddress = $"{options.Authority.TrimEnd('/')}/.well-known/openid-configuration";
using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
var response = await httpClient.GetAsync(metadataAddress, cancellationToken).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
logger.LogDebug("OIDC plugin '{PluginName}' health check passed.", Name);
return AuthorityPluginHealthResult.Healthy(
"OIDC metadata endpoint is accessible.",
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["authority"] = options.Authority,
["metadata_status"] = "ok"
});
}
else
{
logger.LogWarning(
"OIDC plugin '{PluginName}' health check degraded: metadata returned {StatusCode}.",
Name, response.StatusCode);
return AuthorityPluginHealthResult.Degraded(
$"OIDC metadata endpoint returned {response.StatusCode}.",
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
["authority"] = options.Authority,
["http_status"] = ((int)response.StatusCode).ToString()
});
}
}
catch (TaskCanceledException)
{
logger.LogWarning("OIDC plugin '{PluginName}' health check timed out.", Name);
return AuthorityPluginHealthResult.Degraded("OIDC metadata endpoint request timed out.");
}
catch (HttpRequestException ex)
{
logger.LogWarning(ex, "OIDC plugin '{PluginName}' health check failed.", Name);
return AuthorityPluginHealthResult.Unavailable($"Cannot reach OIDC authority: {ex.Message}");
}
catch (Exception ex)
{
logger.LogError(ex, "OIDC plugin '{PluginName}' health check failed unexpectedly.", Name);
return AuthorityPluginHealthResult.Unavailable($"Health check failed: {ex.Message}");
}
}
}

View File

@@ -0,0 +1,211 @@
// -----------------------------------------------------------------------------
// OidcPluginOptions.cs
// Configuration options for the OIDC identity provider plugin.
// -----------------------------------------------------------------------------
namespace StellaOps.Authority.Plugin.Oidc;
/// <summary>
/// Configuration options for the OIDC identity provider plugin.
/// </summary>
public sealed class OidcPluginOptions
{
/// <summary>
/// The OIDC authority URL (e.g., https://login.microsoftonline.com/tenant).
/// </summary>
public string Authority { get; set; } = string.Empty;
/// <summary>
/// The OAuth2 client ID for this application.
/// </summary>
public string ClientId { get; set; } = string.Empty;
/// <summary>
/// The OAuth2 client secret (for confidential clients).
/// </summary>
public string? ClientSecret { get; set; }
/// <summary>
/// Expected audience for token validation.
/// </summary>
public string? Audience { get; set; }
/// <summary>
/// Scopes to request during authorization.
/// </summary>
public IReadOnlyCollection<string> Scopes { get; set; } = new[] { "openid", "profile", "email" };
/// <summary>
/// Claim type used as the unique user identifier.
/// </summary>
public string SubjectClaimType { get; set; } = "sub";
/// <summary>
/// Claim type used for the username.
/// </summary>
public string UsernameClaimType { get; set; } = "preferred_username";
/// <summary>
/// Claim type used for the display name.
/// </summary>
public string DisplayNameClaimType { get; set; } = "name";
/// <summary>
/// Claim type used for email.
/// </summary>
public string EmailClaimType { get; set; } = "email";
/// <summary>
/// Claim types containing user roles.
/// </summary>
public IReadOnlyCollection<string> RoleClaimTypes { get; set; } = new[] { "roles", "role", "groups" };
/// <summary>
/// Whether to validate the issuer.
/// </summary>
public bool ValidateIssuer { get; set; } = true;
/// <summary>
/// Whether to validate the audience.
/// </summary>
public bool ValidateAudience { get; set; } = true;
/// <summary>
/// Whether to validate token lifetime.
/// </summary>
public bool ValidateLifetime { get; set; } = true;
/// <summary>
/// Clock skew tolerance for token validation.
/// </summary>
public TimeSpan ClockSkew { get; set; } = TimeSpan.FromMinutes(5);
/// <summary>
/// Whether to require HTTPS for metadata endpoint.
/// </summary>
public bool RequireHttpsMetadata { get; set; } = true;
/// <summary>
/// Whether to require asymmetric key algorithms (RS*, ES*).
/// Rejects symmetric algorithms (HS*) when enabled.
/// </summary>
public bool RequireAsymmetricKey { get; set; } = false;
/// <summary>
/// Metadata refresh interval.
/// </summary>
public TimeSpan MetadataRefreshInterval { get; set; } = TimeSpan.FromHours(24);
/// <summary>
/// Automatic metadata refresh interval (when keys change).
/// </summary>
public TimeSpan AutomaticRefreshInterval { get; set; } = TimeSpan.FromHours(12);
/// <summary>
/// Cache duration for user sessions.
/// </summary>
public TimeSpan SessionCacheDuration { get; set; } = TimeSpan.FromMinutes(30);
/// <summary>
/// Whether to support client credentials flow.
/// </summary>
public bool SupportClientCredentials { get; set; } = true;
/// <summary>
/// Whether to support authorization code flow.
/// </summary>
public bool SupportAuthorizationCode { get; set; } = true;
/// <summary>
/// Redirect URI for authorization code flow.
/// </summary>
public Uri? RedirectUri { get; set; }
/// <summary>
/// Post-logout redirect URI.
/// </summary>
public Uri? PostLogoutRedirectUri { get; set; }
/// <summary>
/// Role mapping configuration.
/// </summary>
public OidcRoleMappingOptions RoleMapping { get; set; } = new();
/// <summary>
/// Token exchange options (for on-behalf-of flow).
/// </summary>
public OidcTokenExchangeOptions TokenExchange { get; set; } = new();
/// <summary>
/// Validates the options are properly configured.
/// </summary>
public void Validate()
{
if (string.IsNullOrWhiteSpace(Authority))
{
throw new InvalidOperationException("OIDC Authority is required.");
}
if (string.IsNullOrWhiteSpace(ClientId))
{
throw new InvalidOperationException("OIDC ClientId is required.");
}
if (!Uri.TryCreate(Authority, UriKind.Absolute, out var authorityUri))
{
throw new InvalidOperationException($"Invalid OIDC Authority URL: {Authority}");
}
if (RequireHttpsMetadata && !string.Equals(authorityUri.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("OIDC Authority must use HTTPS when RequireHttpsMetadata is true.");
}
}
}
/// <summary>
/// Role mapping configuration for OIDC.
/// </summary>
public sealed class OidcRoleMappingOptions
{
/// <summary>
/// Whether to enable role mapping.
/// </summary>
public bool Enabled { get; set; } = true;
/// <summary>
/// Mapping from IdP group/role names to StellaOps roles.
/// </summary>
public Dictionary<string, string> Mappings { get; set; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Default roles assigned to all authenticated users.
/// </summary>
public IReadOnlyCollection<string> DefaultRoles { get; set; } = Array.Empty<string>();
/// <summary>
/// Whether to include unmapped roles from the IdP.
/// </summary>
public bool IncludeUnmappedRoles { get; set; } = false;
}
/// <summary>
/// Token exchange options for on-behalf-of flows.
/// </summary>
public sealed class OidcTokenExchangeOptions
{
/// <summary>
/// Whether token exchange is enabled.
/// </summary>
public bool Enabled { get; set; } = false;
/// <summary>
/// Token exchange endpoint (if different from token endpoint).
/// </summary>
public string? TokenExchangeEndpoint { get; set; }
/// <summary>
/// Scopes to request during token exchange.
/// </summary>
public IReadOnlyCollection<string> Scopes { get; set; } = Array.Empty<string>();
}

View File

@@ -0,0 +1,85 @@
// -----------------------------------------------------------------------------
// OidcPluginRegistrar.cs
// Registrar for the OIDC identity provider plugin.
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Oidc.Claims;
using StellaOps.Authority.Plugin.Oidc.Credentials;
namespace StellaOps.Authority.Plugin.Oidc;
/// <summary>
/// Registrar for the OIDC identity provider plugin.
/// </summary>
public static class OidcPluginRegistrar
{
/// <summary>
/// The plugin type identifier.
/// </summary>
public const string PluginType = "oidc";
/// <summary>
/// Registers the OIDC plugin with the given context.
/// </summary>
public static IIdentityProviderPlugin Register(
AuthorityPluginRegistrationContext registrationContext,
IServiceProvider serviceProvider)
{
if (registrationContext == null) throw new ArgumentNullException(nameof(registrationContext));
if (serviceProvider == null) throw new ArgumentNullException(nameof(serviceProvider));
var pluginContext = registrationContext.Plugin;
var pluginName = pluginContext.Manifest.Name;
var optionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<OidcPluginOptions>>();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
// Get or create a memory cache for sessions
var sessionCache = serviceProvider.GetService<IMemoryCache>()
?? new MemoryCache(new MemoryCacheOptions());
var credentialStore = new OidcCredentialStore(
pluginName,
optionsMonitor,
sessionCache,
loggerFactory.CreateLogger<OidcCredentialStore>());
var claimsEnricher = new OidcClaimsEnricher(
pluginName,
optionsMonitor,
loggerFactory.CreateLogger<OidcClaimsEnricher>());
var plugin = new OidcIdentityProviderPlugin(
pluginContext,
credentialStore,
claimsEnricher,
optionsMonitor,
loggerFactory.CreateLogger<OidcIdentityProviderPlugin>());
return plugin;
}
/// <summary>
/// Configures services required by the OIDC plugin.
/// </summary>
public static IServiceCollection AddOidcPlugin(
this IServiceCollection services,
string pluginName,
Action<OidcPluginOptions>? configureOptions = null)
{
services.AddMemoryCache();
services.AddHttpClient();
if (configureOptions != null)
{
services.Configure(pluginName, configureOptions);
}
return services;
}
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<RootNamespace>StellaOps.Authority.Plugin.Oidc</RootNamespace>
<Description>StellaOps Authority OIDC Identity Provider Plugin</Description>
<IsAuthorityPlugin>true</IsAuthorityPlugin>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Authority.Plugins.Abstractions\StellaOps.Authority.Plugins.Abstractions.csproj" />
<ProjectReference Include="..\StellaOps.Auth.Abstractions\StellaOps.Auth.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.10.0" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.10.0" />
</ItemGroup>
</Project>