Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using FSH.Modules.Identity.Contracts.DTOs;
using FSH.Modules.Identity.Contracts.DTOs;

namespace FSH.Modules.Identity.Contracts.Services;

public interface IRoleService
{
Task<IEnumerable<RoleDto>> GetRolesAsync();
Task<RoleDto?> GetRoleAsync(string id);
Task<RoleDto> CreateOrUpdateRoleAsync(string roleId, string name, string description);
Task DeleteRoleAsync(string id);
Task<RoleDto> GetWithPermissionsAsync(string id, CancellationToken cancellationToken);

Task<string> UpdatePermissionsAsync(string roleId, List<string> permissions);
}
Task<IEnumerable<RoleDto>> GetRolesAsync(CancellationToken cancellationToken = default);
Task<RoleDto?> GetRoleAsync(string id, CancellationToken cancellationToken = default);
Task<RoleDto> CreateOrUpdateRoleAsync(string roleId, string name, string description, CancellationToken cancellationToken = default);
Task DeleteRoleAsync(string id, CancellationToken cancellationToken = default);
Task<RoleDto> GetWithPermissionsAsync(string id, CancellationToken cancellationToken = default);
Task<string> UpdatePermissionsAsync(string roleId, List<string> permissions, CancellationToken cancellationToken = default);
}
46 changes: 23 additions & 23 deletions src/Modules/Identity/Modules.Identity/Data/IdentityDbInitializer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Finbuckle.MultiTenant.Abstractions;
using Finbuckle.MultiTenant.Abstractions;
using FSH.Framework.Persistence;
using FSH.Framework.Shared.Constants;
using FSH.Framework.Shared.Multitenancy;
Expand Down Expand Up @@ -31,16 +31,16 @@ public async Task MigrateAsync(CancellationToken cancellationToken)

public async Task SeedAsync(CancellationToken cancellationToken)
{
await SeedRolesAsync();
await SeedSystemGroupsAsync();
await SeedAdminUserAsync();
await SeedRolesAsync(cancellationToken);
await SeedSystemGroupsAsync(cancellationToken);
await SeedAdminUserAsync(cancellationToken);
}

