fix blocker 1 - super admin
This commit is contained in:
+4
-4
@@ -1,4 +1,5 @@
|
||||
using System.Security.Claims;
|
||||
using Baya.Domain.Entities.User;
|
||||
|
||||
namespace Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
|
||||
@@ -6,18 +7,17 @@ public class DynamicPermissionService : IDynamicPermissionService
|
||||
{
|
||||
public bool CanAccess(ClaimsPrincipal user, string area, string controller, string action)
|
||||
{
|
||||
if (user.IsInRole("admin"))
|
||||
// super_admin is definitionally the top role, but RoleNames has no hierarchy — each admin
|
||||
// sub-role is a sibling, so it must be granted the bypass explicitly, not implied by "admin".
|
||||
if (user.IsInRole(RoleNames.Admin) || user.IsInRole(RoleNames.SuperAdmin))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
var key = $"{area}:{controller}:";
|
||||
|
||||
var userClaims = user.FindAll(ConstantPolicies.DynamicPermission);
|
||||
|
||||
return userClaims.Any(item => item.Value.Equals(key, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+47
-1
@@ -1,5 +1,8 @@
|
||||
using Baya.Application.Contracts.Identity;
|
||||
using Baya.Application.Models.Identity;
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Identity.Identity.Manager;
|
||||
using Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
@@ -15,12 +18,25 @@ public class SeedDataBase : ISeedDataBase
|
||||
private readonly AppUserManager _userManager;
|
||||
private readonly AppRoleManager _roleManager;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IRoleManagerService _roleManagerService;
|
||||
|
||||
public SeedDataBase(AppUserManager userManager, AppRoleManager roleManager, IConfiguration configuration)
|
||||
// The admin-console permission matrix owned by each non-admin sub-role — mirrors
|
||||
// client/src/hooks/capabilities.ts (useAdminCapabilities), the reviewed source of truth for who
|
||||
// owns which console. Area is always empty: no controller uses [Area] today.
|
||||
private static readonly IReadOnlyDictionary<string, string[]> AdminSubRolePermissions =
|
||||
new Dictionary<string, string[]>
|
||||
{
|
||||
[RoleNames.Finance] = ["AdminRefunds", "AdminPayouts", "PlatformConfig"],
|
||||
[RoleNames.Support] = ["AdminVerifications", "AdminVerificationStepTypes", "SupportAlerts", "AdminTickets"],
|
||||
[RoleNames.Moderation] = ["AdminReviews", "Reviews"],
|
||||
};
|
||||
|
||||
public SeedDataBase(AppUserManager userManager, AppRoleManager roleManager, IConfiguration configuration, IRoleManagerService roleManagerService)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_roleManager = roleManager;
|
||||
_configuration = configuration;
|
||||
_roleManagerService = roleManagerService;
|
||||
}
|
||||
|
||||
public async Task Seed()
|
||||
@@ -38,9 +54,39 @@ public class SeedDataBase : ISeedDataBase
|
||||
}
|
||||
}
|
||||
|
||||
await SeedAdminSubRolePermissionsAsync();
|
||||
await SeedBootstrapAdminAsync();
|
||||
}
|
||||
|
||||
// finance/support/moderation are sibling roles, not implied by super_admin — each needs its own
|
||||
// DynamicPermission claims scoped to the consoles it owns. Skips roles already at the target claim
|
||||
// set so a normal restart doesn't churn every affected user's security stamp.
|
||||
private async Task SeedAdminSubRolePermissionsAsync()
|
||||
{
|
||||
foreach (var (roleName, controllers) in AdminSubRolePermissions)
|
||||
{
|
||||
var role = await _roleManager.FindByNameAsync(roleName);
|
||||
if (role == null)
|
||||
continue;
|
||||
|
||||
var targetPermissions = controllers.Select(controller => $":{controller}:").ToList();
|
||||
|
||||
var existingPermissions = (await _roleManager.GetClaimsAsync(role))
|
||||
.Where(c => c.Type == ConstantPolicies.DynamicPermission)
|
||||
.Select(c => c.Value)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (existingPermissions.SetEquals(targetPermissions))
|
||||
continue;
|
||||
|
||||
await _roleManagerService.ChangeRolePermissionsAsync(new EditRolePermissionsDto
|
||||
{
|
||||
RoleId = role.Id,
|
||||
Permissions = targetPermissions
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// The bootstrap admin is config-driven, never a committed credential: it is created only when both
|
||||
// Seed:AdminUsername and Seed:AdminPassword are supplied (via the environment-specific appsettings file, environment
|
||||
// variables in a deployment). With neither configured — the default for Testing and any fresh boot —
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
using System.Security.Claims;
|
||||
using Baya.Domain.Entities.User;
|
||||
using Baya.Infrastructure.Identity.Identity.PermissionManager;
|
||||
|
||||
namespace Baya.Test.Infrastructure.Identity
|
||||
{
|
||||
public class DynamicPermissionServiceTest
|
||||
{
|
||||
private readonly DynamicPermissionService _sut = new();
|
||||
|
||||
[Fact]
|
||||
public void Admin_Role_Bypasses_Permission_Check()
|
||||
{
|
||||
var user = BuildUser(RoleNames.Admin);
|
||||
|
||||
Assert.True(_sut.CanAccess(user, area: null, controller: "AdminRefunds", action: "Create"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuperAdmin_Role_Bypasses_Permission_Check()
|
||||
{
|
||||
var user = BuildUser(RoleNames.SuperAdmin);
|
||||
|
||||
Assert.True(_sut.CanAccess(user, area: null, controller: "AdminRefunds", action: "Create"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Role_With_Matching_Claim_Is_Granted_Access()
|
||||
{
|
||||
var user = BuildUser(RoleNames.Finance, dynamicPermission: ":AdminRefunds:");
|
||||
|
||||
Assert.True(_sut.CanAccess(user, area: null, controller: "AdminRefunds", action: "Create"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Role_Without_Matching_Claim_Is_Denied_Access()
|
||||
{
|
||||
var user = BuildUser(RoleNames.Finance, dynamicPermission: ":AdminRefunds:");
|
||||
|
||||
Assert.False(_sut.CanAccess(user, area: null, controller: "AdminReviews", action: "Status"));
|
||||
}
|
||||
|
||||
private static ClaimsPrincipal BuildUser(string role, string? dynamicPermission = null)
|
||||
{
|
||||
var claims = new List<Claim> { new(ClaimTypes.Role, role) };
|
||||
|
||||
if (dynamicPermission != null)
|
||||
claims.Add(new Claim(ConstantPolicies.DynamicPermission, dynamicPermission));
|
||||
|
||||
var identity = new ClaimsIdentity(claims, "TestAuth", ClaimTypes.Name, ClaimTypes.Role);
|
||||
return new ClaimsPrincipal(identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user