From 437b1a4e7ce020188e6910077454208dfe8d7c67 Mon Sep 17 00:00:00 2001 From: Mikhail Trubnikov Date: Wed, 6 Dec 2023 10:20:20 +1000 Subject: [PATCH] =?UTF-8?q?feat(api):=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=80=D0=BE=D0=BB=D1=8F=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.API/Contracts/V1/ApiRoutes.cs | 10 ++ .../V1/Requests/UserAddRoleRequest.cs | 7 + PARR.API/Controllers/V1/UserRoleController.cs | 148 ++++++++++++++++++ .../Services/Implementations/AuthService.cs | 13 ++ PARR.API/Services/Interfaces/IAuthService.cs | 14 ++ PARR.DAL/CacheServices/IRedisCacheService.cs | 40 +++++ PARR.DAL/CacheServices/RedisCacheService.cs | 12 +- 7 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 PARR.API/Contracts/V1/Requests/UserAddRoleRequest.cs create mode 100644 PARR.API/Controllers/V1/UserRoleController.cs diff --git a/PARR.API/Contracts/V1/ApiRoutes.cs b/PARR.API/Contracts/V1/ApiRoutes.cs index 802c354d..aa1f957a 100644 --- a/PARR.API/Contracts/V1/ApiRoutes.cs +++ b/PARR.API/Contracts/V1/ApiRoutes.cs @@ -136,6 +136,16 @@ namespace PARR.API.Contracts.V1 public const string getParam = "{id}"; } + public static class UserRoles + { + public const string Get = Base + "/users/" + userParam + "/roles/"; + public const string AddRole = Base + "/users/" + userParam + "/roles/"; + public const string DeleteRole = Base + "/users/" + userParam + "/roles/" + roleParam; + + public const string userParam = "{userId}"; + public const string roleParam = "{roleId}"; + } + public static class Role { public const string GetAll = Base + "/roles/"; diff --git a/PARR.API/Contracts/V1/Requests/UserAddRoleRequest.cs b/PARR.API/Contracts/V1/Requests/UserAddRoleRequest.cs new file mode 100644 index 00000000..8d47b36f --- /dev/null +++ b/PARR.API/Contracts/V1/Requests/UserAddRoleRequest.cs @@ -0,0 +1,7 @@ +namespace PARR.API.Contracts.V1.Requests +{ + public class UserAddRoleRequest + { + public Guid RoleId { get; set; } + } +} diff --git a/PARR.API/Controllers/V1/UserRoleController.cs b/PARR.API/Controllers/V1/UserRoleController.cs new file mode 100644 index 00000000..a8355b0f --- /dev/null +++ b/PARR.API/Controllers/V1/UserRoleController.cs @@ -0,0 +1,148 @@ +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using PARR.API.Contracts.V1; +using PARR.API.Contracts.V1.Requests; +using PARR.API.Contracts.V1.Responses; +using PARR.API.Contracts.V1.Responses.Base; +using PARR.API.Controllers.V1.Base; +using PARR.API.Services.Interfaces; +using PARR.Constants; +using PARR.DAL.Models; +using PARR.DAL.Services.Interfaces; + +namespace PARR.API.Controllers.V1 +{ + /// + /// Управление ролями пользователя + /// + [Authorize(Roles = ParrRoles.Administrator.Role)] + public class UserRoleController : BaseApiController + { + private readonly IUserService userService; + private readonly IMapper mapper; + private readonly ILogger logger; + private readonly IRoleService roleService; + private readonly IAuthService authService; + private readonly IUriService uriService; + + public UserRoleController( + IUserService userService, + IMapper mapper, + ILogger logger, + IRoleService roleService, + IAuthService authService, + IUriService uriService + ) + { + this.userService = userService; + this.mapper = mapper; + this.logger = logger; + this.roleService = roleService; + this.authService = authService; + this.uriService = uriService; + } + + + /// + /// Роли пользователя + /// + /// + /// + [HttpGet(ApiRoutes.UserRoles.Get)] + public async Task Get([FromRoute] Guid userId) + { + var user = await userService.Get() + .Include(t => t.Roles).ThenInclude(t => t.Role) + .FirstOrDefaultAsync(t => t.Id == userId); + + if (user == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Не найден пользователь с id: {userId}" } })); + + var response = mapper.Map>(user.Roles.Select(t => t.Role)).OrderBy(t => t.Description).ToList(); + + return Ok(new Response>(response, true)); + } + + + /// + /// Добавить роль пользователю + /// + /// + /// + [HttpPost(ApiRoutes.UserRoles.AddRole)] + public async Task AddRole([FromRoute] Guid userId, [FromBody] UserAddRoleRequest request) + { + var user = await userService.Get() + .Include(t => t.Roles) + .FirstOrDefaultAsync(t => t.Id == userId); + + if (user == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Не найден пользователь с id: {userId}" } })); + + if (user.Roles.FirstOrDefault(t => t.RoleId == request.RoleId) != null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"У пользователя {userId} уже есть роль {request.RoleId}" } })); + + var newRole = await roleService.GetAsync(request.RoleId); + if (newRole == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Не найдена роль с id: {request.RoleId}" } })); + + // добавляем роль + user.Roles.Add(new UsersInRole { RoleId = newRole.Id }); + if (!await userService.CommitAsync()) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при добавлении роли {request.RoleId} пользователю {userId}" } })); + + logger.LogInformation($"Пользователь {User.Identity?.Name} добвавил роль {newRole.Name} пользователю {userId}, {user.Ip}, {user.Name}"); + + // удалить из кэша + await authService.RemoveCacheUserAsync(user.Ip); + + + var updatedUser = await userService.Get() + .Include(t => t.Roles).ThenInclude(t => t.Role) + .FirstAsync(t => t.Id == userId); + + var response = mapper.Map>(updatedUser.Roles.Select(t => t.Role)).OrderBy(t => t.Description).ToList(); + var locationUri = uriService.GetUri(ApiRoutes.UserRoles.Get, ApiRoutes.UserRoles.userParam, userId); + + return Created(locationUri, new Response>(response, true)); + } + + + /// + /// Удалить роль у пользователя + /// + /// + /// + /// + [HttpDelete(ApiRoutes.UserRoles.DeleteRole)] + public async Task DeleteRole([FromRoute] Guid userId, [FromRoute] Guid roleId) + { + var user = await userService.Get() + .Include(t => t.Roles).ThenInclude(t => t.Role) + .FirstOrDefaultAsync(t => t.Id == userId); + + if (user == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Не найден пользователь с id: {userId}" } })); + + var userRole = user.Roles.FirstOrDefault(t => t.RoleId == roleId); + + if (userRole == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"У пользователя {userId} нет роли с id {roleId}" } })); + + user.Roles.Remove(userRole); + + if (!await userService.CommitAsync()) + return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении роли {roleId} у пользователя {userId}" } })); + + logger.LogInformation($"Пользователь {User.Identity?.Name} удалил роль {userRole.Role.Name} у пользователя {userId}, {user.Ip}, {user.Name}"); + + // удалить из кэша + await authService.RemoveCacheUserAsync(user.Ip); + + return NoContent(); + } + + } +} diff --git a/PARR.API/Services/Implementations/AuthService.cs b/PARR.API/Services/Implementations/AuthService.cs index 901e62be..da31cb08 100644 --- a/PARR.API/Services/Implementations/AuthService.cs +++ b/PARR.API/Services/Implementations/AuthService.cs @@ -85,6 +85,19 @@ namespace PARR.API.Services.Implementations } + public async Task UnlockUserAsync(string ipAddress) + { + await cacheService.DeleteCachedDataAsync(GetBlockKey(ipAddress)); + } + + + public async Task RemoveCacheUserAsync(string ipAddress) + { + await cacheService.DeleteCachedDataAsync(GetAllowKey(ipAddress)); + await cacheService.DeleteCachedDataAsync(GetBlockKey(ipAddress)); + } + + private string GetBlockKey(string ipAddress) { // d - deny diff --git a/PARR.API/Services/Interfaces/IAuthService.cs b/PARR.API/Services/Interfaces/IAuthService.cs index 161133d9..d126cda9 100644 --- a/PARR.API/Services/Interfaces/IAuthService.cs +++ b/PARR.API/Services/Interfaces/IAuthService.cs @@ -18,5 +18,19 @@ namespace PARR.API.Services.Interfaces /// /// Task UserIsBlockedAsync(string ipAddress); + + /// + /// Удалить из кэша пользователя + /// + /// + /// + Task RemoveCacheUserAsync(string ipAddress); + + /// + /// Разблокировать пользователя + /// + /// + /// + Task UnlockUserAsync(string ipAddress); } } diff --git a/PARR.DAL/CacheServices/IRedisCacheService.cs b/PARR.DAL/CacheServices/IRedisCacheService.cs index cc92cc52..4176205f 100644 --- a/PARR.DAL/CacheServices/IRedisCacheService.cs +++ b/PARR.DAL/CacheServices/IRedisCacheService.cs @@ -2,12 +2,52 @@ { public interface IRedisCacheService { + /// + /// Получить кэшированные данные + /// + /// + /// + /// T? GetCachedData(string key); + /// + /// Получить кэшированные данные асинхронно + /// + /// + /// + /// Task GetCachedDataAsync(string key); + /// + /// Добавить в кэш данные + /// + /// + /// + /// + /// void SetCachedData(string key, T data, TimeSpan cacheDuration); + /// + /// Добавить в кэш данные асинхронно + /// + /// + /// + /// + /// + /// Task SetCachedDataAsync(string key, T data, TimeSpan cacheDuration); + + /// + /// Удалить кэшированные данные + /// + /// + void DeleteCachedData(string key); + + /// + /// Удалить кэшированные данные асинхронно + /// + /// + /// + Task DeleteCachedDataAsync(string key); } } diff --git a/PARR.DAL/CacheServices/RedisCacheService.cs b/PARR.DAL/CacheServices/RedisCacheService.cs index abf94815..352462d1 100644 --- a/PARR.DAL/CacheServices/RedisCacheService.cs +++ b/PARR.DAL/CacheServices/RedisCacheService.cs @@ -15,7 +15,7 @@ namespace PARR.DAL.CacheServices public async Task GetCachedDataAsync(string key) { var jsonData = await cache.GetStringAsync(key); - + if (jsonData == null) return default(T); @@ -58,6 +58,16 @@ namespace PARR.DAL.CacheServices } + public async Task DeleteCachedDataAsync(string key) + { + await cache.RemoveAsync(key); + } + + + public void DeleteCachedData(string key) + { + cache.Remove(key); + } } }