cafb7d2006
- WP-2: SecurityRepository + CentralUiRepository with audit log queries - WP-3: AuditService with transactional guarantee (same SaveChangesAsync) - WP-4: Optimistic concurrency tests (deployment records vs template last-write-wins) - WP-5: Seed data (SCADA-Admins → Admin role mapping) - WP-6: LdapAuthService (direct bind, TLS enforcement, group query) - WP-7: JwtTokenService (HMAC-SHA256, 15-min refresh, 30-min idle timeout) - WP-8: RoleMapper (LDAP groups → roles with site-scoped deployment) - WP-9: Authorization policies (Admin/Design/Deployment + site scope handler) - WP-10: Shared Data Protection keys via EF Core 141 tests pass, zero warnings.
53 lines
1.7 KiB
C#
53 lines
1.7 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
|
|
namespace ScadaLink.Security;
|
|
|
|
/// <summary>
|
|
/// Authorization requirement for site-scoped deployment operations.
|
|
/// </summary>
|
|
public class SiteScopeRequirement : IAuthorizationRequirement
|
|
{
|
|
public string TargetSiteId { get; }
|
|
|
|
public SiteScopeRequirement(string targetSiteId)
|
|
{
|
|
TargetSiteId = targetSiteId ?? throw new ArgumentNullException(nameof(targetSiteId));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks that a user with the Deployment role is permitted to operate on the target site.
|
|
/// Users with Deployment role and no SiteId claims are system-wide deployers.
|
|
/// Users with SiteId claims are only permitted on those specific sites.
|
|
/// </summary>
|
|
public class SiteScopeAuthorizationHandler : AuthorizationHandler<SiteScopeRequirement>
|
|
{
|
|
protected override Task HandleRequirementAsync(
|
|
AuthorizationHandlerContext context,
|
|
SiteScopeRequirement requirement)
|
|
{
|
|
// Must have Deployment role
|
|
var hasDeploymentRole = context.User.HasClaim(JwtTokenService.RoleClaimType, "Deployment");
|
|
if (!hasDeploymentRole)
|
|
{
|
|
return Task.CompletedTask; // Fail — no Deployment role
|
|
}
|
|
|
|
var siteIdClaims = context.User.FindAll(JwtTokenService.SiteIdClaimType).ToList();
|
|
|
|
if (siteIdClaims.Count == 0)
|
|
{
|
|
// No site scope restrictions — system-wide deployer
|
|
context.Succeed(requirement);
|
|
}
|
|
else if (siteIdClaims.Any(c => c.Value == requirement.TargetSiteId))
|
|
{
|
|
// User is permitted on this specific site
|
|
context.Succeed(requirement);
|
|
}
|
|
|
|
// Otherwise, silently fail (not authorized for this site)
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|