Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop

This commit is contained in:
jrojas
2026-06-26 09:52:35 +02:00
commit 319fd3dfb0
746 changed files with 106610 additions and 0 deletions
@@ -0,0 +1,28 @@
using System.Security.Claims;
using adas_core.Domain.Enums;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace adas_core.Authentication.Attributes;
[AttributeUsage(AttributeTargets.Method)]
public class AuthorizeRolesAttribute(PermissionEnum.RolesType type) : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (user.Identity?.IsAuthenticated != true)
{
context.Result = new UnauthorizedResult();
return;
}
var roles = user.Claims.Where(c => c.Type == ClaimTypes.Role);
if (type == PermissionEnum.RolesType.AuthSome) return;
var hasRequiredRole = roles.Any(r => r.Value.StartsWith(type.ToString()));
if (!hasRequiredRole) context.Result = new ForbidResult();
}
}
@@ -0,0 +1,29 @@
using adas_core.Authentication.Interfaces;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace adas_core.Authentication.Attributes;
public class AuthorizePermissionsAttribute(
IUserService userService)
: Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
var userName = user.Identity?.Name ?? string.Empty;
var displayIdHeader = context.HttpContext.Request.Headers["Displayid"];
if (string.IsNullOrEmpty(displayIdHeader.ToString()))
{
context.Result = new NotFoundObjectResult("displayIdHeader not found reference id doesn't exist on header");
return;
}
var userDao = userService.GetUserByUserName(userName).Result;
if (userDao == null)
context.Result = new NotFoundObjectResult("User not found on AuthorizePermissionsAttribute");
}
}
@@ -0,0 +1,98 @@
using System.Security.Claims;
using adas_core.Application.Exceptions;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace adas_core.Authentication.Attributes;
public class PermissionAuthorizeAttribute(string source, string? resourceIdHeader = null)
: AuthorizeAttribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
// Obtener el servicio de permisos.
var permissionService = context.HttpContext.RequestServices.GetService<IPermissionService>()
?? throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
var user = context.HttpContext.User;
// Obtener el header que indica la fuente.
var headerSource = context.HttpContext.Request.Headers[source].FirstOrDefault();
// Obtener el identificador del usuario
var username = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
// Obtiene la lista de roles (valores) del usuario.
var userRoles = user.Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value)
.ToList();
// Procesa cada rol: si contiene 2 guiones bajos, elimina el tercer segmento.
var processedRoles = userRoles.Select(role =>
{
var parts = role.Split('_');
return parts.Length == 2 ? parts[0] : role;
}).ToList();
// Validar que se tenga al menos un rol, username y headerSource.
if (!processedRoles.Any() || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(headerSource))
{
context.Result = new ForbidResult();
return;
}
// Parseamos el header para obtener la fuente.
var source1 =
(PermissionEnum.SourcePermissionsEnum)Enum.Parse(typeof(PermissionEnum.SourcePermissionsEnum),
headerSource);
var hasPermission = false;
if (source1 == PermissionEnum.SourcePermissionsEnum.Panel)
{
// Si la fuente es PANEL, verificamos que alguno de los roles tenga acceso.
foreach (var roleStr in processedRoles)
{
if (!Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj)) continue;
var role = (PermissionEnum.RolesType)roleObj;
if (!await permissionService.HasAccessToPanel(username, role, source1)) continue;
hasPermission = true;
break;
}
if (!hasPermission) context.Result = new ForbidResult();
}
else if (resourceIdHeader != null)
{
// Para otras fuentes, se requiere un header que indique el resourceId.
var resourceId = context.HttpContext.Request.Headers[resourceIdHeader].FirstOrDefault();
if (string.IsNullOrEmpty(resourceId))
{
context.Result = new ForbidResult();
return;
}
// Verificar que al menos uno de los roles tenga acceso al recurso.
foreach (var roleStr in processedRoles)
if (Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj))
{
var role = (PermissionEnum.RolesType)roleObj;
hasPermission = resourceIdHeader switch
{
"DisplayId" => await permissionService.HasAccessToDisplay(username, role, source1, resourceId),
"UnitId" => await permissionService.HasAccessToUnit(username, role, source1, resourceId),
_ => false
};
if (hasPermission)
break;
}
if (!hasPermission) context.Result = new ForbidResult();
}
}
}
@@ -0,0 +1,108 @@
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Authentication.Interfaces;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using MongoDB.Bson;
using Serilog;
namespace adas_core.Authentication;
public class AuthorityService(
IAuthorityRepository authorityRepository,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService)
: IAuthorityService
{
public void CreateNew(string roleName, ObjectId userId)
{
authorityRepository.CreateNewAuthority(roleName, userId);
}
public async Task InsertOne(Authorization authorization)
{
await authorityRepository.InsertOneAsync(authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, authorization);
}
public async Task updateOne(Authorization authorization)
{
var oldAuth = await authorityRepository.GetById(authorization.Id);
await authorityRepository.UpdateOneAsync(authorization.Id, authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAuth, authorization);
}
/// <summary>
/// Retrieves the authorities of the specified user.
/// </summary>
/// <param name="userId"></param>
/// <returns>A list of the user's authorities.</returns>
public Task<List<Authorization>> GetUserAuthorities(ObjectId userId)
{
return authorityRepository.GetUserAuthorities(userId);
}
public Task<List<Authorization>> GetAllAuthorities()
{
return authorityRepository.GetAllAuthorities();
}
public async Task<bool> DeleteAuthoritiesForUser(ObjectId id)
{
try
{
var list = await authorityRepository.GetUserAuthorities(id);
var result = await authorityRepository.DeleteAllAuthoritiesByUser(id);
if (!result) return result;
foreach (var auth in list)
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, auth, null);
return result;
}
catch (Exception e)
{
Log.Error("Exception deleting authorities for user {id}. Exception: {e}", id, e.Message);
return false;
}
}
public async Task<Authorization?> CreateNewUserAuthority(Authorization authorization)
{
await authorityRepository.InsertOneAsync(authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, authorization);
return authorization;
}
public async Task<bool> DeleteUserAuthority(ObjectId userAuthorityIdParsed)
{
try
{
var result = await authorityRepository.DeleteAsync(userAuthorityIdParsed);
if (result != null)
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, result, null);
return result != null;
}
catch (Exception e)
{
Log.Error("Exception deleting user authority {userAuthorityIdParsed}. Exception: {e}",
userAuthorityIdParsed, e.Message);
return false;
}
}
public async Task<Authorization?> EditUserAuthority(Authorization authorization)
{
try
{
var oldAuth = await authorityRepository.GetById(authorization.Id);
await authorityRepository.UpdateOneAsync(authorization.Id, authorization);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAuth, authorization);
return authorization;
}
catch (Exception e)
{
Log.Error("Exception editing user authority {authorization}. Exception: {e}", authorization, e.Message);
return null;
}
}
}
@@ -0,0 +1,19 @@
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Authentication.Interfaces;
public interface IAuthorityService
{
public Task<List<Authorization>> GetUserAuthorities(ObjectId userId);
public void CreateNew(string roleName, ObjectId userId);
public Task InsertOne(Authorization authorization);
public Task updateOne(Authorization authorization);
public Task<List<Authorization>> GetAllAuthorities();
Task<bool> DeleteAuthoritiesForUser(ObjectId id);
Task<Authorization?> CreateNewUserAuthority(Authorization authorization);
Task<bool> DeleteUserAuthority(ObjectId userAuthorityIdParsed);
Task<Authorization?> EditUserAuthority(Authorization authorization);
}
@@ -0,0 +1,21 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using MongoDB.Bson;
namespace adas_core.Authentication.Interfaces;
public interface ILoginService
{
bool AllowPassword { get; }
UserEnum.LoginMethod Method { get; }
Task<User> Login(string username, string password);
Task<User> Login(HttpContext context);
Task<User> Authenticate(string username, string password);
Task<User?> GetById(ObjectId id);
Task<User?> GetByEmail(string email);
Task<User?> GetByUsername(string username);
Task<List<User>> GetAllUsers();
}
@@ -0,0 +1,14 @@
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
namespace adas_core.Authentication.Interfaces;
public interface ITokenService
{
int RefreshTokenValidityInDays { get; }
SecurityToken? GetToken(string username, List<Claim> claims);
string GetRefreshToken();
string Serialize(SecurityToken token);
}
@@ -0,0 +1,43 @@
using System.IdentityModel.Tokens.Jwt;
using adas_core.Authentication.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Bson;
namespace adas_core.Authentication.Interfaces;
public interface IUserService
{
static readonly string TokenTypeUser = "user";
static readonly string TokenTypeRefresh = "refresh";
Task<TokenResult> Login(string username, string password);
Task<User> GetUser(string username, string password);
Task<TokenResult> RefreshToken(string refreshToken);
Task<TokenResult> LoginWithAccessToken(string token);
void ValidateToken(string token, string tokenType, out SecurityToken securityToken);
Task<User?> GetUserByToken(JwtSecurityToken? jwtToken);
Task<User?> GetUserByCasTicket(string service, string ticket);
Task<TokenResult> GenerateJwt(User user);
Task<List<User>> GetAll();
Task<User?> GetUserById(ObjectId id);
Task<User?> GetUserByUserName(string name);
Task<User?> GetUserByName(string name);
Task<User?> CreateUser(User userEntryLdap);
Task<User?> CreateNewUserByRequest(User user);
Task<User?> CreateNewUserWithAuthorities(CreateUserWithAuthDto createUserWithAuthDto);
Task<User?> UpdateUserWithAuthorities(UpdateUserWithAuthDto createUserWithAuthDto, bool updatePass);
Task<User?> UpdateUsersByRequest(User user, bool updatePass);
Task<bool> UpdateUserPassword(ObjectId id, string oldPassword, string newPassword);
Task<bool> DeleteUser(ObjectId id);
Task<PaginationResponse<User>> GetPaginatedUsers(PaginationFilter config);
Task<Authorization> CreateNewAuthority(Authorization auth);
Task<bool> DeleteAuthority(string id);
Task<bool> UpdateAuthority(Authorization authorization);
Task<TokenResult> LoginWithGivenAccessToken(string token);
}
@@ -0,0 +1,7 @@
namespace adas_core.Authentication.Models;
public class LoginInfo
{
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
}
+19
View File
@@ -0,0 +1,19 @@
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Authentication.Models;
public class TokenResult
{
public string AccessToken { get; set; } = null!;
public DateTime AccessTokenExpiryTime { get; set; }
public string RefreshToken { get; set; } = null!;
public DateTime RefreshTokenExpiryTime { get; set; }
public User User { get; set; } = new();
}
public class TokenResultPanel
{
public TokenResult TokenResult { get; set; } = null!;
public PanelPermissionTypes Permissions { get; set; } = null!;
}
@@ -0,0 +1,47 @@
namespace adas_core.Authentication.Models;
public class UciResponse<T>
{
protected UciResponse()
{
}
protected UciResponse(T entity)
{
Success = true;
Data = entity;
Message = null;
Error = null;
}
public string? Error { get; set; }
public DateTime Date { get; set; } = DateTime.Now;
public bool Success { get; protected set; }
public string? Message { get; protected set; }
public T? Data { get; protected set; }
public static UciResponse<T> FromSuccess(T entity)
{
return new UciResponse<T>(entity);
}
public static UciResponse<T> FromError(Exception ex)
{
var error = ex.GetType().Name;
var index = error.LastIndexOf("Exception", StringComparison.Ordinal);
if (index > -1) error = error[..index];
return FromError(error, ex.Message);
}
public static UciResponse<T> FromError(string error, string? message = null)
{
return new UciResponse<T>
{
Success = false,
Error = error,
Message = message
};
}
}
@@ -0,0 +1,14 @@
using adas_core.Authentication.Interfaces;
using Microsoft.Extensions.DependencyInjection;
namespace adas_core.Authentication.RegistrationExtensions;
public static class AuthRegistration
{
public static void AddAuth(this IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<IUserService, UserService>();
serviceCollection.AddSingleton<Lazy<IUserService>>(provider =>
new Lazy<IUserService>(provider.GetRequiredService<IUserService>));
}
}
+647
View File
@@ -0,0 +1,647 @@
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Authentication.Interfaces;
using adas_core.Authentication.Models;
using adas_core.Domain.Enums;
using adas_core.Domain.Exceptions;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Bson;
using MongoDB.Driver;
using static System.Enum;
namespace adas_core.Authentication;
public class UserService : IUserService
{
private readonly ILocalAuditService _auditService;
private readonly IAuthorityService _authorityService;
private readonly IEnumerable<ILoginService> _availableLoginServices;
private readonly Lazy<IDisplayService> _displayService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly JwtConfig _jwtConfig;
private readonly ILogger<UserService> _logger;
private readonly IUserRepository _userRepository;
private readonly UsersWhiteListConfig _usersWhiteList;
private readonly ValidGroupsConfig _validGroups;
private readonly List<string> _validLoginMethods;
private readonly ISubscribersService _subscribersService;
private readonly IClientMessageService _clientMessageService;
private readonly Lazy<IPermissionService> _permissionService;
public UserService(
IEnumerable<ILoginService> loginServices,
IOptions<AuthSettings> configuration,
IOptions<ValidGroupsConfig> validGroups,
IOptions<AuthSettings> usersWhiteList,
IOptions<AuthSettings> jwt,
IHttpContextAccessor httpContextAccessor,
IUserRepository userRepository,
IAuthorityService authorityService,
ILocalAuditService auditService,
Lazy<IDisplayService> displayService,
ILogger<UserService> logger,
ISubscribersService subscribersService,
IClientMessageService clientMessageService, Lazy<IPermissionService> permissionService)
{
_availableLoginServices = loginServices;
_httpContextAccessor = httpContextAccessor;
_validGroups = validGroups.Value;
_usersWhiteList = usersWhiteList.Value.UsersWhiteListConfig;
_jwtConfig = jwt.Value.JwtConfig ?? throw new Exception("JWT NOT CONFIGURED");
var loginMethods = configuration.Value.LoginMethods;
_validLoginMethods = loginMethods;
_userRepository = userRepository;
_authorityService = authorityService;
_auditService = auditService;
_displayService = displayService;
_logger = logger;
_subscribersService = subscribersService;
_clientMessageService = clientMessageService;
_permissionService = permissionService;
}
public async Task<TokenResult> Login(string username, string password)
{
var user = await GetUser(username, password);
_logger.LogInformation("[UserService] Login user {u}", user.ToString());
if (user == null) throw new UnauthorizedException(HttpEnum.ErrorMessage.UnauthorizedInvalidCredentials);
if (!user.IsEnabled || (user.LockExpirationDate != null && user.LockExpirationDate > DateTime.UtcNow))
throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenUserBlocked);
if (!_usersWhiteList.IsValid(user) && !_validGroups.IsValid(user))
throw new UnauthorizedException(HttpEnum.ErrorMessage.UnauthorizedNoPermission);
if (user.LockExpirationDate < DateTime.UtcNow) user.LockExpirationDate = null;
user.LastLogin = DateTime.UtcNow;
await UpdateUsersByRequest(user, false);
return await GenerateJwt(user);
}
public void ValidateToken(string token, string tokenType, out SecurityToken validatedToken)
{
try
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = _jwtConfig.ValidateAudience,
ValidateIssuer = _jwtConfig.ValidateIssuer,
ValidAudience = _jwtConfig.Audience,
ValidIssuer = _jwtConfig.Issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.Key ?? string.Empty)),
ValidateLifetime = false
};
var tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
if (securityToken is not JwtSecurityToken jwtSecurityToken ||
!jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256,
StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
validatedToken = securityToken;
}
catch (SecurityTokenException a)
{
_logger.LogError("Error validating token, {msg}", a.Message);
throw new TokenException(a.Message);
}
catch (Exception e)
{
_logger.LogError("Error validating token, {msg}", e.Message);
throw new TokenException("Error validating token");
}
}
public async Task<User?> GetUserByToken(JwtSecurityToken? jwtToken)
{
User? user = null;
if (jwtToken == null)
return user;
var userName = jwtToken.Claims.First(x => x.Type == ClaimTypes.Name).Value;
user = await _userRepository.GetByUserName(userName);
if (user == null)
return user;
//Eliminamos la contraseña
user.Password = string.Empty;
if (user.Authorization == null || user.Authorization.Count == 0)
user.Authorization = await _authorityService.GetUserAuthorities(user.Id);
return user;
}
public async Task<User?> GetUserByCasTicket(string serviceUrl, string ticket)
{
var service = _availableLoginServices.FirstOrDefault(s => s.Method == UserEnum.LoginMethod.Cas);
if (service == null) throw new LoginServicesException("CAS login method not available");
try
{
var user = await service.Login(_httpContextAccessor.HttpContext!);
if (!_usersWhiteList.IsValid(user) && !_validGroups.IsValid(user))
throw new UnauthorizedAccessException("You don't have sufficient permissions");
return user;
}
catch (BusinessException)
{
throw;
}
catch (UnauthorizedAccessException)
{
throw;
}
catch (Exception e)
{
var message = e.Message;
if (e.InnerException != null && e.Message != e.InnerException?.Message)
message += $", {e.InnerException?.Message}";
Debug.WriteLine($"Error while login: {message} ");
throw new LoginServicesException(message, e);
}
////////////////////////
/*
if (!_validLoginMethods.Contains(LoginMethod.CAS.ToString()))
{
Log.Warning("CAS login method not enabled");
return null;
}
_casLoginService = new
Log.Information("CAS ticket received: '{CasTicket}'", ticket);
return string.IsNullOrEmpty(ticket) ? null : _casLoginService.GetUser(serviceUrl, ticket, context);
*/
}
public async Task<TokenResult> RefreshToken(string refreshToken)
{
ValidateToken(refreshToken, IUserService.TokenTypeRefresh, out var validatedToken);
var user = await GetUserByToken((JwtSecurityToken)validatedToken);
if (user == null)
return new TokenResult();
return await GenerateJwt(user);
}
public async Task<User> GetUser(string username, string password)
{
//BusinessException? loginException = null;
foreach (var m in _validLoginMethods)
{
if (!TryParse(m, out UserEnum.LoginMethod method)) continue;
var service = _availableLoginServices.FirstOrDefault(s => s.Method == method && s.AllowPassword);
if (service == null) continue;
try
{
_logger.LogInformation("[UserService] Trying login with method {method}", method);
return await service.Login(username, password);
}
catch (Exception e)
{
_logger.LogError("[UserService] Error while trying method {method}, err {err}", method, e.Message);
}
}
throw new LoginServicesNotFoundException();
}
public async Task<User?> GetUserById(ObjectId id)
{
try
{
var u = await _userRepository.GetById(id);
if (u != null) u.Authorization = await _authorityService.GetUserAuthorities(u.Id);
return u;
}
catch (Exception)
{
return null;
}
}
public async Task<User?> GetUserByUserName(string name)
{
var u = await _userRepository.GetByUserName(name);
if (u == null) return u;
u.Authorization = await _authorityService.GetUserAuthorities(u.Id);
return u;
}
public async Task<User?> GetUserByName(string name)
{
try
{
var u = await _userRepository.GetByName(name);
if (u != null) u.Authorization = await _authorityService.GetUserAuthorities(u.Id);
return u;
}
catch (Exception)
{
return null;
}
}
public async Task<User?> CreateUser(User userEntryLdap)
{
CheckIfUserNameExists(userEntryLdap.UserName);
CheckIfEmailExists(userEntryLdap.Email);
await _userRepository.InsertOneAsync(userEntryLdap);
var result = await GetUserByUserName(userEntryLdap.UserName);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User, null, result);
return result;
}
public async Task<User?> CreateNewUserByRequest(User user)
{
CheckIfEmailExists(user.Email);
CheckIfUserNameExists(user.UserName);
if (user.Type == UserEnum.Type.Local)
{
if (!CryptoAdas.IsStrongPassword(user.Password))
throw new UnprocessableEntityException(HttpEnum.ErrorMessage.UnprocessableEntityInvalidPassword);
user.Password = CryptoAdas.CreateBCrypt(user.Password);
}
await _userRepository.InsertOneAsync(user);
var result = await GetUserByUserName(user.UserName) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User, null, result);
return result;
}
public async Task<User?> CreateNewUserWithAuthorities(CreateUserWithAuthDto createUserWithAuthDto)
{
var user = createUserWithAuthDto.User;
var newUser = await CreateNewUserByRequest(user) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var auths = createUserWithAuthDto.Authorizations;
foreach (var auth in auths)
{
auth.UserId = newUser.Id;
await CreateNewAuthority(auth);
}
return newUser;
}
public async Task<User?> UpdateUsersByRequest(User user, bool updatePass)
{
var oldUser = await _userRepository.GetByUserName(user.UserName);
var logoutHasBeenSent = false;
if (updatePass && !CryptoAdas.IsStrongPassword(user.Password))
throw new UnprocessableEntityException(HttpEnum.ErrorMessage.UnprocessableEntityInvalidPassword);
if (updatePass)
{
user.Password = CryptoAdas.CreateBCrypt(user.Password);
await SendBroadcastUser(user, user.UserName, OperationType.UpdatePassword);
logoutHasBeenSent = true;
}
var result = await _userRepository.UpdateUser(user, updatePass);
if(!logoutHasBeenSent && oldUser != null && result != null && oldUser.IsEnabled && !result.IsEnabled)
{
await SendBroadcastUser(result, user.UserName, OperationType.DisableUser);
logoutHasBeenSent = true;
}
if(!logoutHasBeenSent && result != null && result.LockExpirationDate != null && result.LockExpirationDate>DateTime.UtcNow) await SendBroadcastUser(result, user.UserName, OperationType.LockUser);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User, oldUser, result);
return result;
}
private async Task SendBroadcastUser(object elementUpdated, string userName, OperationType operation)
{
var subscribers = _subscribersService.GetSubscribers().Where(s =>
s.UserName == userName).ToList();
foreach (var subscriber in subscribers)
{
await _clientMessageService.SendAsync(subscriber.Id, operation, elementUpdated);
}
}
private async Task SendBroadcastPermissions(object elementUpdated, string userName, OperationType operation)
{
var subscribers = _subscribersService.GetSubscribers().Where(s =>
s.UserName == userName).ToList();
foreach (var subscriber in subscribers)
{
if (subscriber.SubscriptionType == SubscriptionEnum.WsType.Admin)
{
try
{
var permissionsForPanel = await _permissionService.Value.GetPermissionsForPanel(userName);
_ = _clientMessageService.SendAsync(subscriber.Id, operation, permissionsForPanel);
}
catch (Exception)
{
_ = _clientMessageService.SendAsync(subscriber.Id, operation, null);
}
}
else
{
_ = _clientMessageService.SendAsync(subscriber.Id, operation, elementUpdated);
}
}
}
public async Task<User?> UpdateUserWithAuthorities(UpdateUserWithAuthDto updateUserWithAuthDto, bool updatePass)
{
var u = updateUserWithAuthDto.User;
CheckIfEmailExists(u.Email, u.Id);
CheckIfUserNameExists(u.UserName, u.Id);
var userUpdated = await UpdateUsersByRequest(u, updatePass) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
foreach (var id in updateUserWithAuthDto.AuthsToDelete) await DeleteAuthority(id);
foreach (var auth in updateUserWithAuthDto.Authorizations) await UpdateAuthority(auth);
foreach (var auth in updateUserWithAuthDto.NewAuthorizations)
{
auth.UserId = userUpdated.Id;
await CreateNewAuthority(auth);
}
if(updateUserWithAuthDto.AuthsToDelete.Any() || updateUserWithAuthDto.NewAuthorizations.Any() || updateUserWithAuthDto.Authorizations.Any())
{
var allPermissionsForDisplay = await _displayService.Value.GetAllByUser(u.UserName);
await SendBroadcastPermissions(allPermissionsForDisplay, u.UserName, OperationType.UpdateAuthorities);
}
return userUpdated;
}
public async Task<bool> UpdateUserPassword(ObjectId id, string oldPassword, string newPassword)
{
if (oldPassword == newPassword)
throw new BadRequestException("New password must be different");
var user = await _userRepository.GetById(id) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
if (!CryptoAdas.VerifyPassword(oldPassword, user.Password))
throw new UnprocessableEntityException("Invalid current password");
if (!CryptoAdas.IsStrongPassword(newPassword))
throw new UnprocessableEntityException("Weak password");
user.Password = CryptoAdas.CreateBCrypt(newPassword);
var result = await _userRepository.UpdateUser(user, true);
if (result != null)
{
_logger.LogInformation("Password updated for user {UserId}", id);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User, user, result);
return true;
}
return false;
}
public async Task<bool> DeleteUser(ObjectId id)
{
try
{
var user = await _userRepository.GetById(id);
await _userRepository.DeleteAsync(id);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User, user, null);
return true;
}
catch (Exception e)
{
_logger.LogError("Error deleting user. Exception: {e}", e);
return false;
}
}
public async Task<PaginationResponse<User>> GetPaginatedUsers(PaginationFilter filter)
{
var result = _userRepository.GetPaginatedUsers(filter);
var count = await result.CountDocumentsAsync();
var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize)
.Limit(filter.PageSize)
.ToCursorAsync();
var dataList = await data.ToListAsync();
return new PaginationResponse<User>(dataList, filter.PageNumber, filter.PageSize, count);
}
public async Task<TokenResult> GenerateJwt(User user)
{
var claims = new List<Claim>
{
new(ClaimTypes.Name, user.UserName),
new(ClaimTypes.Email, user.Email),
new("IpAddress", user.IpAddress)
};
// Agregar claims de autorizaciones utilizando ClaimsFromAuthorities
if (user.Authorization != null)
claims.AddRange(await ClaimsFromAuthorities(user.Authorization));
var accessToken = JwtHelper.GetJwtToken(
user.UserName,
_jwtConfig.Key ?? string.Empty,
_jwtConfig.Issuer ?? string.Empty,
_jwtConfig.Audience ?? string.Empty,
_jwtConfig.TokenValidityInMinutes,
claims.ToArray());
var refreshToken = JwtHelper.GetJwtToken(
user.UserName,
_jwtConfig.Key ?? string.Empty,
_jwtConfig.Issuer ?? string.Empty,
_jwtConfig.Audience ?? string.Empty,
_jwtConfig.RefreshTokenValidityInMinutes,
claims.ToArray());
return new TokenResult
{
AccessToken = new JwtSecurityTokenHandler().WriteToken(accessToken),
RefreshToken = new JwtSecurityTokenHandler().WriteToken(refreshToken),
AccessTokenExpiryTime = accessToken.ValidTo,
RefreshTokenExpiryTime = refreshToken.ValidTo,
User = user
};
}
public async Task<List<User>> GetAll()
{
BusinessException? loginException = null;
foreach (var m in _validLoginMethods)
{
if (!TryParse(m, out UserEnum.LoginMethod method)) continue;
var service = _availableLoginServices.FirstOrDefault(s => s.Method == method && s.AllowPassword);
if (service == null) continue;
try
{
return await service.GetAllUsers();
}
catch (BusinessException e)
{
loginException = e;
}
catch (Exception e)
{
var message = e.Message;
if (e.Message != e.InnerException?.Message) message += $", {e.InnerException?.Message}";
Debug.WriteLine($"Error Getting all users: {message} ");
loginException = new LoginServicesException(message);
}
}
if (loginException != null) throw loginException;
throw new LoginServicesNotFoundException();
}
public async Task<TokenResult> LoginWithGivenAccessToken(string token)
{
ValidateToken(token, IUserService.TokenTypeUser, out var validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var user = await GetUserByToken(jwtToken) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
user.Authorization ??= await _authorityService.GetUserAuthorities(user.Id);
if (jwtToken.ValidTo < DateTime.UtcNow)
return await GenerateJwt(user);
var claims = new List<Claim>
{
new(ClaimTypes.Name, user.UserName),
new(ClaimTypes.Email, user.Email),
new("IpAddress", user.IpAddress)
};
// Agregar claims de autorizaciones utilizando ClaimsFromAuthorities
claims.AddRange(await ClaimsFromAuthorities(user.Authorization));
var dif = jwtToken.ValidTo > DateTime.Now ? jwtToken.ValidTo - DateTime.Now : DateTime.Now - jwtToken.ValidTo;
var accessToken = JwtHelper.GetJwtToken(
user.UserName,
_jwtConfig.Key ?? string.Empty,
_jwtConfig.Issuer ?? string.Empty,
_jwtConfig.Audience ?? string.Empty,
(int)dif.TotalMinutes,
claims.ToArray());
var refreshToken = JwtHelper.GetJwtToken(
user.UserName,
_jwtConfig.Key ?? string.Empty,
_jwtConfig.Issuer ?? string.Empty,
_jwtConfig.Audience ?? string.Empty,
(int)dif.TotalMinutes + _jwtConfig.RefreshTokenValidityInMinutes,
claims.ToArray());
return new TokenResult
{
AccessToken = new JwtSecurityTokenHandler().WriteToken(accessToken),
RefreshToken = new JwtSecurityTokenHandler().WriteToken(refreshToken),
AccessTokenExpiryTime = accessToken.ValidTo,
RefreshTokenExpiryTime = refreshToken.ValidTo,
User = user
};
}
public async Task<TokenResult> LoginWithAccessToken(string token)
{
ValidateToken(token, IUserService.TokenTypeUser, out var validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var user = await GetUserByToken(jwtToken) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
user.Authorization ??= await _authorityService.GetUserAuthorities(user.Id);
return await GenerateJwt(user);
}
public async Task<Authorization> CreateNewAuthority(Authorization auth)
{
var newUserAuthority = await _authorityService.CreateNewUserAuthority(auth) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
return newUserAuthority;
}
public async Task<bool> DeleteAuthority(string id)
{
if (!ObjectId.TryParse(id, out var userAuthorityIdParsed))
throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
var isDeleted = await _authorityService.DeleteUserAuthority(userAuthorityIdParsed);
return !isDeleted ? throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed) : isDeleted;
}
public async Task<bool> UpdateAuthority(Authorization authorization)
{
_ = await _authorityService.EditUserAuthority(authorization) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
return true;
}
public async Task<List<Claim>> ClaimsFromAuthorities(List<Authorization> authorities)
{
var claims = new List<Claim>();
foreach (var auth in authorities)
if (auth.UnitId != null)
{
var dis = await
_displayService.Value.GetByUnitId(ObjectId.Parse(auth.UnitId));
foreach (var claim in dis)
{
auth.DisplayId = claim.Id.ToString();
AddClaim(auth, claims);
}
}
else
{
AddClaim(auth, claims);
}
return claims;
}
private static void AddClaim(Authorization auth, List<Claim> claims)
{
var newClaim = new Claim(ClaimTypes.Role,
auth.Rol + (string.IsNullOrEmpty(auth.DisplayId) ? "" : "_" + auth.DisplayId));
if (claims.Any(c => c.Value == newClaim.Value)) return;
//newClaim.Properties.Add("DisplayId", auth.DisplayId ?? "");
claims.Add(newClaim);
}
private void CheckIfUserNameExists(string userName, ObjectId? userId = null)
{
var users = GetAll().Result;
var userNameExists = userId.HasValue
? users.Any(user => user.Id != userId.Value && user.UserName == userName)
: users.Any(user => user.UserName == userName);
if (userNameExists) throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestIncorrectEmailOrUsername);
}
private void CheckIfEmailExists(string email, ObjectId? userId = null)
{
var users = GetAll().Result;
var emailExists = userId.HasValue
? users.Any(user => user.Id != userId.Value && user.Email == email)
: users.Any(user => user.Email == email);
if (emailExists && !string.IsNullOrEmpty(email))
throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestIncorrectEmailOrUsername);
}
}
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>adas_core.Authentication</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AuditLogs" Version="1.0.59" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.10" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.27" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.8" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.18.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.18.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\adas-core.Application\adas-core.Application.csproj" />
<ProjectReference Include="..\adas-core.Domain\adas-core.Domain.csproj" />
</ItemGroup>
</Project>