private async Task SeedRolesAsync()
private async Task SeedRolesAsync(CancellationToken cancellationToken = default)
{
foreach (string roleName in RoleConstants.DefaultRoles)
{
if (await roleManager.Roles.SingleOrDefaultAsync(r => r.Name == roleName)
if (await roleManager.Roles.SingleOrDefaultAsync(r => r.Name == roleName, cancellationToken)
is not FshRole role)
{
// create role
Expand All @@ -51,21 +51,21 @@ private async Task SeedRolesAsync()
// Assign permissions
if (roleName == RoleConstants.Basic)
{
await AssignPermissionsToRoleAsync(context, PermissionConstants.Basic, role);
await AssignPermissionsToRoleAsync(context, PermissionConstants.Basic, role, cancellationToken);
}
else if (roleName == RoleConstants.Admin)
{
await AssignPermissionsToRoleAsync(context, PermissionConstants.Admin, role);
await AssignPermissionsToRoleAsync(context, PermissionConstants.Admin, role, cancellationToken);

if (multiTenantContextAccessor.MultiTenantContext.TenantInfo?.Id == MultitenancyConstants.Root.Id)
{
await AssignPermissionsToRoleAsync(context, PermissionConstants.Root, role);
await AssignPermissionsToRoleAsync(context, PermissionConstants.Root, role, cancellationToken);
}
}
}
}

private async Task AssignPermissionsToRoleAsync(IdentityDbContext dbContext, IReadOnlyList<FshPermission> permissions, FshRole role)
private async Task AssignPermissionsToRoleAsync(IdentityDbContext dbContext, IReadOnlyList<FshPermission> permissions, FshRole role, CancellationToken cancellationToken = default)
{
var currentClaims = await roleManager.GetClaimsAsync(role);
var newClaims = permissions
Expand All @@ -83,18 +83,18 @@ private async Task AssignPermissionsToRoleAsync(IdentityDbContext dbContext, IRe
foreach (var claim in newClaims)
{
logger.LogInformation("Seeding {Role} Permission '{Permission}' for '{TenantId}' Tenant.", role.Name, claim.ClaimValue, multiTenantContextAccessor.MultiTenantContext.TenantInfo?.Id);
await dbContext.RoleClaims.AddAsync(claim);
await dbContext.RoleClaims.AddAsync(claim, cancellationToken);
}

// Save changes to the database context
if (newClaims.Count != 0)
{
await dbContext.SaveChangesAsync();
await dbContext.SaveChangesAsync(cancellationToken);
}

}

private async Task SeedSystemGroupsAsync()
private async Task SeedSystemGroupsAsync(CancellationToken cancellationToken = default)
{
var tenantId = multiTenantContextAccessor.MultiTenantContext.TenantInfo?.Id;
if (string.IsNullOrWhiteSpace(tenantId))
Expand All @@ -105,7 +105,7 @@ private async Task SeedSystemGroupsAsync()
// Seed "All Users" default group - all new users are automatically added to this group
const string allUsersGroupName = "All Users";
var allUsersGroup = await context.Groups
.FirstOrDefaultAsync(g => g.Name == allUsersGroupName && g.IsSystemGroup);
.FirstOrDefaultAsync(g => g.Name == allUsersGroupName && g.IsSystemGroup, cancellationToken);

if (allUsersGroup is null)
{
Expand All @@ -116,14 +116,14 @@ private async Task SeedSystemGroupsAsync()
isSystemGroup: true,
createdBy: "System");

await context.Groups.AddAsync(allUsersGroup);
await context.Groups.AddAsync(allUsersGroup, cancellationToken);
logger.LogInformation("Seeding '{GroupName}' system group for '{TenantId}' Tenant.", allUsersGroupName, tenantId);
}

// Seed "Administrators" group with Admin role
const string administratorsGroupName = "Administrators";
var administratorsGroup = await context.Groups
.FirstOrDefaultAsync(g => g.Name == administratorsGroupName && g.IsSystemGroup);
.FirstOrDefaultAsync(g => g.Name == administratorsGroupName && g.IsSystemGroup, cancellationToken);

if (administratorsGroup is null)
{
Expand All @@ -134,37 +134,37 @@ private async Task SeedSystemGroupsAsync()
isSystemGroup: true,
createdBy: "System");

await context.Groups.AddAsync(administratorsGroup);
await context.Groups.AddAsync(administratorsGroup, cancellationToken);
logger.LogInformation("Seeding '{GroupName}' system group for '{TenantId}' Tenant.", administratorsGroupName, tenantId);
}

await context.SaveChangesAsync();
await context.SaveChangesAsync(cancellationToken);

// Assign Admin role to Administrators group
var adminRole = await roleManager.FindByNameAsync(RoleConstants.Admin);
if (adminRole is not null)
{
var existingGroupRole = await context.GroupRoles
.FirstOrDefaultAsync(gr => gr.GroupId == administratorsGroup.Id && gr.RoleId == adminRole.Id);
.FirstOrDefaultAsync(gr => gr.GroupId == administratorsGroup.Id && gr.RoleId == adminRole.Id, cancellationToken);

if (existingGroupRole is null)
{
context.GroupRoles.Add(GroupRole.Create(administratorsGroup.Id, adminRole.Id));

await context.SaveChangesAsync();
await context.SaveChangesAsync(cancellationToken);
logger.LogInformation("Assigned Admin role to '{GroupName}' group for '{TenantId}' Tenant.", administratorsGroupName, tenantId);
}
}
}

private async Task SeedAdminUserAsync()
private async Task SeedAdminUserAsync(CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(multiTenantContextAccessor.MultiTenantContext.TenantInfo?.Id) || string.IsNullOrWhiteSpace(multiTenantContextAccessor.MultiTenantContext.TenantInfo?.AdminEmail))
{
return;
}

if (await userManager.Users.FirstOrDefaultAsync(u => u.Email == multiTenantContextAccessor.MultiTenantContext.TenantInfo!.AdminEmail)
if (await userManager.Users.FirstOrDefaultAsync(u => u.Email == multiTenantContextAccessor.MultiTenantContext.TenantInfo!.AdminEmail, cancellationToken)
is not FshUser adminUser)
{
string adminUserName = $"{multiTenantContextAccessor.MultiTenantContext.TenantInfo?.Id.Trim()}.{RoleConstants.Admin}".ToUpperInvariant();
Expand Down Expand Up @@ -195,4 +195,4 @@ private async Task SeedAdminUserAsync()
await userManager.AddToRoleAsync(adminUser, RoleConstants.Admin);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public async ValueTask<Unit> Handle(DeleteRoleCommand command, CancellationToken
{
ArgumentNullException.ThrowIfNull(command);

await _roleService.DeleteRoleAsync(command.Id).ConfigureAwait(false);
await _roleService.DeleteRoleAsync(command.Id, cancellationToken).ConfigureAwait(false);

return Unit.Value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ public GetRoleByIdQueryHandler(IRoleService roleService)
public async ValueTask<RoleDto?> Handle(GetRoleQuery query, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
return await _roleService.GetRoleAsync(query.Id).ConfigureAwait(false);
return await _roleService.GetRoleAsync(query.Id, cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public GetRolesQueryHandler(IRoleService roleService)

public async ValueTask<IEnumerable<RoleDto>> Handle(GetRolesQuery query, CancellationToken cancellationToken)
{
return await _roleService.GetRolesAsync().ConfigureAwait(false);
return await _roleService.GetRolesAsync(cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Finbuckle.MultiTenant.Abstractions;
using Finbuckle.MultiTenant.Abstractions;
using FSH.Framework.Core.Context;
using FSH.Framework.Core.Exceptions;
using FSH.Framework.Shared.Constants;
Expand All @@ -17,7 +17,7 @@ public class RoleService(RoleManager<FshRole> roleManager,
IMultiTenantContextAccessor<AppTenantInfo> multiTenantContextAccessor,
ICurrentUser currentUser) : IRoleService
{
public async Task<IEnumerable<RoleDto>> GetRolesAsync()
public async Task<IEnumerable<RoleDto>> GetRolesAsync(CancellationToken cancellationToken = default)
{
if (roleManager is null)
throw new NotFoundException("RoleManager<FshRole> not resolved. Check Identity registration.");
Expand All @@ -28,12 +28,12 @@ public async Task<IEnumerable<RoleDto>> GetRolesAsync()

var roles = await roleManager.Roles
.Select(role => new RoleDto { Id = role.Id, Name = role.Name!, Description = role.Description })
.ToListAsync();
.ToListAsync(cancellationToken);

return roles;
}

public async Task<RoleDto?> GetRoleAsync(string id)
public async Task<RoleDto?> GetRoleAsync(string id, CancellationToken cancellationToken = default)
{
FshRole? role = await roleManager.FindByIdAsync(id);

Expand All @@ -42,7 +42,7 @@ public async Task<IEnumerable<RoleDto>> GetRolesAsync()
return new RoleDto { Id = role.Id, Name = role.Name!, Description = role.Description };
}

public async Task<RoleDto> CreateOrUpdateRoleAsync(string roleId, string name, string description)
public async Task<RoleDto> CreateOrUpdateRoleAsync(string roleId, string name, string description, CancellationToken cancellationToken = default)
{
FshRole? role = await roleManager.FindByIdAsync(roleId);

Expand All @@ -61,7 +61,7 @@ public async Task<RoleDto> CreateOrUpdateRoleAsync(string roleId, string name, s
return new RoleDto { Id = role.Id, Name = role.Name!, Description = role.Description };
}

public async Task DeleteRoleAsync(string id)
public async Task DeleteRoleAsync(string id, CancellationToken cancellationToken = default)
{
FshRole? role = await roleManager.FindByIdAsync(id);

Expand All @@ -70,9 +70,9 @@ public async Task DeleteRoleAsync(string id)
await roleManager.DeleteAsync(role);
}

public async Task<RoleDto> GetWithPermissionsAsync(string id, CancellationToken cancellationToken)
public async Task<RoleDto> GetWithPermissionsAsync(string id, CancellationToken cancellationToken = default)
{
var role = await GetRoleAsync(id);
var role = await GetRoleAsync(id, cancellationToken);
_ = role ?? throw new NotFoundException("role not found");

role.Permissions = await context.RoleClaims
Expand All @@ -83,7 +83,7 @@ public async Task<RoleDto> GetWithPermissionsAsync(string id, CancellationToken
return role;
}

public async Task<string> UpdatePermissionsAsync(string roleId, List<string> permissions)
public async Task<string> UpdatePermissionsAsync(string roleId, List<string> permissions, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(permissions);

Expand All @@ -94,8 +94,8 @@ public async Task<string> UpdatePermissionsAsync(string roleId, List<string> per
FilterRootPermissions(permissions);

var currentClaims = await roleManager.GetClaimsAsync(role);
await RemoveRevokedPermissionsAsync(role, currentClaims, permissions);
await AddNewPermissionsAsync(role, currentClaims, permissions);
await RemoveRevokedPermissionsAsync(role, currentClaims, permissions, cancellationToken);
await AddNewPermissionsAsync(role, currentClaims, permissions, cancellationToken);

return "permissions updated";
}
Expand All @@ -116,12 +116,13 @@ private void FilterRootPermissions(List<string> permissions)
}
}

private async Task RemoveRevokedPermissionsAsync(FshRole role, IList<System.Security.Claims.Claim> currentClaims, List<string> permissions)
private async Task RemoveRevokedPermissionsAsync(FshRole role, IList<System.Security.Claims.Claim> currentClaims, List<string> permissions, CancellationToken cancellationToken = default)
{
var claimsToRemove = currentClaims.Where(c => !permissions.Exists(p => p == c.Value));

foreach (var claim in claimsToRemove)
{
cancellationToken.ThrowIfCancellationRequested();
var result = await roleManager.RemoveClaimAsync(role, claim);
if (!result.Succeeded)
{
Expand All @@ -131,7 +132,7 @@ private async Task RemoveRevokedPermissionsAsync(FshRole role, IList<System.Secu
}
}

private async Task AddNewPermissionsAsync(FshRole role, IList<System.Security.Claims.Claim> currentClaims, List<string> permissions)
private async Task AddNewPermissionsAsync(FshRole role, IList<System.Security.Claims.Claim> currentClaims, List<string> permissions, CancellationToken cancellationToken = default)
{
var newPermissions = permissions
.Where(p => !string.IsNullOrEmpty(p) && !currentClaims.Any(c => c.Value == p))
Expand All @@ -150,7 +151,7 @@ private async Task AddNewPermissionsAsync(FshRole role, IList<System.Security.Cl

if (newPermissions.Count > 0)
{
await context.SaveChangesAsync();
await context.SaveChangesAsync(cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public UpdatePermissionsCommandHandler(IRoleService roleService)
public async ValueTask<string> Handle(UpdatePermissionsCommand command, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(command);
return await _roleService.UpdatePermissionsAsync(command.RoleId, command.Permissions).ConfigureAwait(false);
return await _roleService.UpdatePermissionsAsync(command.RoleId, command.Permissions, cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public async ValueTask<RoleDto> Handle(UpsertRoleCommand command, CancellationTo
{
ArgumentNullException.ThrowIfNull(command);

return await _roleService.CreateOrUpdateRoleAsync(command.Id, command.Name, command.Description ?? string.Empty)
return await _roleService.CreateOrUpdateRoleAsync(command.Id, command.Name, command.Description ?? string.Empty, cancellationToken)
.ConfigureAwait(false);
}
}
Loading