Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup Identity #443

Merged
merged 18 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
HasPermissionAsync: move to userservice and fix caching
* clear cache from RoleService.UpdatePermissions
* also remove roleclaimservice and further cleanup
  • Loading branch information
fretje committed Feb 6, 2022
commit a353fa55dcb9c0dae94a8e3e938de46c6523be8a
18 changes: 0 additions & 18 deletions src/Core/Application/Identity/RoleClaims/IRoleClaimsService.cs

This file was deleted.

1 change: 1 addition & 0 deletions src/Core/Application/Identity/Users/IUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public interface IUserService : ITransientService
Task<string> AssignRolesAsync(string userId, UserRolesRequest request, CancellationToken cancellationToken);

Task<List<string>> GetPermissionsAsync(string userId, CancellationToken cancellationToken);
public Task<bool> HasPermissionAsync(string userId, string permission, CancellationToken cancellationToken = default);

Task ToggleUserStatusAsync(ToggleUserStatusRequest request, CancellationToken cancellationToken);
}
4 changes: 2 additions & 2 deletions src/Core/Shared/Authorization/FSHRoles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ public static class FSHRoles
public static string Admin = nameof(Admin);
public static string Basic = nameof(Basic);

public static IReadOnlyList<string> Default { get; } = new ReadOnlyCollection<string>(new[]
public static IReadOnlyList<string> DefaultRoles { get; } = new ReadOnlyCollection<string>(new[]
{
Admin,
Basic
});

public static bool IsDefault(string roleName) => Default.Any(r => r == roleName);
public static bool IsDefault(string roleName) => DefaultRoles.Any(r => r == roleName);
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
using System.Security.Claims;
using FSH.WebApi.Application.Identity.RoleClaims;
using FSH.WebApi.Application.Identity.Users;
using Microsoft.AspNetCore.Authorization;

namespace FSH.WebApi.Infrastructure.Auth.Permissions;

internal class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IRoleClaimsService _permissionService;
private readonly IUserService _userService;

public PermissionAuthorizationHandler(IRoleClaimsService permissionService)
{
_permissionService = permissionService;
}
public PermissionAuthorizationHandler(IUserService userService) =>
_userService = userService;

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
string? userId = context.User?.GetUserId();
if (userId is not null &&
await _permissionService.HasPermissionAsync(userId, requirement.Permission))
if (context.User?.GetUserId() is { } userId &&
await _userService.HasPermissionAsync(userId, requirement.Permission))
{
context.Succeed(requirement);
}
Expand Down
10 changes: 3 additions & 7 deletions src/Infrastructure/Identity/ApplicationRole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@ public class ApplicationRole : IdentityRole
{
public string? Description { get; set; }

public ApplicationRole()
{
}

public ApplicationRole(string roleName, string? description = null)
: base(roleName)
public ApplicationRole(string name, string? description = null)
: base(name)
{
Description = description;
NormalizedName = roleName.ToUpperInvariant();
NormalizedName = name.ToUpperInvariant();
}
}
14 changes: 2 additions & 12 deletions src/Infrastructure/Identity/ApplicationRoleClaim.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@ namespace FSH.WebApi.Infrastructure.Identity;

public class ApplicationRoleClaim : IdentityRoleClaim<string>
{
public string? CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }

public ApplicationRoleClaim()
{
}

public ApplicationRoleClaim(string? createdBy = null, DateTime? createdOn = null)
{
CreatedBy = createdBy;
CreatedOn = createdOn ?? DateTime.UtcNow;
}
public string? CreatedBy { get; init; }
public DateTime CreatedOn { get; init; }
fretje marked this conversation as resolved.
Show resolved Hide resolved
}
137 changes: 0 additions & 137 deletions src/Infrastructure/Identity/RoleClaimsService.cs

This file was deleted.

56 changes: 35 additions & 21 deletions src/Infrastructure/Identity/RoleService.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.Security.Claims;
using Finbuckle.MultiTenant;
using FSH.WebApi.Application.Common.Caching;
using FSH.WebApi.Application.Common.Exceptions;
using FSH.WebApi.Application.Common.Interfaces;
using FSH.WebApi.Application.Identity;
using FSH.WebApi.Application.Identity.Roles;
using FSH.WebApi.Infrastructure.Common.Extensions;
using FSH.WebApi.Infrastructure.Persistence.Context;
using FSH.WebApi.Shared.Authorization;
using FSH.WebApi.Shared.Multitenancy;
Expand All @@ -18,22 +18,31 @@ public class RoleService : IRoleService
{
private readonly RoleManager<ApplicationRole> _roleManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _context;
private readonly ApplicationDbContext _db;
private readonly IStringLocalizer<RoleService> _localizer;
private readonly ICurrentUser _currentUser;
private readonly ITenantInfo _tenantInfo;
private readonly ICacheService _cache;
private readonly ICacheKeyService _cacheKeys;

public RoleService(
RoleManager<ApplicationRole> roleManager,
UserManager<ApplicationUser> userManager,
ApplicationDbContext context,
ApplicationDbContext db,
IStringLocalizer<RoleService> localizer,
ITenantInfo tenantInfo)
ICurrentUser currentUser,
ITenantInfo tenantInfo,
ICacheService cache,
ICacheKeyService cacheKeys)
{
_roleManager = roleManager;
_userManager = userManager;
_context = context;
_db = db;
_localizer = localizer;
_currentUser = currentUser;
_tenantInfo = tenantInfo;
_cache = cache;
_cacheKeys = cacheKeys;
}

