Files
UnrealEngine/Engine/Source/Programs/UnrealCloudDDC/Jupiter/Controllers/RequestHelper.cs
2025-05-18 13:04:45 +08:00

194 lines
6.3 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using EpicGames.Horde.Storage;
using Jupiter.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using OpenTelemetry.Trace;
namespace Jupiter.Controllers;
public class RequestHelper : IRequestHelper
{
private readonly IAuthorizationService _authorizationService;
private readonly INamespacePolicyResolver _namespacePolicyResolver;
private readonly IOptionsMonitor<JupiterSettings> _settings;
private readonly IOptionsMonitor<AuthSettings> _authSettings;
private readonly Tracer _tracer;
public RequestHelper(IAuthorizationService authorizationService, INamespacePolicyResolver namespacePolicyResolver, IOptionsMonitor<JupiterSettings> settings, IOptionsMonitor<AuthSettings> authSettings, Tracer tracer)
{
_authorizationService = authorizationService;
_namespacePolicyResolver = namespacePolicyResolver;
_settings = settings;
_authSettings = authSettings;
_tracer = tracer;
}
public async Task<ActionResult?> HasAccessToScopeAsync(ClaimsPrincipal user, HttpRequest request, AccessScope scope, JupiterAclAction[] aclActions)
{
using TelemetrySpan _ = _tracer.StartActiveSpan("authorize").SetAttribute("operation.name", "authorize");
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(user, new ScopeAccessRequest
{
AccessScope = scope,
Actions = aclActions
}, ScopeAccessRequirement.Name);
if (!authorizationResult.Succeeded)
{
return new ForbidResult();
}
NamespaceId? ns = scope.Namespace;
// fetch the value of the issuer claim
string? issuer = user.FindFirstValue("iss");
AuthSchemeEntry? authScheme = _authSettings.CurrentValue.Schemes.Values.FirstOrDefault(entry => entry.JwtAuthority == issuer);
if (authScheme != null)
{
if (authScheme.AllowedNamespaces.Length != 0)
{
// check if the auth scheme is allowed to grant access to this namespace
if (!authScheme.AllowedNamespaces.Contains(ns.ToString(), StringComparer.InvariantCultureIgnoreCase))
{
// not allowed to grant access to the namespace
return new ForbidResult();
}
}
}
if (!ns.HasValue)
{
// no namespace set, e.g. it's a global request and these do not allow for per namespace policies
return null;
}
bool isPublicNamespace = _namespacePolicyResolver.GetPoliciesForNs(ns.Value).IsPublicNamespace;
// public namespaces are always accessible
if (isPublicNamespace)
{
return null;
}
// namespace is a restricted namespace, check which port it is being accessed on
bool isPublicPort = IsPublicPort(request.HttpContext);
if (isPublicPort)
{
// trying to access restricted namespace on a public port, this is not allowed
return new ForbidResult();
}
// restricted namespace in corp or internal port, this is okay
return null;
}
public async Task<ActionResult?> HasAccessToNamespaceAsync(ClaimsPrincipal user, HttpRequest request, NamespaceId ns, JupiterAclAction[] aclActions)
{
using TelemetrySpan _ = _tracer.StartActiveSpan("authorize").SetAttribute("operation.name", "authorize");
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(user, new NamespaceAccessRequest
{
Namespace = ns,
Actions = aclActions
}, NamespaceAccessRequirement.Name);
if (!authorizationResult.Succeeded)
{
return new ForbidResult();
}
// fetch the value of the issuer claim
string? issuer = user.FindFirstValue("iss");
AuthSchemeEntry? authScheme = _authSettings.CurrentValue.Schemes.Values.FirstOrDefault(entry => entry.JwtAuthority == issuer);
if (authScheme != null)
{
if (authScheme.AllowedNamespaces.Length != 0)
{
// check if the auth scheme is allowed to grant access to this namespace
if (!authScheme.AllowedNamespaces.Contains(ns.ToString(), StringComparer.InvariantCultureIgnoreCase))
{
// not allowed to grant access to the namespace
return new ForbidResult();
}
}
}
bool isPublicNamespace = _namespacePolicyResolver.GetPoliciesForNs(ns).IsPublicNamespace;
// public namespaces are always accessible
if (isPublicNamespace)
{
return null;
}
// namespace is a restricted namespace, check which port it is being accessed on
bool isPublicPort = IsPublicPort(request.HttpContext);
if (isPublicPort)
{
// trying to access restricted namespace on a public port, this is not allowed
return new ForbidResult();
}
// restricted namespace in corp or internal port, this is okay
return null;
}
public async Task<ActionResult?> HasAccessForGlobalOperationsAsync(ClaimsPrincipal user, JupiterAclAction[] aclActions)
{
using TelemetrySpan _ = _tracer.StartActiveSpan("authorize").SetAttribute("operation.name", "authorize");
AuthorizationResult authorizationResult = await _authorizationService.AuthorizeAsync(user, new GlobalAccessRequest
{
Actions = aclActions
}, GlobalAccessRequirement.Name);
if (!authorizationResult.Succeeded)
{
return new ForbidResult();
}
return null;
}
public bool IsPublicPort(HttpContext context)
{
string? portHeaderValue = null;
if (context.Request.Headers.TryGetValue("X-Jupiter-Port", out StringValues values))
{
portHeaderValue = values.ToString();
}
// unit tests do not run on ports, we consider them always on the internal port
bool isLocalConnection = context.Connection.LocalPort == 0 && context.Connection.LocalIpAddress == null;
// public port is either running on the public port, or if using domain sockets we check the header that is passed along instead
bool isPublicPort = _settings!.CurrentValue.PublicApiPorts.Contains(context.Connection.LocalPort);
if (isLocalConnection && _settings.CurrentValue.AssumeLocalConnectionsHasFullAccess)
{
// local connection so granting it full access
isPublicPort = false;
}
if (isLocalConnection && portHeaderValue != null)
{
if (string.Equals(portHeaderValue, "Public", StringComparison.OrdinalIgnoreCase))
{
isPublicPort = true;
}
else if (string.Equals(portHeaderValue, "Corp", StringComparison.OrdinalIgnoreCase))
{
isPublicPort = false;
}
}
return isPublicPort;
}
}