search and ai stabilization work, localization stablized.

This commit is contained in:
master
2026-02-24 23:29:36 +02:00
parent 4f947a8b61
commit b07d27772e
766 changed files with 55299 additions and 3221 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -60,7 +61,7 @@ public sealed class AdvisoryAiRemoteInferenceOptions
if (_allowedProfiles.Count == 0)
{
throw new InvalidOperationException("Authority configuration requires at least one advisory AI remote inference profile when remote inference is enabled.");
throw new InvalidOperationException(_t("config.authority.remote_inference_required"));
}
}
else

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -38,29 +39,29 @@ public sealed class AdvisoryAiTenantRemoteInferenceOptions
if (remoteOptions is null || !remoteOptions.Enabled)
{
throw new InvalidOperationException("Tenant remote inference consent cannot be granted when remote inference is disabled.");
throw new InvalidOperationException(_t("config.tenant.remote_inference_disabled"));
}
if (ConsentVersion is { Length: > MaxConsentVersionLength })
{
throw new InvalidOperationException($"Tenant remote inference consentVersion must be {MaxConsentVersionLength} characters or fewer.");
throw new InvalidOperationException(_t("config.tenant.remote_inference_consent_version_length", MaxConsentVersionLength));
}
if (ConsentedBy is { Length: > MaxConsentedByLength })
{
throw new InvalidOperationException($"Tenant remote inference consentedBy must be {MaxConsentedByLength} characters or fewer.");
throw new InvalidOperationException(_t("config.tenant.remote_inference_consented_by_length", MaxConsentedByLength));
}
if (remoteOptions.RequireTenantConsent)
{
if (string.IsNullOrWhiteSpace(ConsentVersion))
{
throw new InvalidOperationException("Tenant remote inference consent requires consentVersion when consentGranted is true.");
throw new InvalidOperationException(_t("config.tenant.remote_inference_consent_version_required"));
}
if (!ConsentedAt.HasValue)
{
throw new InvalidOperationException("Tenant remote inference consent requires consentedAt when consentGranted is true.");
throw new InvalidOperationException(_t("config.tenant.remote_inference_consented_at_required"));
}
}
}

View File

