Phase 1 WP-2–10: Repositories, audit service, security & auth (LDAP, JWT, roles, policies, data protection)

- 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.
This commit is contained in:
Joseph Doherty
2026-03-16 19:32:43 -04:00
parent 1996b21961
commit cafb7d2006
31 changed files with 3356 additions and 8 deletions
+58
View File
@@ -0,0 +1,58 @@
using ScadaLink.Commons.Interfaces.Repositories;
namespace ScadaLink.Security;
public class RoleMapper
{
private readonly ISecurityRepository _securityRepository;
public RoleMapper(ISecurityRepository securityRepository)
{
_securityRepository = securityRepository ?? throw new ArgumentNullException(nameof(securityRepository));
}
public async Task<RoleMappingResult> MapGroupsToRolesAsync(
IReadOnlyList<string> ldapGroups,
CancellationToken ct = default)
{
var allMappings = await _securityRepository.GetAllMappingsAsync(ct);
var matchedRoles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var permittedSiteIds = new HashSet<string>();
var hasDeploymentRole = false;
var hasDeploymentWithScopeRules = false;
foreach (var mapping in allMappings)
{
// Match LDAP group names (case-insensitive)
if (!ldapGroups.Any(g => g.Equals(mapping.LdapGroupName, StringComparison.OrdinalIgnoreCase)))
continue;
matchedRoles.Add(mapping.Role);
if (mapping.Role.Equals("Deployment", StringComparison.OrdinalIgnoreCase))
{
hasDeploymentRole = true;
// Check for site scope rules
var scopeRules = await _securityRepository.GetScopeRulesForMappingAsync(mapping.Id, ct);
if (scopeRules.Count > 0)
{
hasDeploymentWithScopeRules = true;
foreach (var rule in scopeRules)
{
permittedSiteIds.Add(rule.SiteId.ToString());
}
}
}
}
// System-wide deployment: user has Deployment role but no site scope rules restrict them
var isSystemWide = hasDeploymentRole && !hasDeploymentWithScopeRules;
return new RoleMappingResult(
matchedRoles.ToList(),
permittedSiteIds.ToList(),
isSystemWide);
}
}