public async Task<List<RoleDto>> GetListAsync(CancellationToken cancellationToken) =>
Expand All @@ -49,15 +58,15 @@ is ApplicationRole existingRole
&& existingRole.Id != excludeId;

public async Task<RoleDto> GetByIdAsync(string id) =>
await _context.Roles.SingleOrDefaultAsync(x => x.Id == id) is { } role
await _db.Roles.SingleOrDefaultAsync(x => x.Id == id) is { } role
? role.Adapt<RoleDto>()
: throw new NotFoundException(_localizer["Role Not Found"]);

public async Task<RoleDto> GetByIdWithPermissionsAsync(string roleId, CancellationToken cancellationToken)
{
var role = await GetByIdAsync(roleId);

role.Permissions = await _context.RoleClaims
role.Permissions = await _db.RoleClaims
.Where(c => c.RoleId == roleId && c.ClaimType == FSHClaims.Permission)
.Select(c => c.ClaimValue)
.ToListAsync(cancellationToken);
Expand All @@ -82,7 +91,7 @@ public async Task<string> CreateOrUpdateAsync(CreateOrUpdateRoleRequest request)

_ = role ?? throw new NotFoundException(_localizer["Role Not Found"]);

if (DefaultRoles.Contains(role.Name))
if (FSHRoles.IsDefault(role.Name))
{
throw new ConflictException(string.Format(_localizer["Not allowed to modify {0} Role."], role.Name));
}
Expand All @@ -100,7 +109,6 @@ public async Task<string> CreateOrUpdateAsync(CreateOrUpdateRoleRequest request)

public async Task<string> UpdatePermissionsAsync(UpdateRolePermissionsRequest request, CancellationToken cancellationToken)
{
var selectedPermissions = request.Permissions;
var role = await _roleManager.FindByIdAsync(request.RoleId);
_ = role ?? throw new NotFoundException(_localizer["Role Not Found"]);
if (role.Name == FSHRoles.Admin)
Expand All @@ -114,10 +122,16 @@ public async Task<string> UpdatePermissionsAsync(UpdateRolePermissionsRequest re
request.Permissions.RemoveAll(u => u.StartsWith("Permissions.Root."));
}

var currentPermissions = await _roleManager.GetClaimsAsync(role);
// Clear the permission cache
foreach (var user in await _userManager.GetUsersInRoleAsync(role.Name))
{
await _cache.RemoveAsync(_cacheKeys.GetCacheKey(FSHClaims.Permission, user.Id));
}

var currentClaims = await _roleManager.GetClaimsAsync(role);

// Remove permissions that were previously selected
foreach (var claim in currentPermissions.Where(c => !selectedPermissions.Any(p => p == c.Value)))
foreach (var claim in currentClaims.Where(c => !request.Permissions.Any(p => p == c.Value)))
{
var removeResult = await _roleManager.RemoveClaimAsync(role, claim);
if (!removeResult.Succeeded)
Expand All @@ -127,15 +141,18 @@ public async Task<string> UpdatePermissionsAsync(UpdateRolePermissionsRequest re
}

// Add all permissions that were not previously selected
foreach (string permission in selectedPermissions.Where(c => !currentPermissions.Any(p => p.Value == c)))
foreach (string permission in request.Permissions.Where(c => !currentClaims.Any(p => p.Value == c)))
{
if (!string.IsNullOrEmpty(permission))
{
var addResult = await _roleManager.AddClaimAsync(role, new Claim(FSHClaims.Permission, permission));
if (!addResult.Succeeded)
_db.RoleClaims.Add(new ApplicationRoleClaim
{
throw new InternalServerException(_localizer["Update permissions failed."], addResult.Errors.Select(e => _localizer[e.Description].ToString()).ToList());
}
RoleId = role.Id,
ClaimType = FSHClaims.Permission,
ClaimValue = permission,
CreatedBy = _currentUser.GetUserId().ToString()
});
await _db.SaveChangesAsync(cancellationToken);
}
}

Expand All @@ -148,7 +165,7 @@ public async Task<string> DeleteAsync(string id)

_ = role ?? throw new NotFoundException(_localizer["Role Not Found"]);

if (DefaultRoles.Contains(role.Name))
if (FSHRoles.IsDefault(role.Name))
{
throw new ConflictException(string.Format(_localizer["Not allowed to delete {0} Role."], role.Name));
}
Expand All @@ -173,7 +190,4 @@ public async Task<string> DeleteAsync(string id)
throw new ConflictException(string.Format(_localizer["Not allowed to delete {0} Role as it is being used."], role.Name));
}
}

internal static List<string> DefaultRoles =>
typeof(FSHRoles).GetAllPublicConstantValues<string>();
}
Loading