120 lines
4.0 KiB
C#
120 lines
4.0 KiB
C#
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<string, string> 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,
|
|
}
|