using FinalFactory.Rendezvous.Contracts; namespace FinalFactory.Rendezvous.Server.Provisioning; internal sealed class PublisherAuthorizationService(GamePolicyRegistry policies) { public PublisherAuthorizationResult Authorize( AuthenticatedPrincipal principal, GameId requestedGameId, EnvironmentId requestedEnvironmentId, RegionId requestedRegionId, uint requestedProtocolVersion, ListingVisibility requestedVisibility, IReadOnlyDictionary requestedMetadata, DateTimeOffset now) { if (principal.ExpiresAt <= now) { return PublisherAuthorizationResult.Denied(PublisherAuthorizationError.PrincipalExpired); } if (principal is not IPublisherPrincipal publisher) { return PublisherAuthorizationResult.Denied(PublisherAuthorizationError.NotPublisher); } if (publisher.GameId != requestedGameId || publisher.EnvironmentId != requestedEnvironmentId) { return PublisherAuthorizationResult.Denied(PublisherAuthorizationError.ScopeMismatch); } if (!policies.TryGet(publisher.GameId, publisher.EnvironmentId, out GamePolicy? policy) || policy is null) { return PublisherAuthorizationResult.Denied(PublisherAuthorizationError.PolicyNotFound); } if (!policy.AllowsPublisherTrust(publisher.TrustMode)) { return PublisherAuthorizationResult.Denied(PublisherAuthorizationError.TrustModeNotAllowed); } if (!publisher.AllowedRegions.Contains(requestedRegionId) || !policy.AllowsRegion(requestedRegionId)) { return PublisherAuthorizationResult.Denied(PublisherAuthorizationError.RegionNotAllowed); } if (!policy.AllowsProtocol(requestedProtocolVersion)) { return PublisherAuthorizationResult.Denied(PublisherAuthorizationError.ProtocolNotAllowed); } if (publisher.TrustMode == PublisherTrustMode.AnonymousUnlisted && requestedVisibility != ListingVisibility.Unlisted) { return PublisherAuthorizationResult.Denied( PublisherAuthorizationError.AnonymousMustBeUnlisted); } if (!policy.AllowsVisibility(requestedVisibility)) { return PublisherAuthorizationResult.Denied(PublisherAuthorizationError.VisibilityNotAllowed); } if (!policy.AllowsMetadata(requestedMetadata)) { return PublisherAuthorizationResult.Denied(PublisherAuthorizationError.MetadataNotAllowed); } return PublisherAuthorizationResult.Allowed(new AuthorizedPublisherContext( publisher.Subject, publisher.GameId, publisher.EnvironmentId, requestedRegionId, requestedProtocolVersion, requestedVisibility, publisher.TrustMode, policy)); } } internal sealed record AuthorizedPublisherContext( string Subject, GameId GameId, EnvironmentId EnvironmentId, RegionId RegionId, uint ProtocolVersion, ListingVisibility Visibility, PublisherTrustMode TrustMode, GamePolicy Policy); internal readonly record struct PublisherAuthorizationResult( bool IsAllowed, PublisherAuthorizationError Error, AuthorizedPublisherContext? Context) { public static PublisherAuthorizationResult Allowed(AuthorizedPublisherContext context) => new(true, PublisherAuthorizationError.None, context); public static PublisherAuthorizationResult Denied(PublisherAuthorizationError error) => new(false, error, null); } internal enum PublisherAuthorizationError { None = 0, NotPublisher = 1, ScopeMismatch = 2, PolicyNotFound = 3, TrustModeNotAllowed = 4, RegionNotAllowed = 5, ProtocolNotAllowed = 6, AnonymousMustBeUnlisted = 7, VisibilityNotAllowed = 8, MetadataNotAllowed = 9, PrincipalExpired = 10, }