@@ -1,5 +1,6 @@
using StellaOps.Cryptography;
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -14,27 +15,27 @@ public sealed partial class AuthorityAckTokenOptions
if (string.IsNullOrWhiteSpace(PayloadType))
{
throw new InvalidOperationException("notifications.ackTokens.payloadType must be specified when ack tokens are enabled.");
throw new InvalidOperationException(_t("config.ack_token.payload_type_required"));
}
if (DefaultLifetime <= TimeSpan.Zero)
{
throw new InvalidOperationException("notifications.ackTokens.defaultLifetime must be greater than zero.");
throw new InvalidOperationException(_t("config.ack_token.default_lifetime_invalid"));
}
if (MaxLifetime <= TimeSpan.Zero || MaxLifetime < DefaultLifetime)
{
throw new InvalidOperationException("notifications.ackTokens.maxLifetime must be greater than zero and greater than or equal to defaultLifetime.");
throw new InvalidOperationException(_t("config.ack_token.max_lifetime_invalid"));
}
if (string.IsNullOrWhiteSpace(ActiveKeyId))
{
throw new InvalidOperationException("notifications.ackTokens.activeKeyId must be provided when ack tokens are enabled.");
throw new InvalidOperationException(_t("config.ack_token.key_id_required"));
}
if (string.IsNullOrWhiteSpace(KeyPath))
{
throw new InvalidOperationException("notifications.ackTokens.keyPath must be provided when ack tokens are enabled.");
throw new InvalidOperationException(_t("config.ack_token.key_path_required"));
}
if (string.IsNullOrWhiteSpace(KeySource))
@@ -59,7 +60,7 @@ public sealed partial class AuthorityAckTokenOptions
if (JwksCacheLifetime <= TimeSpan.Zero || JwksCacheLifetime > TimeSpan.FromHours(1))
{
throw new InvalidOperationException("notifications.ackTokens.jwksCacheLifetime must be between 00:00:01 and 01:00:00.");
throw new InvalidOperationException(_t("config.ack_token.jwks_cache_range"));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -58,7 +59,7 @@ public sealed class AuthorityLegacyAuthEndpointOptions
if (normalizedSunset <= normalizedDeprecation)
{
throw new InvalidOperationException("Legacy auth sunset date must be after the deprecation date.");
throw new InvalidOperationException(_t("config.api_lifecycle.sunset_after_deprecation"));
}
DeprecationDate = normalizedDeprecation;
@@ -69,7 +70,7 @@ public sealed class AuthorityLegacyAuthEndpointOptions
if (!Uri.TryCreate(DocumentationUrl, UriKind.Absolute, out var uri) ||
(uri.Scheme != Uri.UriSchemeHttps && uri.Scheme != Uri.UriSchemeHttp))
{
throw new InvalidOperationException("Legacy auth documentation URL must be an absolute HTTP or HTTPS URL.");
throw new InvalidOperationException(_t("config.api_lifecycle.docs_url_format"));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -28,12 +29,12 @@ public sealed class AuthorityBootstrapOptions
if (string.IsNullOrWhiteSpace(ApiKey))
{
throw new InvalidOperationException("Authority bootstrap configuration requires an API key when enabled.");
throw new InvalidOperationException(_t("config.bootstrap.api_key_required"));
}
if (string.IsNullOrWhiteSpace(DefaultIdentityProvider))
{
throw new InvalidOperationException("Authority bootstrap configuration requires a default identity provider name when enabled.");
throw new InvalidOperationException(_t("config.bootstrap.idp_required"));
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -44,7 +45,7 @@ public sealed class AuthorityDelegationOptions
if (!seenAccounts.Add(account.AccountId))
{
throw new InvalidOperationException($"Delegation configuration contains duplicate service account id '{account.AccountId}'.");
throw new InvalidOperationException(_t("config.delegation.duplicate_account", account.AccountId));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -10,7 +11,7 @@ public sealed class AuthorityDelegationQuotaOptions
{
if (MaxActiveTokens <= 0)
{
throw new InvalidOperationException($"Authority delegation configuration requires {propertyName}.{nameof(MaxActiveTokens)} to be greater than zero.");
throw new InvalidOperationException(_t("config.delegation.quota_max_tokens", $"{propertyName}.{nameof(MaxActiveTokens)}"));
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -30,29 +31,29 @@ public sealed class AuthorityDpopNonceOptions
{
if (Ttl <= TimeSpan.Zero)
{
throw new InvalidOperationException("Dpop.Nonce.Ttl must be greater than zero.");
throw new InvalidOperationException(_t("auth.dpop.nonce_ttl_invalid"));
}
if (MaxIssuancePerMinute < 1)
{
throw new InvalidOperationException("Dpop.Nonce.MaxIssuancePerMinute must be at least 1.");
throw new InvalidOperationException(_t("auth.dpop.nonce_max_issuance_invalid"));
}
if (string.IsNullOrWhiteSpace(Store))
{
throw new InvalidOperationException("Dpop.Nonce.Store must be specified.");
throw new InvalidOperationException(_t("auth.dpop.nonce_store_required"));
}
Store = Store.Trim().ToLowerInvariant();
if (Store is not ("memory" or "redis"))
{
throw new InvalidOperationException("Dpop.Nonce.Store must be either 'memory' or 'redis'.");
throw new InvalidOperationException(_t("auth.dpop.nonce_store_invalid"));
}
if (Store == "redis" && string.IsNullOrWhiteSpace(RedisConnectionString))
{
throw new InvalidOperationException("Dpop.Nonce.RedisConnectionString must be provided when using the 'redis' store.");
throw new InvalidOperationException(_t("auth.dpop.nonce_redis_required"));
}
var normalizedAudiences = _requiredAudiences
@@ -62,7 +63,7 @@ public sealed class AuthorityDpopNonceOptions
if (normalizedAudiences.Count == 0)
{
throw new InvalidOperationException("Dpop.Nonce.RequiredAudiences must include at least one audience.");
throw new InvalidOperationException(_t("auth.dpop.nonce_audiences_required"));
}
_requiredAudiences.Clear();

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -35,22 +36,22 @@ public sealed class AuthorityDpopOptions
{
if (ProofLifetime <= TimeSpan.Zero)
{
throw new InvalidOperationException("Dpop.ProofLifetime must be greater than zero.");
throw new InvalidOperationException(_t("auth.dpop.proof_lifetime_invalid"));
}
if (AllowedClockSkew < TimeSpan.Zero || AllowedClockSkew > TimeSpan.FromMinutes(5))
{
throw new InvalidOperationException("Dpop.AllowedClockSkew must be between 0 and 5 minutes.");
throw new InvalidOperationException(_t("auth.dpop.clock_skew_invalid"));
}
if (ReplayWindow < TimeSpan.Zero)
{
throw new InvalidOperationException("Dpop.ReplayWindow must be greater than or equal to zero.");
throw new InvalidOperationException(_t("auth.dpop.replay_window_invalid"));
}
if (_allowedAlgorithms.Count == 0)
{
throw new InvalidOperationException("At least one DPoP algorithm must be configured.");
throw new InvalidOperationException(_t("auth.dpop.algorithm_required"));
}
NormalizedAlgorithms = _allowedAlgorithms
@@ -60,7 +61,7 @@ public sealed class AuthorityDpopOptions
if (NormalizedAlgorithms.Count == 0)
{
throw new InvalidOperationException("Allowed DPoP algorithms cannot be empty after normalization.");
throw new InvalidOperationException(_t("auth.dpop.algorithm_empty_after_normalization"));
}
Nonce.Validate();

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading.RateLimiting;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -39,17 +40,17 @@ public sealed class AuthorityEndpointRateLimitOptions
if (PermitLimit <= 0)
{
throw new InvalidOperationException($"Authority rate limiting '{name}' requires permitLimit to be greater than zero.");
throw new InvalidOperationException(_t("config.rate_limit.permit_limit", name));
}
if (QueueLimit < 0)
{
throw new InvalidOperationException($"Authority rate limiting '{name}' queueLimit cannot be negative.");
throw new InvalidOperationException(_t("config.rate_limit.queue_limit", name));
}
if (Window <= TimeSpan.Zero || Window > TimeSpan.FromHours(1))
{
throw new InvalidOperationException($"Authority rate limiting '{name}' window must be greater than zero and no more than one hour.");
throw new InvalidOperationException(_t("config.rate_limit.window", name));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -21,7 +22,7 @@ public sealed class AuthorityEscalationOptions
{
if (string.IsNullOrWhiteSpace(Scope))
{
throw new InvalidOperationException("notifications.escalation.scope must be specified.");
throw new InvalidOperationException(_t("config.escalation.scope_required"));
}
Scope = Scope.Trim().ToLowerInvariant();

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -35,12 +36,12 @@ public sealed class AuthorityExceptionRoutingTemplateOptions
{
if (string.IsNullOrEmpty(Id))
{
throw new InvalidOperationException("Authority exception routing templates require an id.");
throw new InvalidOperationException(_t("config.exceptions.template_id_required"));
}
if (string.IsNullOrEmpty(AuthorityRouteId))
{
throw new InvalidOperationException($"Authority exception routing template '{Id}' requires authorityRouteId.");
throw new InvalidOperationException(_t("config.exceptions.template_route_required", Id));
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -39,7 +40,7 @@ public sealed class AuthorityExceptionsOptions
{
if (templateOptions is null)
{
throw new InvalidOperationException("Authority exception routing template entries must not be null.");
throw new InvalidOperationException(_t("config.exceptions.null_template"));
}
templateOptions.Normalize();
@@ -48,7 +49,7 @@ public sealed class AuthorityExceptionsOptions
var template = templateOptions.ToImmutable();
if (!normalized.TryAdd(template.Id, template))
{
throw new InvalidOperationException($"Authority exception routing template '{template.Id}' is configured more than once.");
throw new InvalidOperationException(_t("config.exceptions.duplicate_template", template.Id));
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -42,7 +43,7 @@ public sealed class AuthorityMtlsOptions
{
if (RotationGrace < TimeSpan.Zero)
{
throw new InvalidOperationException("Mtls.RotationGrace must be non-negative.");
throw new InvalidOperationException(_t("config.mtls.rotation_grace_negative"));
}
NormalizedAudiences = _enforceForAudiences
@@ -52,12 +53,12 @@ public sealed class AuthorityMtlsOptions
if (Enabled && NormalizedAudiences.Count == 0)
{
throw new InvalidOperationException("Mtls.EnforceForAudiences must include at least one audience when enabled.");
throw new InvalidOperationException(_t("config.mtls.audiences_required"));
}
if (AllowedCertificateAuthorities.Any(static path => string.IsNullOrWhiteSpace(path)))
{
throw new InvalidOperationException("Mtls.AllowedCertificateAuthorities entries must not be empty.");
throw new InvalidOperationException(_t("config.mtls.ca_empty"));
}
NormalizedSanTypes = _allowedSanTypes
@@ -68,7 +69,7 @@ public sealed class AuthorityMtlsOptions
if (Enabled && NormalizedSanTypes.Count == 0)
{
throw new InvalidOperationException("Mtls.AllowedSanTypes must include at least one entry when enabled.");
throw new InvalidOperationException(_t("config.mtls.san_types_required"));
}
var compiledPatterns = new List<Regex>(AllowedSubjectPatterns.Count);
@@ -77,7 +78,7 @@ public sealed class AuthorityMtlsOptions
{
if (string.IsNullOrWhiteSpace(pattern))
{
throw new InvalidOperationException("Mtls.AllowedSubjectPatterns entries must not be empty.");
throw new InvalidOperationException(_t("config.mtls.subject_patterns_empty"));
}
try
@@ -86,7 +87,7 @@ public sealed class AuthorityMtlsOptions
}
catch (RegexParseException ex)
{
throw new InvalidOperationException($"Mtls.AllowedSubjectPatterns entry '{pattern}' is not a valid regular expression.", ex);
throw new InvalidOperationException(_t("config.mtls.subject_pattern_invalid", pattern), ex);
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -30,7 +31,7 @@ public sealed class AuthorityPluginSettings
{
if (descriptor is null)
{
throw new InvalidOperationException($"Authority plugin descriptor '{name}' is null.");
throw new InvalidOperationException(_t("config.plugin.descriptor_null", name));
}
descriptor.Normalize(name);

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -41,17 +42,17 @@ public sealed class AuthoritySealedModeOptions
if (string.IsNullOrWhiteSpace(EvidencePath))
{
throw new InvalidOperationException("AirGap.SealedMode.EvidencePath must be provided when enforcement is enabled.");
throw new InvalidOperationException(_t("config.sealed_mode.evidence_path_required"));
}
if (MaxEvidenceAge <= TimeSpan.Zero || MaxEvidenceAge > TimeSpan.FromDays(7))
{
throw new InvalidOperationException("AirGap.SealedMode.MaxEvidenceAge must be between 00:00:01 and 7.00:00:00.");
throw new InvalidOperationException(_t("config.sealed_mode.max_age_range"));
}
if (CacheLifetime <= TimeSpan.Zero || CacheLifetime > MaxEvidenceAge)
{
throw new InvalidOperationException("AirGap.SealedMode.CacheLifetime must be greater than zero and less than or equal to AirGap.SealedMode.MaxEvidenceAge.");
throw new InvalidOperationException(_t("config.sealed_mode.cache_lifetime_range"));
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -18,27 +19,27 @@ public sealed partial class AuthorityServiceAccountSeedOptions
{
if (string.IsNullOrWhiteSpace(AccountId))
{
throw new InvalidOperationException("Delegation service account seeds require an accountId.");
throw new InvalidOperationException(_t("config.service_account.id_required"));
}
if (!_accountIdRegex.IsMatch(AccountId))
{
throw new InvalidOperationException($"Service account id '{AccountId}' must contain lowercase letters, digits, colon, underscore, or hyphen.");
throw new InvalidOperationException(_t("config.service_account.id_format", AccountId));
}
if (string.IsNullOrWhiteSpace(Tenant))
{
throw new InvalidOperationException($"Service account '{AccountId}' requires a tenant assignment.");
throw new InvalidOperationException(_t("config.service_account.tenant_required", AccountId));
}
if (tenantIds.Count > 0 && !tenantIds.Contains(Tenant))
{
throw new InvalidOperationException($"Service account '{AccountId}' references unknown tenant '{Tenant}'.");
throw new InvalidOperationException(_t("config.service_account.tenant_unknown", AccountId, Tenant));
}
if (AllowedScopes.Count == 0)
{
throw new InvalidOperationException($"Service account '{AccountId}' must specify at least one allowed scope.");
throw new InvalidOperationException(_t("config.service_account.scope_required", AccountId));
}
if (Attributes.Count > 0)
@@ -47,7 +48,7 @@ public sealed partial class AuthorityServiceAccountSeedOptions
{
if (!_allowedAttributeKeys.Contains(attributeName))
{
throw new InvalidOperationException($"Service account '{AccountId}' defines unsupported attribute '{attributeName}'. Allowed attributes: env, owner, business_tier.");
throw new InvalidOperationException(_t("config.service_account.unsupported_attribute", AccountId, attributeName));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -14,12 +15,12 @@ public sealed class AuthoritySigningAdditionalKeyOptions
{
if (string.IsNullOrWhiteSpace(KeyId))
{
throw new InvalidOperationException("Additional signing keys require a keyId.");
throw new InvalidOperationException(_t("config.signing.additional_key_id_required"));
}
if (string.IsNullOrWhiteSpace(Path))
{
throw new InvalidOperationException($"Signing key '{KeyId}' requires a path.");
throw new InvalidOperationException(_t("config.signing.additional_key_path_required", KeyId));
}
if (string.IsNullOrWhiteSpace(Source))

View File

@@ -3,6 +3,7 @@
using StellaOps.Cryptography;
using System;
using System.Collections.Generic;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -62,12 +63,12 @@ public sealed class AuthoritySigningOptions
if (string.IsNullOrWhiteSpace(ActiveKeyId))
{
throw new InvalidOperationException("Authority signing configuration requires signing.activeKeyId.");
throw new InvalidOperationException(_t("config.signing.key_id_required"));
}
if (string.IsNullOrWhiteSpace(KeyPath))
{
throw new InvalidOperationException("Authority signing configuration requires signing.keyPath.");
throw new InvalidOperationException(_t("config.signing.key_path_required"));
}
if (string.IsNullOrWhiteSpace(Algorithm))
@@ -87,7 +88,7 @@ public sealed class AuthoritySigningOptions
if (JwksCacheLifetime <= TimeSpan.Zero || JwksCacheLifetime > TimeSpan.FromHours(1))
{
throw new InvalidOperationException("Authority signing configuration requires signing.jwksCacheLifetime to be between 00:00:01 and 01:00:00.");
throw new InvalidOperationException(_t("config.signing.jwks_cache_range"));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -23,12 +24,12 @@ public sealed class AuthorityStorageOptions
{
if (string.IsNullOrWhiteSpace(ConnectionString))
{
throw new InvalidOperationException("Authority storage requires a connection string.");
throw new InvalidOperationException(_t("config.storage.connection_required"));
}
if (CommandTimeout <= TimeSpan.Zero)
{
throw new InvalidOperationException("Authority storage command timeout must be greater than zero.");
throw new InvalidOperationException(_t("config.storage.timeout_invalid"));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -17,7 +18,7 @@ public sealed class AuthorityTenantDelegationOptions
if (MaxActiveTokens is { } value && value <= 0)
{
throw new InvalidOperationException($"Tenant '{tenantId}' delegation maxActiveTokens must be greater than zero when specified.");
throw new InvalidOperationException(_t("config.tenant.delegation_max_tokens", tenantId));
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Text.RegularExpressions;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -12,12 +13,12 @@ public sealed partial class AuthorityTenantOptions
{
if (string.IsNullOrWhiteSpace(Id))
{
throw new InvalidOperationException("Each tenant requires an id (slug).");
throw new InvalidOperationException(_t("config.tenant.id_required"));
}
if (!_tenantSlugRegex.IsMatch(Id))
{
throw new InvalidOperationException($"Tenant id '{Id}' must contain only lowercase letters, digits, and hyphen.");
throw new InvalidOperationException(_t("config.tenant.id_format", Id));
}
if (string.IsNullOrWhiteSpace(DisplayName))
@@ -31,7 +32,7 @@ public sealed partial class AuthorityTenantOptions
{
if (!_projectSlugRegex.IsMatch(project))
{
throw new InvalidOperationException($"Tenant '{Id}' defines project '{project}' which must contain only lowercase letters, digits, and hyphen.");
throw new InvalidOperationException(_t("config.tenant.project_format", Id, project));
}
}
}
@@ -45,7 +46,7 @@ public sealed partial class AuthorityTenantOptions
{
if (roleOptions is null)
{
throw new InvalidOperationException($"Tenant '{Id}' defines role '{roleName}' without configuration.");
throw new InvalidOperationException(_t("config.tenant.role_missing_config", Id, roleName));
}
roleOptions.Validate(Id, roleName);

View File

@@ -1,6 +1,7 @@
using StellaOps.Auth.Abstractions;
using System;
using System.Collections.Generic;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -17,14 +18,14 @@ public sealed partial class AuthorityTenantRoleOptions
{
if (Scopes.Count == 0)
{
throw new InvalidOperationException($"Tenant '{tenantId}' role '{roleName}' must specify at least one scope.");
throw new InvalidOperationException(_t("config.tenant.role_scope_required", tenantId, roleName));
}
foreach (var scope in Scopes)
{
if (!StellaOpsScopes.IsKnown(scope))
{
throw new InvalidOperationException($"Tenant '{tenantId}' role '{roleName}' references unknown scope '{scope}'.");
throw new InvalidOperationException(_t("config.tenant.role_unknown_scope", tenantId, roleName, scope));
}
}
@@ -34,7 +35,7 @@ public sealed partial class AuthorityTenantRoleOptions
{
if (!_allowedAttributeKeys.Contains(attributeName))
{
throw new InvalidOperationException($"Tenant '{tenantId}' role '{roleName}' defines unsupported attribute '{attributeName}'. Allowed attributes: env, owner, business_tier.");
throw new InvalidOperationException(_t("config.tenant.role_unsupported_attribute", tenantId, roleName, attributeName));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -46,27 +47,27 @@ public sealed class AuthorityVulnAntiForgeryOptions
if (string.IsNullOrWhiteSpace(Audience))
{
throw new InvalidOperationException("vulnerabilityExplorer.workflow.antiForgery.audience must be specified when anti-forgery tokens are enabled.");
throw new InvalidOperationException(_t("config.anti_forgery.audience_required"));
}
if (DefaultLifetime <= TimeSpan.Zero)
{
throw new InvalidOperationException("vulnerabilityExplorer.workflow.antiForgery.defaultLifetime must be greater than zero.");
throw new InvalidOperationException(_t("config.anti_forgery.default_lifetime_invalid"));
}
if (MaxLifetime <= TimeSpan.Zero || MaxLifetime < DefaultLifetime)
{
throw new InvalidOperationException("vulnerabilityExplorer.workflow.antiForgery.maxLifetime must be greater than zero and greater than or equal to defaultLifetime.");
throw new InvalidOperationException(_t("config.anti_forgery.max_lifetime_invalid"));
}
if (MaxContextEntries < 0)
{
throw new InvalidOperationException("vulnerabilityExplorer.workflow.antiForgery.maxContextEntries must be non-negative.");
throw new InvalidOperationException(_t("config.anti_forgery.max_context_entries"));
}
if (MaxContextValueLength <= 0)
{
throw new InvalidOperationException("vulnerabilityExplorer.workflow.antiForgery.maxContextValueLength must be greater than zero.");
throw new InvalidOperationException(_t("config.anti_forgery.max_context_value_length"));
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -46,27 +47,27 @@ public sealed class AuthorityVulnAttachmentOptions
if (DefaultLifetime <= TimeSpan.Zero)
{
throw new InvalidOperationException("vulnerabilityExplorer.attachments.defaultLifetime must be greater than zero when attachment tokens are enabled.");
throw new InvalidOperationException(_t("config.attachment.default_lifetime_invalid"));
}
if (MaxLifetime <= TimeSpan.Zero || MaxLifetime < DefaultLifetime)
{
throw new InvalidOperationException("vulnerabilityExplorer.attachments.maxLifetime must be greater than zero and greater than or equal to defaultLifetime.");
throw new InvalidOperationException(_t("config.attachment.max_lifetime_invalid"));
}
if (string.IsNullOrWhiteSpace(PayloadType))
{
throw new InvalidOperationException("vulnerabilityExplorer.attachments.payloadType must be specified when attachment tokens are enabled.");
throw new InvalidOperationException(_t("config.attachment.payload_type_required"));
}
if (MaxMetadataEntries < 0)
{
throw new InvalidOperationException("vulnerabilityExplorer.attachments.maxMetadataEntries must be non-negative.");
throw new InvalidOperationException(_t("config.attachment.max_metadata_entries"));
}
if (MaxMetadataValueLength <= 0)
{
throw new InvalidOperationException("vulnerabilityExplorer.attachments.maxMetadataValueLength must be greater than zero.");
throw new InvalidOperationException(_t("config.attachment.max_metadata_value_length"));
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -35,7 +36,7 @@ public sealed class AuthorityWebhookAllowlistOptions
if (_allowedHosts.Count == 0)
{
throw new InvalidOperationException("notifications.webhooks.allowedHosts must include at least one host when enabled.");
throw new InvalidOperationException(_t("config.webhook.hosts_required"));
}
NormalizeList(_allowedHosts);

View File

@@ -23,6 +23,7 @@
<ProjectReference Include="..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.Pkcs11Gost\StellaOps.Cryptography.Plugin.Pkcs11Gost.csproj" />
<ProjectReference Include="..\StellaOps.Cryptography.DependencyInjection\StellaOps.Cryptography.DependencyInjection.csproj" />
<ProjectReference Include="..\StellaOps.Localization\StellaOps.Localization.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(StellaOpsEnableCryptoPro)' == 'true'">

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -9,12 +10,12 @@ public sealed partial class StellaOpsAuthorityOptions
{
if (value <= TimeSpan.Zero)
{
throw new InvalidOperationException($"Authority configuration requires {propertyName} to be greater than zero.");
throw new InvalidOperationException(_t("config.authority.property_greater_than_zero", propertyName));
}
if (value > maximum)
{
throw new InvalidOperationException($"Authority configuration requires {propertyName} to be less than or equal to {maximum}.");
throw new InvalidOperationException(_t("config.authority.property_max", propertyName, maximum));
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using static StellaOps.Localization.T;
namespace StellaOps.Configuration;
@@ -13,22 +14,22 @@ public sealed partial class StellaOpsAuthorityOptions
{
if (SchemaVersion <= 0)
{
throw new InvalidOperationException("Authority configuration requires a positive schemaVersion.");
throw new InvalidOperationException(_t("config.authority.schema_version_required"));
}
if (Issuer is null)
{
throw new InvalidOperationException("Authority configuration requires an issuer URL.");
throw new InvalidOperationException(_t("config.authority.issuer_required"));
}
if (!Issuer.IsAbsoluteUri)
{
throw new InvalidOperationException("Authority issuer must be an absolute URI.");
throw new InvalidOperationException(_t("config.authority.issuer_absolute"));
}
if (string.Equals(Issuer.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && !Issuer.IsLoopback)
{
throw new InvalidOperationException("Authority issuer must use HTTPS unless running on a loopback interface.");
throw new InvalidOperationException(_t("config.authority.issuer_https"));
}
ValidateLifetime(AccessTokenLifetime, nameof(AccessTokenLifetime), TimeSpan.FromHours(24));
@@ -63,7 +64,7 @@ public sealed partial class StellaOpsAuthorityOptions
tenant.Validate(AdvisoryAi, Delegation);
if (!identifiers.Add(tenant.Id))
{
throw new InvalidOperationException($"Authority configuration contains duplicate tenant identifier '{tenant.Id}'.");
throw new InvalidOperationException(_t("config.authority.duplicate_tenant", tenant.Id));
}
}
}