audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories

This commit is contained in:
master
2026-01-07 18:49:59 +02:00
parent 04ec098046
commit 608a7f85c0
866 changed files with 56323 additions and 6231 deletions

View File

@@ -16,9 +16,5 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="xunit.runner.visualstudio" >
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -22,6 +22,7 @@ internal sealed class LdapIdentityProviderPlugin : IIdentityProviderPlugin
private readonly IOptionsMonitor<LdapPluginOptions> optionsMonitor;
private readonly LdapClientProvisioningStore clientProvisioningStore;
private readonly ILogger<LdapIdentityProviderPlugin> logger;
private readonly TimeProvider timeProvider;
private readonly LdapCapabilityProbe capabilityProbe;
private readonly AuthorityIdentityProviderCapabilities manifestCapabilities;
private readonly SemaphoreSlim capabilityGate = new(1, 1);
@@ -38,7 +39,8 @@ internal sealed class LdapIdentityProviderPlugin : IIdentityProviderPlugin
ILdapConnectionFactory connectionFactory,
IOptionsMonitor<LdapPluginOptions> optionsMonitor,
LdapClientProvisioningStore clientProvisioningStore,
ILogger<LdapIdentityProviderPlugin> logger)
ILogger<LdapIdentityProviderPlugin> logger,
TimeProvider? timeProvider = null)
{
this.pluginContext = pluginContext ?? throw new ArgumentNullException(nameof(pluginContext));
this.credentialStore = credentialStore ?? throw new ArgumentNullException(nameof(credentialStore));
@@ -47,6 +49,7 @@ internal sealed class LdapIdentityProviderPlugin : IIdentityProviderPlugin
this.optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
this.clientProvisioningStore = clientProvisioningStore ?? throw new ArgumentNullException(nameof(clientProvisioningStore));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.timeProvider = timeProvider ?? TimeProvider.System;
capabilityProbe = new LdapCapabilityProbe(pluginContext.Manifest.Name, connectionFactory, logger);
@@ -142,7 +145,7 @@ internal sealed class LdapIdentityProviderPlugin : IIdentityProviderPlugin
var checkBootstrap = manifestCapabilities.SupportsBootstrap && options.Bootstrap.Enabled;
var fingerprint = LdapCapabilitySnapshotCache.ComputeFingerprint(options, checkProvisioning, checkBootstrap);
if (LdapCapabilitySnapshotCache.TryGet(Name, fingerprint, DateTimeOffset.UtcNow, out var snapshot))
if (LdapCapabilitySnapshotCache.TryGet(Name, fingerprint, timeProvider.GetUtcNow(), out var snapshot))
{
UpdateCapabilities(snapshot, checkProvisioning, checkBootstrap, logDegrade: true);
}
@@ -158,7 +161,7 @@ internal sealed class LdapIdentityProviderPlugin : IIdentityProviderPlugin
var checkProvisioning = manifestCapabilities.SupportsClientProvisioning && options.ClientProvisioning.Enabled;
var checkBootstrap = manifestCapabilities.SupportsBootstrap && options.Bootstrap.Enabled;
var fingerprint = LdapCapabilitySnapshotCache.ComputeFingerprint(options, checkProvisioning, checkBootstrap);
var now = DateTimeOffset.UtcNow;
var now = timeProvider.GetUtcNow();
if (LdapCapabilitySnapshotCache.TryGet(Name, fingerprint, now, out var cached))
{
@@ -169,7 +172,7 @@ internal sealed class LdapIdentityProviderPlugin : IIdentityProviderPlugin
await capabilityGate.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (LdapCapabilitySnapshotCache.TryGet(Name, fingerprint, DateTimeOffset.UtcNow, out cached))
if (LdapCapabilitySnapshotCache.TryGet(Name, fingerprint, timeProvider.GetUtcNow(), out cached))
{
UpdateCapabilities(cached, checkProvisioning, checkBootstrap, logDegrade: true);
return;
@@ -183,7 +186,7 @@ internal sealed class LdapIdentityProviderPlugin : IIdentityProviderPlugin
cancellationToken)
.ConfigureAwait(false);
LdapCapabilitySnapshotCache.Set(Name, fingerprint, DateTimeOffset.UtcNow, options.CapabilityProbe.CacheTtl, snapshot);
LdapCapabilitySnapshotCache.Set(Name, fingerprint, timeProvider.GetUtcNow(), options.CapabilityProbe.CacheTtl, snapshot);
UpdateCapabilities(snapshot, checkProvisioning, checkBootstrap, logDegrade: true);
}
finally

View File

@@ -3,6 +3,7 @@
// Credential store for validating OIDC tokens.
// -----------------------------------------------------------------------------
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.Extensions.Caching.Memory;
@@ -161,7 +162,7 @@ internal sealed class OidcCredentialStore : IUserCredentialStore
new[]
{
new AuthEventProperty { Name = "oidc_issuer", Value = ClassifiedString.Public(jwtToken.Issuer) },
new AuthEventProperty { Name = "token_valid_until", Value = ClassifiedString.Public(jwtToken.ValidTo.ToString("O")) }
new AuthEventProperty { Name = "token_valid_until", Value = ClassifiedString.Public(jwtToken.ValidTo.ToString("O", CultureInfo.InvariantCulture)) }
});
}
catch (SecurityTokenExpiredException ex)

