From cb6fa0507acea2890af6eab7ef30378a5e0f60bd Mon Sep 17 00:00:00 2001 From: Tudor Stanciu Date: Sat, 15 Apr 2023 01:17:17 +0300 Subject: [PATCH] Permissions and authorizations at the user role level have been added to the application. --- dependencies.props | 2 +- .../NetworkResurrector.Api.Application.csproj | 1 + .../Queries/GetUserPermissions.cs | 42 ++++++++++ .../DbContexts/NetworkDbContext.cs | 3 + .../DependencyInjectionExtensions.cs | 1 + .../Repositories/SecurityRepository.cs | 80 +++++++++++++++++++ .../Abstractions/IUserService.cs | 10 --- .../Repositories/ISecurityRepository.cs | 10 +++ .../Controllers/SecurityController.cs | 28 +++++++ .../Extensions/StartupExtensions.cs | 5 -- .../NetworkResurrector.Api.csproj | 1 - .../Services/UserService.cs | 46 ----------- 12 files changed, 166 insertions(+), 63 deletions(-) create mode 100644 src/api/NetworkResurrector.Api.Application/Queries/GetUserPermissions.cs create mode 100644 src/api/NetworkResurrector.Api.Domain.Data/Repositories/SecurityRepository.cs delete mode 100644 src/api/NetworkResurrector.Api.Domain/Abstractions/IUserService.cs create mode 100644 src/api/NetworkResurrector.Api.Domain/Repositories/ISecurityRepository.cs create mode 100644 src/api/NetworkResurrector.Api/Controllers/SecurityController.cs delete mode 100644 src/api/NetworkResurrector.Api/Services/UserService.cs diff --git a/dependencies.props b/dependencies.props index 1ac3a3d..fe0615c 100644 --- a/dependencies.props +++ b/dependencies.props @@ -11,7 +11,7 @@ 6.0.30 6.0.1 1.0.7 - 2.2.0 + 2.2.1 1.2.0 1.0.1 diff --git a/src/api/NetworkResurrector.Api.Application/NetworkResurrector.Api.Application.csproj b/src/api/NetworkResurrector.Api.Application/NetworkResurrector.Api.Application.csproj index b687deb..0a0c3a2 100644 --- a/src/api/NetworkResurrector.Api.Application/NetworkResurrector.Api.Application.csproj +++ b/src/api/NetworkResurrector.Api.Application/NetworkResurrector.Api.Application.csproj @@ -11,6 +11,7 @@ + diff --git a/src/api/NetworkResurrector.Api.Application/Queries/GetUserPermissions.cs b/src/api/NetworkResurrector.Api.Application/Queries/GetUserPermissions.cs new file mode 100644 index 0000000..227967b --- /dev/null +++ b/src/api/NetworkResurrector.Api.Application/Queries/GetUserPermissions.cs @@ -0,0 +1,42 @@ +using MediatR; +using Netmash.Security.Authentication.Tuitio.Abstractions; +using NetworkResurrector.Api.Domain.Repositories; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NetworkResurrector.Api.Application.Queries +{ + public class GetUserPermissions + { + public class Query : IRequest { } + + public class Model + { + public IEnumerable Permissions { get; set; } + } + + public class QueryHandler : IRequestHandler + { + private readonly IUserContextAccessor _userContext; + private readonly ISecurityRepository _securityRepository; + + public QueryHandler(IUserContextAccessor userContext, ISecurityRepository securityRepository) + { + _userContext=userContext; + _securityRepository=securityRepository; + } + + public async Task Handle(Query request, CancellationToken cancellationToken) + { + var roles = _userContext.UserRoles.Select(r => r.id); + var permissions = await _securityRepository.GetUserPermissionCodes(roles); + return new Model() + { + Permissions = permissions + }; + } + } + } +} diff --git a/src/api/NetworkResurrector.Api.Domain.Data/DbContexts/NetworkDbContext.cs b/src/api/NetworkResurrector.Api.Domain.Data/DbContexts/NetworkDbContext.cs index 3aec565..d3c4c54 100644 --- a/src/api/NetworkResurrector.Api.Domain.Data/DbContexts/NetworkDbContext.cs +++ b/src/api/NetworkResurrector.Api.Domain.Data/DbContexts/NetworkDbContext.cs @@ -3,6 +3,7 @@ using NetworkResurrector.Api.Domain.Data.EntityTypeConfiguration; using NetworkResurrector.Api.Domain.Data.EntityTypeConfiguration.Power; using NetworkResurrector.Api.Domain.Data.EntityTypeConfiguration.Security; using NetworkResurrector.Api.Domain.Entities; +using NetworkResurrector.Api.Domain.Entities.Security; namespace NetworkResurrector.Api.Domain.Data.DbContexts { @@ -10,6 +11,8 @@ namespace NetworkResurrector.Api.Domain.Data.DbContexts { public DbSet Machines { get; set; } public DbSet PowerActionConfigurations { get; set; } + public DbSet UserRoleAuthorizations { get; set; } + public DbSet PermissionHierarchies { get; set; } public NetworkDbContext(DbContextOptions options) : base(options) diff --git a/src/api/NetworkResurrector.Api.Domain.Data/DependencyInjectionExtensions.cs b/src/api/NetworkResurrector.Api.Domain.Data/DependencyInjectionExtensions.cs index 91c2194..2a653cc 100644 --- a/src/api/NetworkResurrector.Api.Domain.Data/DependencyInjectionExtensions.cs +++ b/src/api/NetworkResurrector.Api.Domain.Data/DependencyInjectionExtensions.cs @@ -12,6 +12,7 @@ namespace NetworkResurrector.Api.Domain.Data public static void AddDataAccess(this IServiceCollection services) { services.AddScoped(); + services.AddScoped(); services .AddDbContextPool( diff --git a/src/api/NetworkResurrector.Api.Domain.Data/Repositories/SecurityRepository.cs b/src/api/NetworkResurrector.Api.Domain.Data/Repositories/SecurityRepository.cs new file mode 100644 index 0000000..a3b65ec --- /dev/null +++ b/src/api/NetworkResurrector.Api.Domain.Data/Repositories/SecurityRepository.cs @@ -0,0 +1,80 @@ +using Microsoft.EntityFrameworkCore; +using NetworkResurrector.Api.Domain.Data.DbContexts; +using NetworkResurrector.Api.Domain.Entities.Security; +using NetworkResurrector.Api.Domain.Repositories; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace NetworkResurrector.Api.Domain.Data.Repositories +{ + public class SecurityRepository : ISecurityRepository + { + private readonly NetworkDbContext _dbContext; + + public SecurityRepository(NetworkDbContext dbContext) + { + _dbContext=dbContext; + } + + private async Task BuildChildrenHierarchy(Permission permission) + { + var children = await _dbContext.PermissionHierarchies + .Include(z => z.Child) + .Where(z => z.ParentPermissionId == permission.PermissionId) + .AsNoTracking() + .ToArrayAsync(); + + if (children == null || !children.Any()) + return; + + permission.Children = children; + + foreach (var element in permission.Children) + { + await BuildChildrenHierarchy(element.Child); + } + } + + private IEnumerable GetPermissionCodes(IEnumerable permissions) + { + var result = new List(); + foreach (var permission in permissions) + { + result.Add(permission.PermissionCode); + if (permission.Children != null && permission.Children.Count > 0) + { + var children = permission.Children.Select(ch => ch.Child); + var childrenCodes = GetPermissionCodes(children); + result.AddRange(childrenCodes); + } + } + return result.Distinct(); + } + + private async Task> GetUserPermissions(IEnumerable roles) + { + var authorizations = await _dbContext.UserRoleAuthorizations + .Include(z => z.Permission) + .AsNoTracking() + .Where(z => roles.Contains(z.UserRoleId) && z.Active) + .ToArrayAsync(); + + var permissions = authorizations.Select(z => z.Permission); + foreach (var permission in permissions) + { + await BuildChildrenHierarchy(permission); + } + + return permissions; + } + + public async Task> GetUserPermissionCodes(IEnumerable roles) + { + var permissions = await GetUserPermissions(roles); + var codes = GetPermissionCodes(permissions); + return codes; + } + } +} diff --git a/src/api/NetworkResurrector.Api.Domain/Abstractions/IUserService.cs b/src/api/NetworkResurrector.Api.Domain/Abstractions/IUserService.cs deleted file mode 100644 index 76e1518..0000000 --- a/src/api/NetworkResurrector.Api.Domain/Abstractions/IUserService.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NetworkResurrector.Api.Domain.Abstractions -{ - public interface IUserService - { - bool UserIsLoggedIn { get; } - string GetUserId(); - string GetUserName(); - bool UserIsGuest(); - } -} diff --git a/src/api/NetworkResurrector.Api.Domain/Repositories/ISecurityRepository.cs b/src/api/NetworkResurrector.Api.Domain/Repositories/ISecurityRepository.cs new file mode 100644 index 0000000..20e73cf --- /dev/null +++ b/src/api/NetworkResurrector.Api.Domain/Repositories/ISecurityRepository.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace NetworkResurrector.Api.Domain.Repositories +{ + public interface ISecurityRepository + { + Task> GetUserPermissionCodes(IEnumerable roles); + } +} diff --git a/src/api/NetworkResurrector.Api/Controllers/SecurityController.cs b/src/api/NetworkResurrector.Api/Controllers/SecurityController.cs new file mode 100644 index 0000000..d66cc76 --- /dev/null +++ b/src/api/NetworkResurrector.Api/Controllers/SecurityController.cs @@ -0,0 +1,28 @@ +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using NetworkResurrector.Api.Application.Queries; +using System.Threading.Tasks; + +namespace NetworkResurrector.Api.Controllers +{ + [Authorize] + [ApiController] + [Route("security")] + public class SecurityController : ControllerBase + { + private readonly IMediator _mediator; + + public SecurityController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet("permissions")] + public async Task GetUserPermissions([FromRoute] GetUserPermissions.Query query) + { + var result = await _mediator.Send(query); + return Ok(result); + } + } +} diff --git a/src/api/NetworkResurrector.Api/Extensions/StartupExtensions.cs b/src/api/NetworkResurrector.Api/Extensions/StartupExtensions.cs index 270b672..3ce270f 100644 --- a/src/api/NetworkResurrector.Api/Extensions/StartupExtensions.cs +++ b/src/api/NetworkResurrector.Api/Extensions/StartupExtensions.cs @@ -10,9 +10,7 @@ using Netmash.Infrastructure.DatabaseMigration.Constants; using Netmash.Security.Authentication.Tuitio; using NetworkResurrector.Agent.Wrapper; using NetworkResurrector.Api.Application; -using NetworkResurrector.Api.Domain.Abstractions; using NetworkResurrector.Api.Domain.Data; -using NetworkResurrector.Api.Services; using NetworkResurrector.Server.Wrapper; using Newtonsoft.Json; @@ -28,9 +26,6 @@ namespace NetworkResurrector.Api.Extensions // Add basic authentication services.AddTuitioAuthentication(configuration.GetSection("Tuitio")["BaseAddress"]); - services.AddHttpContextAccessor(); - services.AddScoped(); - // MediatR services.AddMediatR(typeof(Application.Queries.GetMachines).Assembly); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>)); diff --git a/src/api/NetworkResurrector.Api/NetworkResurrector.Api.csproj b/src/api/NetworkResurrector.Api/NetworkResurrector.Api.csproj index 747ee22..efa7bd8 100644 --- a/src/api/NetworkResurrector.Api/NetworkResurrector.Api.csproj +++ b/src/api/NetworkResurrector.Api/NetworkResurrector.Api.csproj @@ -20,7 +20,6 @@ - diff --git a/src/api/NetworkResurrector.Api/Services/UserService.cs b/src/api/NetworkResurrector.Api/Services/UserService.cs deleted file mode 100644 index b573afe..0000000 --- a/src/api/NetworkResurrector.Api/Services/UserService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Http; -using NetworkResurrector.Api.Domain.Abstractions; -using System; -using System.Linq; -using System.Security.Claims; - -namespace NetworkResurrector.Api.Services -{ - public class UserService : IUserService - { - private readonly IHttpContextAccessor _httpAccessor; - - public UserService(IHttpContextAccessor httpAccessor) - { - _httpAccessor = httpAccessor; - } - - public bool UserIsLoggedIn => _httpAccessor.HttpContext.User != null; - - public string GetUserId() - { - var userId = _httpAccessor.HttpContext.User?.Claims.FirstOrDefault(z => z.Type == ClaimTypes.NameIdentifier)?.Value; - - if (string.IsNullOrEmpty(userId)) - throw new Exception("User id could not be retrieved from claims."); - - return userId; - } - - public string GetUserName() - { - var userName = _httpAccessor.HttpContext.User?.Claims.FirstOrDefault(z => z.Type == ClaimTypes.Name)?.Value; - - if (string.IsNullOrEmpty(userName)) - throw new Exception("User name could not be retrieved from claims."); - - return userName; - } - - public bool UserIsGuest() - { - var userIsGuest = _httpAccessor.HttpContext.User?.Claims.FirstOrDefault(z => z.Type == Netmash.Security.Authentication.Tuitio.Constants.ClaimTypes.IsGuestUser)?.Value; - return !string.IsNullOrEmpty(userIsGuest) && bool.TrueString == userIsGuest; - } - } -}