using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.MxGateway.Server.Configuration;
namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
///
/// Authorizes a dashboard request by checking either: (a) the LDAP-issued
/// role claim satisfies ,
/// (b) authentication is fully disabled, or (c) the request is from loopback
/// and MxGateway:Dashboard:AllowAnonymousLocalhost is on.
///
///
/// The environment bypasses in (b) and (c) grant read-only access only: they
/// satisfy a requirement that includes (i.e.
/// ) but never the
/// requirement. Destructive/admin
/// surfaces still require a real Admin role claim, preserving the documented
/// "anonymous localhost is read-only" contract at the policy layer rather than by accident
/// in downstream service re-checks. The loopback test trusts
/// Connection.RemoteIpAddress; if forwarded-headers middleware is ever added upstream,
/// must be revisited so a spoofed X-Forwarded-For
/// cannot masquerade as loopback.
///
public sealed class DashboardAuthorizationHandler(
IHttpContextAccessor httpContextAccessor,
IOptions options) : AuthorizationHandler
{
///
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
DashboardAuthorizationRequirement requirement)
{
GatewayOptions gatewayOptions = options.Value;
// Environment bypasses are read-only: they satisfy the Viewer requirement
// (AnyDashboardRole) but never AdminOnly, which lacks Viewer in RequiredRoles.
bool grantsReadOnly = requirement.RequiredRoles.Contains(DashboardRoles.Viewer);
if (grantsReadOnly && gatewayOptions.Authentication.Mode == AuthenticationMode.Disabled)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
if (grantsReadOnly && gatewayOptions.Dashboard.AllowAnonymousLocalhost && IsLoopbackRequest())
{
context.Succeed(requirement);
return Task.CompletedTask;
}
if (context.User.Identity?.IsAuthenticated != true)
{
return Task.CompletedTask;
}
foreach (string role in requirement.RequiredRoles)
{
if (context.User.IsInRole(role))
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
private bool IsLoopbackRequest()
{
IPAddress? remoteAddress = httpContextAccessor.HttpContext?.Connection.RemoteIpAddress;
return remoteAddress is not null && IPAddress.IsLoopback(remoteAddress);
}
}