View File

@@ -3,6 +3,7 @@
// Credential store for validating SAML assertions.
// -----------------------------------------------------------------------------
using System.Globalization;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
@@ -31,6 +32,7 @@ internal sealed class SamlCredentialStore : IUserCredentialStore
private readonly IMemoryCache sessionCache;
private readonly ILogger<SamlCredentialStore> logger;
private readonly IHttpClientFactory httpClientFactory;
private readonly TimeProvider timeProvider;
private readonly Saml2SecurityTokenHandler tokenHandler;
private X509Certificate2? idpSigningCertificate;
private string? certificateCacheKey;
@@ -42,13 +44,15 @@ internal sealed class SamlCredentialStore : IUserCredentialStore
IOptionsMonitor<SamlPluginOptions> optionsMonitor,
IMemoryCache sessionCache,
ILogger<SamlCredentialStore> logger,
IHttpClientFactory httpClientFactory)
IHttpClientFactory httpClientFactory,
TimeProvider? timeProvider = null)
{
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));
this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
this.timeProvider = timeProvider ?? TimeProvider.System;
tokenHandler = new Saml2SecurityTokenHandler();
@@ -162,7 +166,7 @@ internal sealed class SamlCredentialStore : IUserCredentialStore
["email"] = email,
["issuer"] = token.Assertion.Issuer?.Value,
["session_index"] = token.Assertion.Id?.Value,
["auth_instant"] = token.Assertion.IssueInstant.ToString("O")
["auth_instant"] = token.Assertion.IssueInstant.ToString("O", CultureInfo.InvariantCulture)
};
var user = new AuthorityUserDescriptor(
@@ -398,7 +402,7 @@ internal sealed class SamlCredentialStore : IUserCredentialStore
{
idpSigningCertificate = certificate;
certificateCacheKey = key;
lastMetadataRefresh = DateTimeOffset.UtcNow;
lastMetadataRefresh = timeProvider.GetUtcNow();
return;
}
@@ -427,7 +431,7 @@ internal sealed class SamlCredentialStore : IUserCredentialStore
return true;
}
return DateTimeOffset.UtcNow - lastMetadataRefresh.Value >= options.MetadataRefreshInterval;
return timeProvider.GetUtcNow() - lastMetadataRefresh.Value >= options.MetadataRefreshInterval;
}
private static string BuildCertificateCacheKey(SamlPluginOptions options)

View File

@@ -14,10 +14,6 @@
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit.runner.visualstudio" >
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Authority\StellaOps.Authority.csproj" />

View File

@@ -1,6 +1,7 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
@@ -98,8 +99,8 @@ internal sealed class AckTokenPayload
writer.WriteString("channel", Channel);
writer.WriteString("webhook", Webhook);
writer.WriteString("nonce", Nonce);
writer.WriteString("issuedAt", IssuedAt.UtcDateTime.ToString("O"));
writer.WriteString("expiresAt", ExpiresAt.UtcDateTime.ToString("O"));
writer.WriteString("issuedAt", IssuedAt.UtcDateTime.ToString("O", CultureInfo.InvariantCulture));
writer.WriteString("expiresAt", ExpiresAt.UtcDateTime.ToString("O", CultureInfo.InvariantCulture));
writer.WritePropertyName("actions");
writer.WriteStartArray();

View File

@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using System.Globalization;
using System.Text.Json;
using StellaOps.Authority.Persistence.Documents;
using StellaOps.Authority.Persistence.Sessions;
@@ -453,7 +454,7 @@ internal sealed class PostgresTokenStore : IAuthorityTokenStore, IAuthorityRefre
if (document.RevokedAt is not null)
{
properties["revoked_at"] = document.RevokedAt.Value.ToUniversalTime().ToString("O");
properties["revoked_at"] = document.RevokedAt.Value.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture);
}
if (!string.IsNullOrWhiteSpace(document.RevokedReason))

View File

@@ -0,0 +1,22 @@
# Authority ConfigDiff Tests Charter
## Mission
- Maintain deterministic tests for Authority configuration diffing.
## Responsibilities
- Validate config diff output stability and edge cases.
- Keep fixtures deterministic and offline-safe.
## Required Reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- docs/modules/authority/architecture.md
## Definition of Done
- Tests are deterministic and offline-safe.
- Coverage includes mismatch detection and ordering behavior.
## Working Agreement
- Use fixed time and ids in fixtures.
- Avoid non-deterministic ordering; assert sorted output.