using FinalFactory.Rendezvous.Contracts; namespace FinalFactory.Rendezvous.Server.Provisioning; internal sealed class ProvisioningRuntime : IDisposable { private readonly IDisposable? _secretProviderLifetime; private ProvisioningRuntime( GamePolicyRegistry policies, SigningKeyRing signingKeys, PrincipalCredentialService credentials, PublisherAuthorizationService publisherAuthorization, IDisposable? secretProviderLifetime) { Policies = policies; SigningKeys = signingKeys; Credentials = credentials; PublisherAuthorization = publisherAuthorization; _secretProviderLifetime = secretProviderLifetime; } public GamePolicyRegistry Policies { get; } public SigningKeyRing SigningKeys { get; } public PrincipalCredentialService Credentials { get; } public PublisherAuthorizationService PublisherAuthorization { get; } public static ProvisioningRuntime Create( ProvisioningOptions options, ISecretProvider secretProvider, DateTimeOffset now) { try { SigningKeyRing signingKeys = SigningKeyRing.Create(options.SigningKeys, secretProvider); try { if (!signingKeys.HasKeys || !signingKeys.HasActiveSigningKey(now)) { throw new ProvisioningConfigurationException( "At least one active signing key with available production key material is required."); } GamePolicyRegistry policies = GamePolicyRegistry.Create(options.Games); if (!policies.HasEnabledPolicies) { throw new ProvisioningConfigurationException( "At least one enabled game/environment policy is required."); } foreach (GamePolicy policy in policies.EnabledPolicies) { RequirePublisherKey( signingKeys, policy, PublisherTrustMode.ManagedDedicated, PrincipalCredentialKind.DedicatedPublisher, now); RequirePublisherKey( signingKeys, policy, PublisherTrustMode.PlayerGrant, PrincipalCredentialKind.PlayerHostGrant, now); } PrincipalCredentialService credentials = new( options.Issuer, options.Audience, TimeSpan.FromSeconds(options.ClockSkewSeconds), signingKeys); PublisherAuthorizationService authorization = new(policies); return new ProvisioningRuntime( policies, signingKeys, credentials, authorization, secretProvider as IDisposable); } catch { signingKeys.Dispose(); throw; } } catch { (secretProvider as IDisposable)?.Dispose(); throw; } } public void Dispose() { SigningKeys.Dispose(); _secretProviderLifetime?.Dispose(); } private static void RequirePublisherKey( SigningKeyRing signingKeys, GamePolicy policy, PublisherTrustMode trustMode, PrincipalCredentialKind credentialKind, DateTimeOffset now) { if (policy.AllowsPublisherTrust(trustMode) && !signingKeys.HasActiveSigningKey( now, credentialKind, policy.GameId.ToString(), policy.EnvironmentId.ToString())) { throw new ProvisioningConfigurationException( $"Policy {policy.GameId}/{policy.EnvironmentId} has no active {credentialKind} key."); } } } internal sealed record ProvisioningReadiness(bool IsReady);