diff --git a/src/HelloShop.IdentityService/Controllers/PermissionsController.cs b/src/HelloShop.IdentityService/Controllers/PermissionsController.cs index 2f066ba..4feef68 100644 --- a/src/HelloShop.IdentityService/Controllers/PermissionsController.cs +++ b/src/HelloShop.IdentityService/Controllers/PermissionsController.cs @@ -9,38 +9,16 @@ using Microsoft.EntityFrameworkCore; [Route("api/[controller]")] [ApiController] [Authorize] -public class PermissionsController(IdentityServiceDbContext dbContext) : ControllerBase +public class PermissionsController(IPermissionChecker permissionChecker) : ControllerBase { - - [HttpGet(nameof(PermissionList))] - public async Task>> PermissionList(int? roleId, string? name, string? resourceType = null, string? resourceId = null) + [HttpHead] + public async Task CheckPermission(string permissionName, string? resourceType = null, string? resourceId = null) { - var roleIds = HttpContext.User.FindAll(CustomClaimTypes.RoleIdentifier).Select(c => Convert.ToInt32(c.Value)); - - if (roleId.HasValue && roleIds.Any(x => x == roleId)) + if(await permissionChecker.IsGrantedAsync(permissionName, resourceType, resourceId)) { - roleIds = [roleId.Value]; + return Ok(); } - List result = []; - - IQueryable queryable = dbContext.Set().Where(x => roleIds.Contains(x.RoleId)); - - if (!string.IsNullOrWhiteSpace(name)) - { - queryable = queryable.Where(x => x.PermissionName == name); - } - - var permissionGrants = await queryable.Where(x => x.ResourceType == resourceType && x.ResourceId == resourceId).Distinct().ToListAsync(); - - result.AddRange(permissionGrants.Select(x => new PermissionGrantedResponse - { - Name = x.PermissionName, - ResourceType = x.ResourceType, - ResourceId = x.ResourceId, - IsGranted = true - })); - - return Ok(result); + return Forbid(); } } diff --git a/src/HelloShop.IdentityService/Controllers/UsersController.cs b/src/HelloShop.IdentityService/Controllers/UsersController.cs index f684e3a..a49b30e 100644 --- a/src/HelloShop.IdentityService/Controllers/UsersController.cs +++ b/src/HelloShop.IdentityService/Controllers/UsersController.cs @@ -21,9 +21,25 @@ namespace HelloShop.IdentityService.Controllers [HttpGet("{id}")] [Authorize(IdentityPermissions.Users.Default)] - public User? GetUser(int id) + public async Task> GetUser(int id, [FromServices] IAuthorizationService authorizationService) { - return dbContext.Set().Find(id); + ResourceInfo resource = new(nameof(User), id.ToString()); + + var authorizationResult = await authorizationService.AuthorizeAsync(User, resource, IdentityPermissions.Users.Default); + + if (!authorizationResult.Succeeded) + { + return Forbid(); + } + + User? user = dbContext.Set().Find(id); + + if (user == null) + { + return NotFound(); + } + + return Ok(user); } [HttpPost] diff --git a/src/HelloShop.IdentityService/DataSeeding/UserDataSeedingProvider.cs b/src/HelloShop.IdentityService/DataSeeding/UserDataSeedingProvider.cs index 5f2a703..538796e 100644 --- a/src/HelloShop.IdentityService/DataSeeding/UserDataSeedingProvider.cs +++ b/src/HelloShop.IdentityService/DataSeeding/UserDataSeedingProvider.cs @@ -2,64 +2,57 @@ using HelloShop.IdentityService.EntityFrameworks; using HelloShop.ServiceDefaults.Infrastructure; using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; namespace HelloShop.IdentityService.DataSeeding { public class UserDataSeedingProvider(UserManager userManager, RoleManager roleManager) : IDataSeedingProvider { - public async Task SeedingAsync(IServiceProvider serviceProvider) + public async Task SeedingAsync(IServiceProvider ServiceProvider) { - var adminRole = await roleManager.FindByNameAsync("AdminRole"); + var adminRole = await roleManager.Roles.SingleOrDefaultAsync(x => x.Name == "AdminRole"); if (adminRole == null) { - await roleManager.CreateAsync(new Role - { - Name = "AdminRole" - }); + adminRole = new Role { Name = "AdminRole", }; + await roleManager.CreateAsync(adminRole); } - var guestRole = await roleManager.FindByNameAsync("GuestRole"); + var guestRole = await roleManager.Roles.SingleOrDefaultAsync(x => x.Name == "GuestRole"); if (guestRole == null) { - await roleManager.CreateAsync(new Role - { - Name = "GuestRole" - }); + guestRole = new Role { Name = "GuestRole", }; + await roleManager.CreateAsync(guestRole); } var adminUser = await userManager.FindByNameAsync("admin"); if (adminUser == null) { - await userManager.CreateAsync(new User + adminUser = new User { UserName = "admin", Email = "admin@test.com" - },"admin"); + }; + await userManager.CreateAsync(adminUser, adminUser.UserName); } - if (adminUser!=null) - { - await userManager.AddToRolesAsync(adminUser, ["AdminRole", "GuestRole"]); - } + await userManager.AddToRolesAsync(adminUser, ["AdminRole", "GuestRole"]); var guestUser = await userManager.FindByNameAsync("guest"); if (guestUser == null) { - await userManager.CreateAsync(new User + guestUser = new User { UserName = "guest", Email = "guest@test.com" - },"guest"); + }; + await userManager.CreateAsync(guestUser, guestUser.UserName); } - if (guestUser!=null) - { - await userManager.AddToRoleAsync(guestUser, "GuestRole"); - } + await userManager.AddToRoleAsync(guestUser, "GuestRole"); } } } diff --git a/src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240330112954_InitialCreate.Designer.cs b/src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240403122821_InitialCreate.Designer.cs similarity index 99% rename from src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240330112954_InitialCreate.Designer.cs rename to src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240403122821_InitialCreate.Designer.cs index ea30e08..8348a16 100644 --- a/src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240330112954_InitialCreate.Designer.cs +++ b/src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240403122821_InitialCreate.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace HelloShop.IdentityService.EntityFrameworks.Migrations { [DbContext(typeof(IdentityServiceDbContext))] - [Migration("20240330112954_InitialCreate")] + [Migration("20240403122821_InitialCreate")] partial class InitialCreate { /// diff --git a/src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240330112954_InitialCreate.cs b/src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240403122821_InitialCreate.cs similarity index 100% rename from src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240330112954_InitialCreate.cs rename to src/HelloShop.IdentityService/EntityFrameworks/Migrations/20240403122821_InitialCreate.cs diff --git a/src/HelloShop.ServiceDefaults/Authorization/IPermissionChecker.cs b/src/HelloShop.ServiceDefaults/Authorization/IPermissionChecker.cs index 5726c2e..1c375b7 100644 --- a/src/HelloShop.ServiceDefaults/Authorization/IPermissionChecker.cs +++ b/src/HelloShop.ServiceDefaults/Authorization/IPermissionChecker.cs @@ -4,7 +4,7 @@ namespace HelloShop.ServiceDefaults.Authorization; public interface IPermissionChecker { - Task IsGrantedAsync(string name, string? resourceType = null, string? resourceId = null); + Task IsGrantedAsync(string permissionName, string? resourceType = null, string? resourceId = null); - Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name, string? resourceType = null, string? resourceId = null); + Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string permissionName, string? resourceType = null, string? resourceId = null); } diff --git a/src/HelloShop.ServiceDefaults/Authorization/PermissionChecker.cs b/src/HelloShop.ServiceDefaults/Authorization/PermissionChecker.cs index 5d10d6d..54bd9d6 100644 --- a/src/HelloShop.ServiceDefaults/Authorization/PermissionChecker.cs +++ b/src/HelloShop.ServiceDefaults/Authorization/PermissionChecker.cs @@ -9,15 +9,15 @@ public abstract class PermissionChecker(IHttpContextAccessor httpContextAccessor { protected HttpContext HttpContext { get; init; } = httpContextAccessor.HttpContext ?? throw new InvalidOperationException(); - public async Task IsGrantedAsync(string name, string? resourceType = null, string? resourceId = null) => await IsGrantedAsync(HttpContext.User, name, resourceType, resourceId); + public async Task IsGrantedAsync(string permissionName, string? resourceType = null, string? resourceId = null) => await IsGrantedAsync(HttpContext.User, permissionName, resourceType, resourceId); - public async Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string name, string? resourceType = null, string? resourceId = null) + public async Task IsGrantedAsync(ClaimsPrincipal claimsPrincipal, string permissionName, string? resourceType = null, string? resourceId = null) { var roleIds = claimsPrincipal.FindAll(CustomClaimTypes.RoleIdentifier).Select(c => Convert.ToInt32(c.Value)).ToArray(); foreach (var roleId in roleIds) { - var cacheKey = PermissionGrantCacheItem.CreateCacheKey(roleId, name, resourceType, resourceId); + var cacheKey = PermissionGrantCacheItem.CreateCacheKey(roleId, permissionName, resourceType, resourceId); if (distributedCache.TryGetValue(cacheKey, out PermissionGrantCacheItem? cacheItem) && cacheItem != null) { @@ -29,11 +29,11 @@ public abstract class PermissionChecker(IHttpContextAccessor httpContextAccessor continue; } - bool isGranted = await IsGrantedAsync(roleId, name, resourceType, resourceId); + bool isGranted = await IsGrantedAsync(roleId, permissionName, resourceType, resourceId); await distributedCache.SetObjectAsync(cacheKey, new PermissionGrantCacheItem(isGranted), new DistributedCacheEntryOptions { - AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(1) + AbsoluteExpiration = DateTimeOffset.Now }); if (isGranted) @@ -45,5 +45,5 @@ public abstract class PermissionChecker(IHttpContextAccessor httpContextAccessor return false; } - public abstract Task IsGrantedAsync(int roleId, string name, string? resourceType = null, string? resourceId = null); + public abstract Task IsGrantedAsync(int roleId, string permissionName, string? resourceType = null, string? resourceId = null); } diff --git a/src/HelloShop.ServiceDefaults/Authorization/PermissionRequirementHandler.cs b/src/HelloShop.ServiceDefaults/Authorization/PermissionRequirementHandler.cs index f6aaa43..565d56b 100644 --- a/src/HelloShop.ServiceDefaults/Authorization/PermissionRequirementHandler.cs +++ b/src/HelloShop.ServiceDefaults/Authorization/PermissionRequirementHandler.cs @@ -7,7 +7,21 @@ public class PermissionRequirementHandler(IPermissionChecker permissionChecker) { protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement) { - if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name)) + if (context.Resource is IAuthorizationResource resource) + { + if (await permissionChecker.IsGrantedAsync(context.User,requirement.Name, resource.ResourceType, resource.ResourceId)) + { + context.Succeed(requirement); + } + else + { + context.Fail(); + } + + return; + } + + if(await permissionChecker.IsGrantedAsync(context.User,requirement.Name)) { context.Succeed(requirement); return; @@ -16,14 +30,3 @@ public class PermissionRequirementHandler(IPermissionChecker permissionChecker) context.Fail(); } } - -public class ResourcePermissionRequirementHandler(IPermissionChecker permissionChecker) : AuthorizationHandler -{ - protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement, IAuthorizationResource resource) - { - if (await permissionChecker.IsGrantedAsync(context.User, requirement.Name, resource.ResourceType, resource.ResourceId)) - { - context.Succeed(requirement); - } - } -} diff --git a/src/HelloShop.ServiceDefaults/Authorization/RemotePermissionChecker.cs b/src/HelloShop.ServiceDefaults/Authorization/RemotePermissionChecker.cs index aa40391..860a177 100644 --- a/src/HelloShop.ServiceDefaults/Authorization/RemotePermissionChecker.cs +++ b/src/HelloShop.ServiceDefaults/Authorization/RemotePermissionChecker.cs @@ -13,7 +13,7 @@ public class RemotePermissionChecker(IHttpContextAccessor httpContextAccessor, I { private readonly RemotePermissionCheckerOptions _remotePermissionCheckerOptions = options.Value; - public override async Task IsGrantedAsync(int roleId, string name, string? resourceType = null, string? resourceId = null) + public override async Task IsGrantedAsync(int roleId, string permissionName, string? resourceType = null, string? resourceId = null) { string? accessToken = await HttpContext.GetTokenAsync("access_token"); @@ -25,24 +25,17 @@ public class RemotePermissionChecker(IHttpContextAccessor httpContextAccessor, I Dictionary parameters = new() { - { nameof(roleId), roleId.ToString() }, - { nameof(name), name }, + { nameof(permissionName), permissionName }, { nameof(resourceType) , resourceType }, { nameof(resourceId), resourceId } }; string queryString = QueryHelpers.AddQueryString(string.Empty, parameters); - var permissionGrants = httpClient.GetFromJsonAsAsyncEnumerable(queryString); + HttpRequestMessage request = new(HttpMethod.Head, queryString); - await foreach (var permissionGrant in permissionGrants) - { - if (permissionGrant != null && permissionGrant.IsGranted) - { - return true; - } - } + HttpResponseMessage response = await httpClient.SendAsync(request); - return false; + return response.IsSuccessStatusCode; } } diff --git a/src/HelloShop.ServiceDefaults/Authorization/ResourceInfo.cs b/src/HelloShop.ServiceDefaults/Authorization/ResourceInfo.cs index cb94bfb..88b2e2e 100644 --- a/src/HelloShop.ServiceDefaults/Authorization/ResourceInfo.cs +++ b/src/HelloShop.ServiceDefaults/Authorization/ResourceInfo.cs @@ -1,27 +1,7 @@ namespace HelloShop.ServiceDefaults.Authorization; -public class ResourceInfo : IAuthorizationResource +public record struct ResourceInfo(string ResourceType, string ResourceId) : IAuthorizationResource { - public required string ResourceType { get; set; } - - public required string ResourceId { get; set; } - - public static implicit operator string(ResourceInfo resource) => resource.ToString(); - - public static explicit operator ResourceInfo(string resourcePath) - { - string[] separators = resourcePath.Split(":"); - - if (separators == null || separators.Length != 2) - { - throw new ArgumentException("Resource path must be in the format 'type:id'", nameof(resourcePath)); - } - - ResourceInfo resourceInfo = new() { ResourceType = separators.First(), ResourceId = separators.Last() }; - - return resourceInfo; - } - - - public override string ToString() => $"{ResourceType}:{ResourceId}"; + public override readonly string ToString() => $"{ResourceType}:{ResourceId}"; } + diff --git a/src/HelloShop.ServiceDefaults/Extensions/PermissionExtensions.cs b/src/HelloShop.ServiceDefaults/Extensions/PermissionExtensions.cs index e5abec2..55732d8 100644 --- a/src/HelloShop.ServiceDefaults/Extensions/PermissionExtensions.cs +++ b/src/HelloShop.ServiceDefaults/Extensions/PermissionExtensions.cs @@ -78,7 +78,6 @@ public static class PermissionExtensions { services.AddSingleton(); services.AddTransient(); - services.AddTransient(); return services; }