From 3926c2159b6152be7154880981eb54563eb74568 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 20 Nov 2023 10:42:11 -0500 Subject: [PATCH 001/178] Correccion del README --- README.md | 6 +++--- global.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 53efb6a..6ba19f1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Proyecto de Autenticación de Usuarios LIN -Este es un proyecto desarrollado en C# y .NET 7 que proporciona funcionalidades de autenticación de usuarios para sistemas LIN. El proyecto se centra en garantizar la seguridad y la gestión de usuarios en entornos LIN, permitiendo un acceso controlado a la información. +Este es un proyecto desarrollado en C# y .NET 8 que proporciona funcionalidades de autenticación de usuarios para sistemas LIN. El proyecto se centra en garantizar la seguridad y la gestión de usuarios en entornos LIN, permitiendo un acceso controlado a la información. # Características @@ -15,7 +15,7 @@ Este es un proyecto desarrollado en C# y .NET 7 que proporciona funcionalidades ## Requisitos del Sistema -- [.NET 7 Runtime](https://dotnet.microsoft.com/download/dotnet/7.0) +- [.NET 8 Runtime](https://dotnet.microsoft.com/download/dotnet/8.0) - Base de datos compatible con Entity Framework (SQL Server) ## Configuración @@ -23,7 +23,7 @@ Este es un proyecto desarrollado en C# y .NET 7 que proporciona funcionalidades 1. Clona este repositorio en tu máquina local. ``` - git clone https://github.com/LINServices/LIN.Auth.git + git clone https://github.com/LINServices/LIN.Identity.git ``` 2. Abre la solución en Visual Studio o tu IDE preferido. diff --git a/global.json b/global.json index 7cd6a1f..dad2db5 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.0", + "version": "8.0.0", "rollForward": "latestMajor", "allowPrerelease": true } From 823e33ebdba925b483018468506dfc54d7442466 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 20 Nov 2023 11:06:42 -0500 Subject: [PATCH 002/178] Mejoras de comentarios --- LIN.Identity.sln | 2 +- .../Areas/Accounts/AccountController.cs | 39 ++++++++++--------- LIN.Identity/Program.cs | 2 + 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/LIN.Identity.sln b/LIN.Identity.sln index e7e7e59..7d1a5f7 100644 --- a/LIN.Identity.sln +++ b/LIN.Identity.sln @@ -13,7 +13,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http", "..\..\Tipos\Http\Ht EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types.Auth", "..\..\Tipos\LIN.Types.Auth\LIN.Types.Auth.csproj", "{CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LIN.Access.Logger", "..\..\AccesoAPI\LIN.Access.Logger\LIN.Access.Logger.csproj", "{6CB65EA1-51C6-4450-B174-F0A04C489FB5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Access.Logger", "..\..\AccesoAPI\LIN.Access.Logger\LIN.Access.Logger.csproj", "{6CB65EA1-51C6-4450-B174-F0A04C489FB5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index c9d76a9..518f4d9 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -54,21 +54,22 @@ public async Task Create([FromBody] AccountModel? modelo) public async Task> Read([FromQuery] int id, [FromHeader] string token) { + // Id es invalido. if (id <= 0) return new(Responses.InvalidParam); + // Información del token. var (isValid, _, user, orgId, _) = Jwt.Validate(token); + // Token es invalido. if (!isValid) - { return new ReadOneResponse() { Response = Responses.Unauthorized, Message = "Token invalido." }; - } - // Obtiene el usuario + // Obtiene el usuario. var response = await Data.Accounts.Read(id, new() { ContextOrg = orgId, @@ -83,8 +84,7 @@ public async Task> Read([FromQuery] int id, [F if (response.Response != Responses.Success) return new ReadOneResponse() { - Response = response.Response, - Model = new() + Response = response.Response }; // Retorna el resultado @@ -103,23 +103,22 @@ public async Task> Read([FromQuery] int id, [F public async Task> Read([FromQuery] string user, [FromHeader] string token) { + // Usuario es invalido. if (string.IsNullOrWhiteSpace(user)) return new(Responses.InvalidParam); - + // Información del token. var (isValid, _, userId, orgId, _) = Jwt.Validate(token); + // Token es invalido. if (!isValid) - { - return new ReadOneResponse() + return new() { Response = Responses.Unauthorized, Message = "Token invalido." }; - } - - + // Obtiene el usuario. var response = await Data.Accounts.Read(user, new() { ContextOrg = orgId, @@ -129,8 +128,6 @@ public async Task> Read([FromQuery] string use OrgLevel = FilterModels.IncludeOrgLevel.Advance }); - - // Si es erróneo if (response.Response != Responses.Success) return new ReadOneResponse() @@ -164,13 +161,11 @@ public async Task> Search([FromQuery] string p // Token es invalido if (!isValid) - { return new ReadAllResponse { Message = "Token es invalido", Response = Responses.Unauthorized }; - } // Obtiene el usuario var response = await Data.Accounts.Search(pattern, new() @@ -196,13 +191,17 @@ public async Task> Search([FromQuery] string p public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) { + // Información del token. var (isValid, _, userId, orgId, _) = Jwt.Validate(token); + // Es invalido. if (!isValid) - { - return new(Responses.Unauthorized); - } - + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + // Obtiene el usuario var response = await Data.Accounts.FindAll(ids, new() { @@ -292,8 +291,10 @@ public async Task Update([FromBody] UpdatePasswordModel modelo public async Task Delete([FromHeader] string token) { + // Información del token. var (isValid, _, userId, _, _) = Jwt.Validate(token); + // Si es invalido. if (!isValid) return new ResponseBase { diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index 72f9b92..88a208b 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -1,4 +1,6 @@ using LIN.Identity.Data; + + { LIN.Access.Logger.Logger.AppName = "LIN.IDENTITY"; From 826469a83691d5ec234826309959a6ce9830b81d Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 20 Nov 2023 11:11:52 -0500 Subject: [PATCH 003/178] Mejoras de comentarios --- .../Areas/Accounts/AccountController.cs | 37 ++++++++++++------- LIN.Identity/Controllers/Security/Security.cs | 2 +- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index 518f4d9..ee3c8b8 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -385,7 +385,7 @@ public async Task> FindAll([FromQuery] string /// - /// Actualiza la información de una cuenta + /// Actualiza la información de una cuenta. /// /// Modelo /// Token de acceso @@ -393,8 +393,10 @@ public async Task> FindAll([FromQuery] string public async Task Update([FromBody] AccountModel modelo, [FromHeader] string token) { + // Información del token. var (isValid, _, userId, _, _) = Jwt.Validate(token); + // Es invalido. if (!isValid) return new ResponseBase { @@ -402,6 +404,7 @@ public async Task Update([FromBody] AccountModel modelo, [From Message = "Token Invalido" }; + // Organizar el modelo. modelo.ID = userId; modelo.Perfil = Image.Zip(modelo.Perfil); @@ -423,15 +426,18 @@ public async Task Update([FromBody] AccountModel modelo, [From public async Task Update([FromHeader] string token, [FromHeader] Genders genero) { - + // Información del token. var (isValid, _, id, _, _) = Jwt.Validate(token); - + // Token es invalido. if (!isValid) - { - return new(Responses.Unauthorized); - } + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + // Realizar actualización. return await Data.Accounts.Update(id, genero); } @@ -439,22 +445,25 @@ public async Task Update([FromHeader] string token, [FromHeade /// - /// Actualiza la visibilidad de una cuenta + /// Actualiza la visibilidad de una cuenta. /// - /// Token de acceso - /// Nueva visibilidad + /// Token de acceso. + /// Nueva visibilidad. [HttpPatch("update/visibility")] public async Task Update([FromHeader] string token, [FromHeader] AccountVisibility visibility) { - + // Información del token. var (isValid, _, id, _, _) = Jwt.Validate(token); + // Token es invalido. if (!isValid) - { - return new(Responses.Unauthorized); - } - + return new() { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + // Actualización. return await Data.Accounts.Update(id, visibility); } diff --git a/LIN.Identity/Controllers/Security/Security.cs b/LIN.Identity/Controllers/Security/Security.cs index d23d1a0..3e96f52 100644 --- a/LIN.Identity/Controllers/Security/Security.cs +++ b/LIN.Identity/Controllers/Security/Security.cs @@ -79,7 +79,7 @@ public async Task> ForgetPassword([FromQuery] st // Envía el correo - await EmailWorker.SendPassword(verifiedMail?.Email!, userData.Usuario, $"http://linaccount.somee.com/resetpassword/{userData.ID}/{link.Key}"); + await EmailWorker.SendPassword(verifiedMail?.Email!, userData.Usuario, $"http://linapps.co/resetpassword/{userData.ID}/{link.Key}"); return new(Responses.Success, new() From a81abf31684f5317c7177205b357691906674e14 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 21 Nov 2023 15:59:38 -0500 Subject: [PATCH 004/178] Comentarios de codigo --- LIN.Identity/Areas/Accounts/AdminController.cs | 10 +++++----- global.json | 7 ------- 2 files changed, 5 insertions(+), 12 deletions(-) delete mode 100644 global.json diff --git a/LIN.Identity/Areas/Accounts/AdminController.cs b/LIN.Identity/Areas/Accounts/AdminController.cs index 03efe4f..822d14a 100644 --- a/LIN.Identity/Areas/Accounts/AdminController.cs +++ b/LIN.Identity/Areas/Accounts/AdminController.cs @@ -83,20 +83,21 @@ public async Task> Read([FromQuery] int id, [F public async Task> Read([FromQuery] string user, [FromHeader] string token) { + // Validar el parámetro. if (string.IsNullOrWhiteSpace(user)) return new(Responses.InvalidParam); - + // Información del token. var (isValid, _, userId, orgId, _) = Jwt.Validate(token); + // Token es invalido. if (!isValid) - { return new ReadOneResponse() { Response = Responses.Unauthorized, Message = "Token invalido." }; - } + var rol = (await Data.Accounts.Read(userId, new() @@ -106,13 +107,12 @@ public async Task> Read([FromQuery] string use })).Model.Rol; if (rol != AccountRoles.Admin) - { return new ReadOneResponse() { Response = Responses.Unauthorized, Message = "Tienes que ser un administrador." }; - } + diff --git a/global.json b/global.json deleted file mode 100644 index dad2db5..0000000 --- a/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "8.0.0", - "rollForward": "latestMajor", - "allowPrerelease": true - } -} \ No newline at end of file From c354bd81026a83a66bb0929bc9fb18ac85e23482 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 21 Nov 2023 20:20:20 -0500 Subject: [PATCH 005/178] Mejoras --- LIN.Identity/Areas/Accounts/AccountController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index ee3c8b8..f598d45 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -8,7 +8,7 @@ public class AccountController : ControllerBase /// - /// Crear nueva cuenta (Cuenta de LIN) + /// Crear nueva cuenta (Cuenta de LIN). /// /// Modelo de la cuenta [HttpPost("create")] From 1fe5335d5a5a8d9c5d80b20da86b36e5e8710561 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 16:51:22 -0500 Subject: [PATCH 006/178] Cambios en la ruta de Mails --- LIN.Identity/Controllers/MailController.cs | 106 +++++++++++++++++- LIN.Identity/Controllers/Security/Security.cs | 102 ----------------- LIN.Identity/Services/Image.cs | 1 - LIN.Identity/Services/Jwt.cs | 2 - 4 files changed, 104 insertions(+), 107 deletions(-) diff --git a/LIN.Identity/Controllers/MailController.cs b/LIN.Identity/Controllers/MailController.cs index 7b9e870..8c81d45 100644 --- a/LIN.Identity/Controllers/MailController.cs +++ b/LIN.Identity/Controllers/MailController.cs @@ -7,7 +7,7 @@ public class MailController : ControllerBase /// - /// Obtiene los mails asociados a una cuenta + /// Obtiene los mails asociados a una cuenta. /// /// Token de acceso [HttpGet("all")] @@ -19,8 +19,9 @@ public async Task> GetMails([FromHeader] string // Validación del token if (!isValid) - return new(Responses.Unauthorized) + return new() { + Response = Responses.Unauthorized, Message = "Token invalido." }; @@ -30,4 +31,105 @@ public async Task> GetMails([FromHeader] string + /// + /// Agrega un nuevo email a una cuenta. + /// + /// Token de acceso. + /// Contraseña de la cuenta. + /// Modelo del email. + [HttpPost("add")] + public async Task EmailAdd([FromHeader] string token, [FromHeader] string password, [FromBody] EmailModel model) + { + + // Obtener el usuario + var userData = await Data.Accounts.ReadBasic(model.UserID); + + // Evaluación de la respuesta + if (userData.Response != Responses.Success) + { + return new(Responses.NotExistAccount); + } + + // Evaluación de la contraseña + if (userData.Model.Contraseña != EncryptClass.Encrypt(password)) + return new(Responses.Unauthorized); + + // Conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + // Obtiene los mails actuales + var mails = await Data.Mails.ReadAll(userData.Model.ID, context); + + // Evalúa la respuesta + if (mails.Response != Responses.Success) + { + context.CloseActions(connectionKey); + return new(); + } + + // Este email ya esta en la cuenta? + var countEmail = mails.Models.Where(T => T.Email == model.Email).Count(); + + // Si ya existía + if (countEmail > 0) + { + context.CloseActions(connectionKey); + return new(); + } + + // Agrega el mail + var addMail = await Data.Mails.Create(new() + { + Status = EmailStatus.Unverified, + Email = model.Email, + ID = 0, + UserID = userData.Model.ID + }, context); + + + // Evalúa la respuesta + if (addMail.Response != Responses.Success) + { + context.CloseActions(connectionKey); + return new(); + } + + //Crea el LINK + var emailLink = new MailMagicLink() + { + Email = addMail.LastID, + Status = MagicLinkStatus.Activated, + Vencimiento = DateTime.Now.AddMinutes(10), + Key = KeyGen.Generate(20, "eml") + }; + + try + { + // Agrega y guarda el link + context.DataBase.MailMagicLinks.Add(emailLink); + + + var bytes = Encoding.UTF8.GetBytes(model.Email); + var mail64 = Convert.ToBase64String(bytes); + + bytes = Encoding.UTF8.GetBytes(userData.Model.Usuario); + var user64 = Convert.ToBase64String(bytes); + + await EmailWorker.SendVerification(model.Email, $"http://linaccount.somee.com/verificate/{user64}/{mail64}/{emailLink.Key}", model.Email); + return new(Responses.Success); + + } + catch + { + } + finally + { + context.DataBase.SaveChanges(); + } + + return new(); + + } + + } \ No newline at end of file diff --git a/LIN.Identity/Controllers/Security/Security.cs b/LIN.Identity/Controllers/Security/Security.cs index 3e96f52..3dd82b0 100644 --- a/LIN.Identity/Controllers/Security/Security.cs +++ b/LIN.Identity/Controllers/Security/Security.cs @@ -227,108 +227,6 @@ public async Task VerifyEmail([FromHeader] string key) - /// - /// Agrega un nuevo email a una cuenta - /// - /// Contraseña de la cuenta - /// Modelo del email - [HttpPost("mails/add")] - public async Task EmailAdd([FromHeader] string password, [FromBody] EmailModel model) - { - - // Obtener el usuario - var userData = await Data.Accounts.ReadBasic(model.UserID); - - // Evaluación de la respuesta - if (userData.Response != Responses.Success) - { - return new(Responses.NotExistAccount); - } - - // Evaluación de la contraseña - if (userData.Model.Contraseña != EncryptClass.Encrypt(password)) - return new(Responses.Unauthorized); - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - // Obtiene los mails actuales - var mails = await Data.Mails.ReadAll(userData.Model.ID, context); - - // Evalúa la respuesta - if (mails.Response != Responses.Success) - { - context.CloseActions(connectionKey); - return new(); - } - - // Este email ya esta en la cuenta? - var countEmail = mails.Models.Where(T => T.Email == model.Email).Count(); - - // Si ya existía - if (countEmail > 0) - { - context.CloseActions(connectionKey); - return new(); - } - - // Agrega el mail - var addMail = await Data.Mails.Create(new() - { - Status = EmailStatus.Unverified, - Email = model.Email, - ID = 0, - UserID = userData.Model.ID - }, context); - - - // Evalúa la respuesta - if (addMail.Response != Responses.Success) - { - context.CloseActions(connectionKey); - return new(); - } - - //Crea el LINK - var emailLink = new MailMagicLink() - { - Email = addMail.LastID, - Status = MagicLinkStatus.Activated, - Vencimiento = DateTime.Now.AddMinutes(10), - Key = KeyGen.Generate(20, "eml") - }; - - try - { - // Agrega y guarda el link - context.DataBase.MailMagicLinks.Add(emailLink); - - - var bytes = Encoding.UTF8.GetBytes(model.Email); - var mail64 = Convert.ToBase64String(bytes); - - bytes = Encoding.UTF8.GetBytes(userData.Model.Usuario); - var user64 = Convert.ToBase64String(bytes); - - await EmailWorker.SendVerification(model.Email, $"http://linaccount.somee.com/verificate/{user64}/{mail64}/{emailLink.Key}", model.Email); - return new(Responses.Success); - - } - catch - { - - } - finally - { - context.DataBase.SaveChanges(); - } - - return new(); - - } - - - /// /// Reenvía el correo para la activación /// diff --git a/LIN.Identity/Services/Image.cs b/LIN.Identity/Services/Image.cs index b051ee2..e168be0 100644 --- a/LIN.Identity/Services/Image.cs +++ b/LIN.Identity/Services/Image.cs @@ -7,7 +7,6 @@ namespace LIN.Identity.Services; public class Image { - /// /// Comprime una imagen /// diff --git a/LIN.Identity/Services/Jwt.cs b/LIN.Identity/Services/Jwt.cs index 2e19028..0ba3144 100644 --- a/LIN.Identity/Services/Jwt.cs +++ b/LIN.Identity/Services/Jwt.cs @@ -1,6 +1,5 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; -using System.Security.Cryptography; namespace LIN.Identity.Services; @@ -111,7 +110,6 @@ internal static (bool isValid, string user, int userID, int orgID, int appID) Va } catch (SecurityTokenException) { - } From 254b8acb5e322b34a091526ad41b323e547c9353 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 16:55:32 -0500 Subject: [PATCH 007/178] Mejoras de calidad de comentarios --- LIN.Identity/Controllers/ApiController.cs | 11 ----------- .../Controllers/AuthenticationController.cs | 14 ++++++++++---- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/LIN.Identity/Controllers/ApiController.cs b/LIN.Identity/Controllers/ApiController.cs index d5440fd..d99b46c 100644 --- a/LIN.Identity/Controllers/ApiController.cs +++ b/LIN.Identity/Controllers/ApiController.cs @@ -6,7 +6,6 @@ public class ApiVersion : Controller { - /// /// Obtiene el estado del servidor /// @@ -20,14 +19,4 @@ public dynamic Status() } - - - - [HttpGet] - public dynamic Index() - { - return Ok("Abierto"); - } - - } \ No newline at end of file diff --git a/LIN.Identity/Controllers/AuthenticationController.cs b/LIN.Identity/Controllers/AuthenticationController.cs index 925bef4..5caefbc 100644 --- a/LIN.Identity/Controllers/AuthenticationController.cs +++ b/LIN.Identity/Controllers/AuthenticationController.cs @@ -41,7 +41,10 @@ public async Task> Login([FromQuery] string us // Incorrecto default: - return new(response.Response); + return new(response.Response) + { + Message = "Hubo un error grave." + }; } @@ -84,12 +87,15 @@ public async Task> Login([FromQuery] string us public async Task> LoginWithToken([FromHeader] string token) { - // Valida el token + // Información del token de acceso. var (isValid, _, user, _, _) = Jwt.Validate(token); + // Si el token es invalido. if (!isValid) - return new(Responses.InvalidParam); - + return new(Responses.InvalidParam) + { + Message = "El token proporcionado no es valido." + }; // Obtiene el usuario var response = await Data.Accounts.Read(user, new() From 94ffd2fe984eab599277667b670199c7a675be9e Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 17:04:25 -0500 Subject: [PATCH 008/178] Mejoras de seguridad y rendimiento --- .../Areas/Organizations/MemberController.cs | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index 030a2ef..414edbb 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -9,24 +9,23 @@ public class MemberController : ControllerBase /// - /// Crea un nuevo miembro en una organización + /// Crea un nuevo miembro en una organización. /// /// Modelo de la cuenta /// Token de acceso de un administrador /// Rol asignado - [HttpPost("create")] + [HttpPost] public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] OrgRoles rol) { // Validación del modelo. if (modelo == null || !modelo.Usuario.Trim().Any() || !modelo.Nombre.Trim().Any()) - { return new CreateResponse { Response = Responses.InvalidParam, Message = "Uno o varios parámetros inválidos." }; - } + // Visibilidad oculta modelo.Visibilidad = AccountVisibility.Hidden; @@ -130,44 +129,35 @@ public async Task Create([FromBody] AccountModel modelo, [Fr /// - /// Obtiene la lista de miembros asociados a una organización + /// Obtiene la lista de integrantes asociados a una organización. /// /// Token de acceso [HttpGet] public async Task> ReadAll([FromHeader] string token) { + // Información del token. var (isValid, _, _, orgID, _) = Jwt.Validate(token); - + // Si el token es invalido. if (!isValid) - { return new ReadAllResponse { - Message = "", + Message = "El token es invalido.", Response = Responses.Unauthorized }; - } - + + // Obtiene los miembros. var members = await Data.Organizations.Members.ReadAll(orgID); - + // Error al obtener los integrantes. if (members.Response != Responses.Success) - { return new ReadAllResponse { - Message = "No found Organization", + Message = "No se encontró la organización.", Response = Responses.Unauthorized }; - } - - - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - context.CloseActions(connectionKey); - + // Retorna el resultado return members; From 29ff88905765715b0d5c14def2a7157bd8cb24b6 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 17:06:17 -0500 Subject: [PATCH 009/178] Cambio de rutas en Orgs -> Organizations --- .../Organizations/OrganizationController.cs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index a7e2eb9..31a8da0 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -3,15 +3,15 @@ namespace LIN.Identity.Areas.Organizations; -[Route("orgs")] +[Route("organizations")] public class OrganizationsController : ControllerBase { /// - /// Crea una nueva organizacion + /// Crea una nueva organización. /// - /// Modelo de la organizaci�n y el usuario administrador + /// Modelo de la organización y el usuario administrador [HttpPost("create")] public async Task Create([FromBody] OrganizationModel modelo) { @@ -25,7 +25,7 @@ public async Task Create([FromBody] OrganizationModel modelo var (context, connectionKey) = Conexión.GetOneConnection(); - // Organizaci�n del modelo + // organización del modelo modelo.ID = 0; modelo.AppList = new(); @@ -36,7 +36,7 @@ public async Task Create([FromBody] OrganizationModel modelo member.Organization = modelo; } - // Creaci�n de la organizaci�n + // Creaci�n de la organización var response = await Data.Organizations.Organizations.Create(modelo, context); // Evaluaci�n @@ -58,9 +58,10 @@ public async Task Create([FromBody] OrganizationModel modelo /// - /// Obtiene una organizaci�n por medio del ID + /// Obtiene una organización por medio del Id. /// /// ID de la organización + /// Token de acceso [HttpGet("read/id")] public async Task> ReadOneByID([FromQuery] int id, [FromHeader] string token) { @@ -114,7 +115,7 @@ public async Task> ReadOneByID([FromQuery /// - /// Actualiza si una organizaci�n tiene lista blanca + /// Actualiza si una organización tiene lista blanca. /// /// Toke de acceso administrador /// Nuevo estado @@ -142,22 +143,22 @@ public async Task Update([FromHeader] string token, [FromQuery }; } - // Si el usuario no tiene una organizaci�n + // Si el usuario no tiene una organización if (userContext.Model.OrganizationAccess == null) { return new ResponseBase { - Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organizaci�n.", + Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", Response = Responses.Unauthorized }; } - // Verificaci�n del rol dentro de la organizaci�n + // Verificaci�n del rol dentro de la organización if (!userContext.Model.OrganizationAccess.Rol.IsAdmin()) { return new ResponseBase { - Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de la lista blanca de esta organizaci�n.", + Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de la lista blanca de esta organización.", Response = Responses.Unauthorized }; } @@ -173,7 +174,7 @@ public async Task Update([FromHeader] string token, [FromQuery /// - /// Actualiza si los usuarios no admins de una organizaci�n tienen acceso a su cuenta + /// Actualiza si los usuarios no admins de una organización tienen acceso a su cuenta. /// /// Token de acceso administrador /// Nuevo estado @@ -201,22 +202,22 @@ public async Task UpdateAccess([FromHeader] string token, [Fro }; } - // Si el usuario no tiene una organizaci�n + // Si el usuario no tiene una organización if (userContext.Model.OrganizationAccess == null) { return new ResponseBase { - Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organizaci�n.", + Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", Response = Responses.Unauthorized }; } - // Verificaci�n del rol dentro de la organizaci�n + // Verificaci�n del rol dentro de la organización if (userContext.Model.OrganizationAccess.Rol != OrgRoles.SuperManager) { return new ResponseBase { - Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de accesos de esta organizaci�n.", + Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de accesos de esta organización.", Response = Responses.Unauthorized }; } From e8176f4cc532876c1d71eb081b947e935141bfe2 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 17:09:46 -0500 Subject: [PATCH 010/178] =?UTF-8?q?Area=20de=20autenticaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Authentication}/AuthenticationController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename LIN.Identity/{Controllers => Areas/Authentication}/AuthenticationController.cs (98%) diff --git a/LIN.Identity/Controllers/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs similarity index 98% rename from LIN.Identity/Controllers/AuthenticationController.cs rename to LIN.Identity/Areas/Authentication/AuthenticationController.cs index 5caefbc..9c6ff1b 100644 --- a/LIN.Identity/Controllers/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,6 +1,6 @@ using LIN.Identity.Services.Login; -namespace LIN.Identity.Controllers; +namespace LIN.Identity.Areas.Authentication; [Route("authentication")] From 75181525b910c424464198af9180aeab75b1c7c3 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 17:11:11 -0500 Subject: [PATCH 011/178] Mejoras de Intents --- .../Authentication}/IntentsController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename LIN.Identity/{Controllers => Areas/Authentication}/IntentsController.cs (92%) diff --git a/LIN.Identity/Controllers/IntentsController.cs b/LIN.Identity/Areas/Authentication/IntentsController.cs similarity index 92% rename from LIN.Identity/Controllers/IntentsController.cs rename to LIN.Identity/Areas/Authentication/IntentsController.cs index 3315034..128abe5 100644 --- a/LIN.Identity/Controllers/IntentsController.cs +++ b/LIN.Identity/Areas/Authentication/IntentsController.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Controllers; +namespace LIN.Identity.Areas.Authentication; [Route("Intents")] @@ -6,7 +6,7 @@ public class IntentsController : ControllerBase { /// - /// Obtiene la lista de intentos Passkey activos + /// Obtiene la lista de intentos de llaves de paso están activos. /// /// Token de acceso [HttpGet] From e78c007619f92a3b6045b489d1b6505825c64bb7 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 17:12:07 -0500 Subject: [PATCH 012/178] Cambio de rutas de ApplicationsController --- .../Applications}/ApplicationController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename LIN.Identity/{Controllers => Areas/Applications}/ApplicationController.cs (98%) diff --git a/LIN.Identity/Controllers/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs similarity index 98% rename from LIN.Identity/Controllers/ApplicationController.cs rename to LIN.Identity/Areas/Applications/ApplicationController.cs index 7138d53..57d08a9 100644 --- a/LIN.Identity/Controllers/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Controllers; +namespace LIN.Identity.Areas.Applications; [Route("applications")] @@ -11,7 +11,7 @@ public class ApplicationController : ControllerBase /// /// Modelo. /// Token de acceso. - [HttpPost("create")] + [HttpPost] public async Task Create([FromBody] ApplicationModel applicationModel, [FromHeader] string token) { From d93c676313d24a572ddd6a5fd7eb20e45ece2e4b Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 17:13:08 -0500 Subject: [PATCH 013/178] Mejoras de Areas --- .../{Controllers => Areas/Accounts}/LoginLogController.cs | 4 ++-- LIN.Identity/{Controllers => Areas}/Security/Security.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename LIN.Identity/{Controllers => Areas/Accounts}/LoginLogController.cs (92%) rename LIN.Identity/{Controllers => Areas}/Security/Security.cs (99%) diff --git a/LIN.Identity/Controllers/LoginLogController.cs b/LIN.Identity/Areas/Accounts/LoginLogController.cs similarity index 92% rename from LIN.Identity/Controllers/LoginLogController.cs rename to LIN.Identity/Areas/Accounts/LoginLogController.cs index 9bed542..143a22a 100644 --- a/LIN.Identity/Controllers/LoginLogController.cs +++ b/LIN.Identity/Areas/Accounts/LoginLogController.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Controllers; +namespace LIN.Identity.Areas.Accounts; [Route("Account/logs")] @@ -10,7 +10,7 @@ public class LoginLogController : ControllerBase /// Obtienes la lista de accesos asociados a una cuenta /// /// Token de acceso - [HttpGet("read/all")] + [HttpGet] public async Task> GetAll([FromHeader] string token) { diff --git a/LIN.Identity/Controllers/Security/Security.cs b/LIN.Identity/Areas/Security/Security.cs similarity index 99% rename from LIN.Identity/Controllers/Security/Security.cs rename to LIN.Identity/Areas/Security/Security.cs index 3dd82b0..f048855 100644 --- a/LIN.Identity/Controllers/Security/Security.cs +++ b/LIN.Identity/Areas/Security/Security.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Controllers.Security; +namespace LIN.Identity.Areas.Security; [Route("security")] From 00ae587e7dd78c415683c0d23475edd3a68a7415 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 17:13:54 -0500 Subject: [PATCH 014/178] Finalizacion de areas --- .../Accounts}/DeviceController.cs | 2 +- .../Accounts}/MailController.cs | 2 +- LIN.Identity/Controllers/ApiController.cs | 22 ------------------- 3 files changed, 2 insertions(+), 24 deletions(-) rename LIN.Identity/{Controllers => Areas/Accounts}/DeviceController.cs (97%) rename LIN.Identity/{Controllers => Areas/Accounts}/MailController.cs (98%) delete mode 100644 LIN.Identity/Controllers/ApiController.cs diff --git a/LIN.Identity/Controllers/DeviceController.cs b/LIN.Identity/Areas/Accounts/DeviceController.cs similarity index 97% rename from LIN.Identity/Controllers/DeviceController.cs rename to LIN.Identity/Areas/Accounts/DeviceController.cs index bf93b48..81a707d 100644 --- a/LIN.Identity/Controllers/DeviceController.cs +++ b/LIN.Identity/Areas/Accounts/DeviceController.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Controllers; +namespace LIN.Identity.Areas.Accounts; [Route("devices")] diff --git a/LIN.Identity/Controllers/MailController.cs b/LIN.Identity/Areas/Accounts/MailController.cs similarity index 98% rename from LIN.Identity/Controllers/MailController.cs rename to LIN.Identity/Areas/Accounts/MailController.cs index 8c81d45..91cc575 100644 --- a/LIN.Identity/Controllers/MailController.cs +++ b/LIN.Identity/Areas/Accounts/MailController.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Controllers; +namespace LIN.Identity.Areas.Accounts; [Route("mails")] diff --git a/LIN.Identity/Controllers/ApiController.cs b/LIN.Identity/Controllers/ApiController.cs deleted file mode 100644 index d99b46c..0000000 --- a/LIN.Identity/Controllers/ApiController.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace LIN.Identity.Controllers; - - -[Route("/")] -public class ApiVersion : Controller -{ - - - /// - /// Obtiene el estado del servidor - /// - [HttpGet("status")] - public dynamic Status() - { - return StatusCode(200, new - { - Status = "Open" - }); - } - - -} \ No newline at end of file From 79529255470a27d46ffe6265300a4e3a927205aa Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 5 Dec 2023 12:04:02 -0500 Subject: [PATCH 015/178] Version 3 de controladores --- .../Areas/V1/Accounts/AccountController.cs | 474 ++++++++++++++++++ .../{ => V1}/Accounts/AdminController.cs | 8 +- .../Areas/V1/Accounts/DeviceController.cs | 54 ++ .../Areas/V1/Accounts/LoginLogController.cs | 37 ++ .../Areas/V1/Accounts/MailController.cs | 135 +++++ .../V1/Applications/ApplicationController.cs | 119 +++++ .../AuthenticationController.cs | 123 +++++ .../V1/Authentication/IntentsController.cs | 58 +++ .../ApplicationOrgsController.cs | 164 ++++++ .../Organizations/MemberController.cs | 10 +- .../Organizations/OrganizationController.cs | 234 +++++++++ LIN.Identity/Areas/V1/Security/Security.cs | 312 ++++++++++++ .../{ => V3}/Accounts/AccountController.cs | 11 +- .../Areas/V3/Accounts/AdminController.cs | 266 ++++++++++ .../{ => V3}/Accounts/DeviceController.cs | 4 +- .../{ => V3}/Accounts/LoginLogController.cs | 4 +- .../Areas/{ => V3}/Accounts/MailController.cs | 4 +- .../Applications/ApplicationController.cs | 4 +- .../AuthenticationController.cs | 4 +- .../Authentication/IntentsController.cs | 4 +- .../ApplicationOrgsController.cs | 4 +- .../V3/Organizations/MemberController.cs | 168 +++++++ .../Organizations/OrganizationController.cs | 4 +- .../Areas/{ => V3}/Security/Security.cs | 4 +- LIN.Identity/Program.cs | 51 +- LIN.Identity/Services/Swagger.cs | 14 + 26 files changed, 2240 insertions(+), 34 deletions(-) create mode 100644 LIN.Identity/Areas/V1/Accounts/AccountController.cs rename LIN.Identity/Areas/{ => V1}/Accounts/AdminController.cs (98%) create mode 100644 LIN.Identity/Areas/V1/Accounts/DeviceController.cs create mode 100644 LIN.Identity/Areas/V1/Accounts/LoginLogController.cs create mode 100644 LIN.Identity/Areas/V1/Accounts/MailController.cs create mode 100644 LIN.Identity/Areas/V1/Applications/ApplicationController.cs create mode 100644 LIN.Identity/Areas/V1/Authentication/AuthenticationController.cs create mode 100644 LIN.Identity/Areas/V1/Authentication/IntentsController.cs create mode 100644 LIN.Identity/Areas/V1/Organizations/ApplicationOrgsController.cs rename LIN.Identity/Areas/{ => V1}/Organizations/MemberController.cs (98%) create mode 100644 LIN.Identity/Areas/V1/Organizations/OrganizationController.cs create mode 100644 LIN.Identity/Areas/V1/Security/Security.cs rename LIN.Identity/Areas/{ => V3}/Accounts/AccountController.cs (99%) create mode 100644 LIN.Identity/Areas/V3/Accounts/AdminController.cs rename LIN.Identity/Areas/{ => V3}/Accounts/DeviceController.cs (95%) rename LIN.Identity/Areas/{ => V3}/Accounts/LoginLogController.cs (92%) rename LIN.Identity/Areas/{ => V3}/Accounts/MailController.cs (98%) rename LIN.Identity/Areas/{ => V3}/Applications/ApplicationController.cs (98%) rename LIN.Identity/Areas/{ => V3}/Authentication/AuthenticationController.cs (97%) rename LIN.Identity/Areas/{ => V3}/Authentication/IntentsController.cs (95%) rename LIN.Identity/Areas/{ => V3}/Organizations/ApplicationOrgsController.cs (98%) create mode 100644 LIN.Identity/Areas/V3/Organizations/MemberController.cs rename LIN.Identity/Areas/{ => V3}/Organizations/OrganizationController.cs (99%) rename LIN.Identity/Areas/{ => V3}/Security/Security.cs (99%) create mode 100644 LIN.Identity/Services/Swagger.cs diff --git a/LIN.Identity/Areas/V1/Accounts/AccountController.cs b/LIN.Identity/Areas/V1/Accounts/AccountController.cs new file mode 100644 index 0000000..506dd1f --- /dev/null +++ b/LIN.Identity/Areas/V1/Accounts/AccountController.cs @@ -0,0 +1,474 @@ +using LIN.Identity.Validations; +namespace LIN.Identity.Areas.V1.Accounts; + + +[Route("v1/account")] +public class AccountController : ControllerBase +{ + + + /// + /// Crear nueva cuenta (Cuenta de LIN). + /// + /// Modelo de la cuenta + [HttpPost("create")] + public async Task Create([FromBody] AccountModel? modelo) + { + + // Comprobaciones + if (modelo == null || modelo.Contraseña.Length < 4 || modelo.Nombre.Length <= 0 || modelo.Usuario.Length <= 0) + return new(Responses.InvalidParam); + + // Organización del modelo + modelo = Account.Process(modelo); + + // Creación del usuario + var response = await Data.Accounts.Create(modelo); + + // Evaluación + if (response.Response != Responses.Success) + return new(response.Response); + + // Obtiene el usuario + var token = Jwt.Generate(response.Model, 0); + + // Retorna el resultado + return new CreateResponse() + { + LastID = response.Model.ID, + Response = Responses.Success, + Token = token, + Message = "Success" + }; + + } + + + + /// + /// Obtiene la información de usuario. + /// + /// ID del usuario + /// Token de acceso + [HttpGet("read/id")] + public async Task> Read([FromQuery] int id, [FromHeader] string token) + { + + // Id es invalido. + if (id <= 0) + return new(Responses.InvalidParam); + + // Información del token. + var (isValid, _, user, orgId, _) = Jwt.Validate(token); + + // Token es invalido. + if (!isValid) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + // Obtiene el usuario. + var response = await Data.Accounts.Read(id, new() + { + ContextOrg = orgId, + ContextUser = user, + FindOn = FilterModels.FindOn.StableAccounts, + IncludeOrg = FilterModels.IncludeOrg.IncludeIf, + IsAdmin = false, + OrgLevel = FilterModels.IncludeOrgLevel.Advance + }); + + // Si es erróneo + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtiene la información de usuario. + /// + /// Usuario único + /// Token de acceso + [HttpGet("read/user")] + public async Task> Read([FromQuery] string user, [FromHeader] string token) + { + + // Usuario es invalido. + if (string.IsNullOrWhiteSpace(user)) + return new(Responses.InvalidParam); + + // Información del token. + var (isValid, _, userId, orgId, _) = Jwt.Validate(token); + + // Token es invalido. + if (!isValid) + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + // Obtiene el usuario. + var response = await Data.Accounts.Read(user, new() + { + ContextOrg = orgId, + ContextUser = userId, + FindOn = FilterModels.FindOn.StableAccounts, + IncludeOrg = FilterModels.IncludeOrg.IncludeIf, + OrgLevel = FilterModels.IncludeOrgLevel.Advance + }); + + // Si es erróneo + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = response.Response, + Model = new() + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtiene una lista de diez (10) usuarios que coincidan con un patron + /// + /// Patron + /// Token de acceso + [HttpGet("search")] + public async Task> Search([FromQuery] string pattern, [FromHeader] string token) + { + + // Comprobación + if (pattern.Trim().Length <= 0) + return new(Responses.InvalidParam); + + // Info del token + var (isValid, _, userId, orgId, _) = Jwt.Validate(token); + + // Token es invalido + if (!isValid) + return new ReadAllResponse + { + Message = "Token es invalido", + Response = Responses.Unauthorized + }; + + // Obtiene el usuario + var response = await Data.Accounts.Search(pattern, new() + { + ContextOrg = orgId, + ContextUser = userId, + FindOn = FilterModels.FindOn.StableAccounts, + IncludeOrg = FilterModels.IncludeOrg.IncludeIf, + OrgLevel = FilterModels.IncludeOrgLevel.Advance + }); + + return response; + } + + + + /// + /// Obtiene una lista cuentas + /// + /// IDs de las cuentas + /// Token de acceso + [HttpPost("findAll")] + public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) + { + + // Información del token. + var (isValid, _, userId, orgId, _) = Jwt.Validate(token); + + // Es invalido. + if (!isValid) + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + // Obtiene el usuario + var response = await Data.Accounts.FindAll(ids, new() + { + ContextOrg = orgId, + ContextUser = userId, + FindOn = FilterModels.FindOn.StableAccounts, + IncludeOrg = FilterModels.IncludeOrg.Include, + OrgLevel = FilterModels.IncludeOrgLevel.Advance + }); + + return response; + + } + + + + /// + /// Actualiza la contraseña de una cuenta + /// + /// Modelo de actualización + /// Token de acceso + [HttpPatch("update/password")] + public async Task Update([FromBody] UpdatePasswordModel modelo, [FromHeader] string token) + { + + // Validar parámetros. + if (modelo == null) + return new ResponseBase() + { + Message = "Parámetro para nueva actualización de contraseña es invalido.", + Response = Responses.InvalidParam + }; + + // Validar de la nueva contraseña. + if (modelo.OldPassword.Length < 4 || modelo.NewPassword.Length < 4) + return new ResponseBase(Responses.InvalidParam) + { + Message = "Antigua contraseña o nueva contraseña tienen una longitud invalida." + }; + + // Validar el token. + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + // No es valido. + if (!isValid) + return new ResponseBase(Responses.Unauthorized) + { + Message = "Token invalido" + }; + + // Obtener datos antiguos. + var actualData = await Data.Accounts.ReadBasic(userId); + + // Error al encontrar usuario. + if (actualData.Response != Responses.Success) + return new ResponseBase(Responses.Unauthorized) + { + Message = $"Error al encontrar el usuario con ID '{userId}'" + }; + + + // Encriptar la contraseña + modelo.OldPassword = EncryptClass.Encrypt(modelo.OldPassword); + + // Valida la contraseña actual + if (modelo.OldPassword != actualData.Model.Contraseña) + return new ResponseBase(Responses.InvalidPassword) + { + Message = $"Las contraseñas no coinciden." + }; + + // Actualiza el ID. + modelo.Account = userId; + + // Actualizar la contraseña + return await Data.Accounts.Update(modelo); + + } + + + + /// + /// Elimina una cuenta + /// + /// Token de acceso + [HttpDelete("delete")] + public async Task Delete([FromHeader] string token) + { + + // Información del token. + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + // Si es invalido. + if (!isValid) + return new ResponseBase + { + Response = Responses.Unauthorized, + Message = "Token invalido" + }; + + if (userId <= 0) + return new(Responses.InvalidParam); + + var response = await Data.Accounts.Delete(userId); + return response; + } + + + + /// + /// Desactiva una cuenta + /// + /// Modelo + [HttpPatch("disable")] + public async Task Disable([FromBody] AccountModel user) + { + + if (user.ID <= 0) + { + return new(Responses.ExistAccount); + } + + // Modelo de usuario de la BD + var userModel = await Data.Accounts.ReadBasic(user.ID); + + if (userModel.Model.Contraseña != EncryptClass.Encrypt(user.Contraseña)) + { + return new(Responses.InvalidPassword); + } + + + return await Data.Accounts.Update(user.ID, AccountStatus.Disable); + + } + + + + /// + /// (ADMIN) encuentra diez (10) usuarios que coincidan con el patron + /// + /// + /// + [HttpGet("admin/search")] + public async Task> FindAll([FromQuery] string pattern, [FromHeader] string token) + { + + var (isValid, _, id, _, _) = Jwt.Validate(token); + + + if (!isValid) + { + return new(Responses.Unauthorized); + } + + + var rol = (await Data.Accounts.Read(id, new() + { + FindOn = FilterModels.FindOn.StableAccounts, + IncludeOrg = FilterModels.IncludeOrg.None + })).Model.Rol; + + + if (rol != AccountRoles.Admin) + return new(Responses.Unauthorized); + + // Obtiene el usuario + var response = await Data.Accounts.Search(pattern, new() + { + ContextOrg = 0, + OrgLevel = FilterModels.IncludeOrgLevel.Advance, + ContextUser = 0, + FindOn = FilterModels.FindOn.AllAccount, + IncludeOrg = FilterModels.IncludeOrg.Include, + IsAdmin = true + }); + + return response; + + } + + + + /// + /// Actualiza la información de una cuenta. + /// + /// Modelo + /// Token de acceso + [HttpPut("update")] + public async Task Update([FromBody] AccountModel modelo, [FromHeader] string token) + { + + // Información del token. + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + // Es invalido. + if (!isValid) + return new ResponseBase + { + Response = Responses.Unauthorized, + Message = "Token Invalido" + }; + + // Organizar el modelo. + modelo.ID = userId; + modelo.Perfil = Image.Zip(modelo.Perfil); + + if (modelo.ID <= 0 || modelo.Nombre.Any()) + return new(Responses.InvalidParam); + + return await Data.Accounts.Update(modelo); + + } + + + + /// + /// Actualiza el genero de un usuario + /// + /// Token de acceso + /// Nuevo genero + [HttpPatch("update/gender")] + public async Task Update([FromHeader] string token, [FromHeader] Genders genero) + { + + // Información del token. + var (isValid, _, id, _, _) = Jwt.Validate(token); + + // Token es invalido. + if (!isValid) + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + // Realizar actualización. + return await Data.Accounts.Update(id, genero); + + } + + + + /// + /// Actualiza la visibilidad de una cuenta. + /// + /// Token de acceso. + /// Nueva visibilidad. + [HttpPatch("update/visibility")] + public async Task Update([FromHeader] string token, [FromHeader] AccountVisibility visibility) + { + + // Información del token. + var (isValid, _, id, _, _) = Jwt.Validate(token); + + // Token es invalido. + if (!isValid) + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + // Actualización. + return await Data.Accounts.Update(id, visibility); + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Accounts/AdminController.cs b/LIN.Identity/Areas/V1/Accounts/AdminController.cs similarity index 98% rename from LIN.Identity/Areas/Accounts/AdminController.cs rename to LIN.Identity/Areas/V1/Accounts/AdminController.cs index 822d14a..a5b797a 100644 --- a/LIN.Identity/Areas/Accounts/AdminController.cs +++ b/LIN.Identity/Areas/V1/Accounts/AdminController.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.Accounts; +namespace LIN.Identity.Areas.V1.Accounts; -[Route("administrator")] +[Route("v1/administrator")] public class AdminController : ControllerBase { @@ -97,7 +97,7 @@ public async Task> Read([FromQuery] string use Response = Responses.Unauthorized, Message = "Token invalido." }; - + var rol = (await Data.Accounts.Read(userId, new() @@ -112,7 +112,7 @@ public async Task> Read([FromQuery] string use Response = Responses.Unauthorized, Message = "Tienes que ser un administrador." }; - + diff --git a/LIN.Identity/Areas/V1/Accounts/DeviceController.cs b/LIN.Identity/Areas/V1/Accounts/DeviceController.cs new file mode 100644 index 0000000..39a55e9 --- /dev/null +++ b/LIN.Identity/Areas/V1/Accounts/DeviceController.cs @@ -0,0 +1,54 @@ +namespace LIN.Identity.Areas.V1.Accounts; + + +[Route("v1/devices")] +public class DeviceController : ControllerBase +{ + + + /// + /// Obtiene la lista de dispositivos asociados a una cuenta en tiempo real + /// + /// Token de acceso + [HttpGet] + public HttpReadAllResponse GetAll([FromHeader] string token) + { + try + { + + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + if (!isValid) + { + return new ReadAllResponse + { + Message = "Token Invalido", + Response = Responses.Unauthorized + }; + } + + + var devices = (from account in AccountHub.Cuentas + where account.Key == userId + select account).FirstOrDefault().Value ?? new(); + + + var filter = (from device in devices + where device.Estado == DeviceState.Actived + select device).ToList(); + + // Retorna + return new(Responses.Success, filter ?? new()); + } + catch + { + return new(Responses.Undefined) + { + Message = "Hubo un error al obtener los dispositivos asociados." + }; + } + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Accounts/LoginLogController.cs b/LIN.Identity/Areas/V1/Accounts/LoginLogController.cs new file mode 100644 index 0000000..81be12a --- /dev/null +++ b/LIN.Identity/Areas/V1/Accounts/LoginLogController.cs @@ -0,0 +1,37 @@ +namespace LIN.Identity.Areas.V1.Accounts; + + +[Route("v1/Account/logs")] +public class LoginLogController : ControllerBase +{ + + + /// + /// Obtienes la lista de accesos asociados a una cuenta + /// + /// Token de acceso + [HttpGet] + public async Task> GetAll([FromHeader] string token) + { + + // JWT. + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + // Validación. + if (!isValid) + return new(Responses.Unauthorized) + { + Message = "Token invalido." + }; + + // Obtiene el usuario. + var result = await Data.Logins.ReadAll(userId); + + // Retorna el resultado. + return result ?? new(); + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Accounts/MailController.cs b/LIN.Identity/Areas/V1/Accounts/MailController.cs new file mode 100644 index 0000000..957cb85 --- /dev/null +++ b/LIN.Identity/Areas/V1/Accounts/MailController.cs @@ -0,0 +1,135 @@ +namespace LIN.Identity.Areas.V1.Accounts; + + +[Route("v1/mails")] +public class MailController : ControllerBase +{ + + + /// + /// Obtiene los mails asociados a una cuenta. + /// + /// Token de acceso + [HttpGet("all")] + public async Task> GetMails([FromHeader] string token) + { + + // Información del token. + var (isValid, _, id, _, _) = Jwt.Validate(token); + + // Validación del token + if (!isValid) + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + return await Data.Mails.ReadAll(id); + + } + + + + /// + /// Agrega un nuevo email a una cuenta. + /// + /// Token de acceso. + /// Contraseña de la cuenta. + /// Modelo del email. + [HttpPost("add")] + public async Task EmailAdd([FromHeader] string token, [FromHeader] string password, [FromBody] EmailModel model) + { + + // Obtener el usuario + var userData = await Data.Accounts.ReadBasic(model.UserID); + + // Evaluación de la respuesta + if (userData.Response != Responses.Success) + { + return new(Responses.NotExistAccount); + } + + // Evaluación de la contraseña + if (userData.Model.Contraseña != EncryptClass.Encrypt(password)) + return new(Responses.Unauthorized); + + // Conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + // Obtiene los mails actuales + var mails = await Data.Mails.ReadAll(userData.Model.ID, context); + + // Evalúa la respuesta + if (mails.Response != Responses.Success) + { + context.CloseActions(connectionKey); + return new(); + } + + // Este email ya esta en la cuenta? + var countEmail = mails.Models.Where(T => T.Email == model.Email).Count(); + + // Si ya existía + if (countEmail > 0) + { + context.CloseActions(connectionKey); + return new(); + } + + // Agrega el mail + var addMail = await Data.Mails.Create(new() + { + Status = EmailStatus.Unverified, + Email = model.Email, + ID = 0, + UserID = userData.Model.ID + }, context); + + + // Evalúa la respuesta + if (addMail.Response != Responses.Success) + { + context.CloseActions(connectionKey); + return new(); + } + + //Crea el LINK + var emailLink = new MailMagicLink() + { + Email = addMail.LastID, + Status = MagicLinkStatus.Activated, + Vencimiento = DateTime.Now.AddMinutes(10), + Key = KeyGen.Generate(20, "eml") + }; + + try + { + // Agrega y guarda el link + context.DataBase.MailMagicLinks.Add(emailLink); + + + var bytes = Encoding.UTF8.GetBytes(model.Email); + var mail64 = Convert.ToBase64String(bytes); + + bytes = Encoding.UTF8.GetBytes(userData.Model.Usuario); + var user64 = Convert.ToBase64String(bytes); + + await EmailWorker.SendVerification(model.Email, $"http://linaccount.somee.com/verificate/{user64}/{mail64}/{emailLink.Key}", model.Email); + return new(Responses.Success); + + } + catch + { + } + finally + { + context.DataBase.SaveChanges(); + } + + return new(); + + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Applications/ApplicationController.cs b/LIN.Identity/Areas/V1/Applications/ApplicationController.cs new file mode 100644 index 0000000..86193bf --- /dev/null +++ b/LIN.Identity/Areas/V1/Applications/ApplicationController.cs @@ -0,0 +1,119 @@ +namespace LIN.Identity.Areas.V1.Applications; + + +[Route("v1/applications")] +public class ApplicationController : ControllerBase +{ + + + /// + /// Crear nueva aplicación. + /// + /// Modelo. + /// Token de acceso. + [HttpPost] + public async Task Create([FromBody] ApplicationModel applicationModel, [FromHeader] string token) + { + + // Información del token. + var (isValid, _, userID, _, _) = Jwt.Validate(token); + + // Si el token es invalido. + if (!isValid) + return new CreateResponse() + { + Response = Responses.Unauthorized, + Message = "El token es invalido." + }; + + // Validaciones. + if (applicationModel == null || applicationModel.ApplicationUid.Trim().Length < 4 || applicationModel.Name.Trim().Length < 4) + return new CreateResponse() + { + Response = Responses.InvalidParam, + Message = "Parámetros inválidos." + }; + + // Preparar el modelo + applicationModel.ApplicationUid = applicationModel.ApplicationUid.Trim().ToLower(); + applicationModel.Name = applicationModel.Name.Trim().ToLower(); + applicationModel.AccountID = userID; + + // Crear la aplicación. + return await Data.Applications.Create(applicationModel); + + } + + + + /// + /// Obtener las aplicaciones asociadas + /// + /// Token de acceso + [HttpGet] + public async Task> GetAll([FromHeader] string token) + { + + // Información del token. + var (isValid, _, userID, _, _) = Jwt.Validate(token); + + // Si el token es invalido. + if (!isValid) + return new ReadAllResponse() + { + Response = Responses.Unauthorized, + Message = "El token es invalido." + }; + + // Obtiene la data. + var data = await Data.Applications.ReadAll(userID); + + return data; + + } + + + + /// + /// Crear acceso permitido a una app. + /// + /// Token de acceso. + /// ID de la aplicación. + /// ID del integrante. + [HttpPut] + public async Task> InsertAllow([FromHeader] string token, [FromHeader] int appId, [FromHeader] int accountId) + { + + // Información del token. + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + // Si el token es invalido. + if (!isValid) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "El token es invalido." + }; + + // Respuesta de Iam. + var iam = await Services.Iam.Applications.ValidateAccess(userId, appId); + + // Validación de Iam + if (iam.Model != IamLevels.Privileged) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "No tienes autorización para modificar este recurso." + }; + + + // Enviar la actualización + var data = await Data.Applications.AllowTo(appId, accountId); + + return data; + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/V1/Authentication/AuthenticationController.cs new file mode 100644 index 0000000..067e2e8 --- /dev/null +++ b/LIN.Identity/Areas/V1/Authentication/AuthenticationController.cs @@ -0,0 +1,123 @@ +using LIN.Identity.Services.Login; + +namespace LIN.Identity.Areas.V1.Authentication; + + +[Route("v1/authentication")] +public class AuthenticationController : ControllerBase +{ + + + /// + /// Inicia una sesión de usuario + /// + /// Usuario único + /// Contraseña del usuario + /// Key de aplicación + [HttpGet("login")] + public async Task> Login([FromQuery] string user, [FromQuery] string password, [FromHeader] string application) + { + + // Validación de parámetros. + if (!user.Any() || !password.Any() || !application.Any()) + return new(Responses.InvalidParam); + + // Obtiene el usuario. + var response = await Data.Accounts.Read(user, new() + { + SensibleInfo = true, + IsAdmin = true, + IncludeOrg = FilterModels.IncludeOrg.Include, + OrgLevel = FilterModels.IncludeOrgLevel.Advance, + FindOn = FilterModels.FindOn.StableAccounts + }); + + // Validación al obtener el usuario + switch (response.Response) + { + // Correcto + case Responses.Success: + break; + + // Incorrecto + default: + return new(response.Response) + { + Message = "Hubo un error grave." + }; + } + + + // Estrategia de login + LoginService strategy; + + // Definir la estrategia + strategy = response.Model.OrganizationAccess == null ? new LoginNormal(response.Model, application, password) + : new LoginOnOrg(response.Model, application, password); + + // Respuesta del login + var loginResponse = await strategy.Login(); + + + // Respuesta + if (loginResponse.Response != Responses.Success) + return new ReadOneResponse() + { + Message = loginResponse.Message, + Response = loginResponse.Response + }; + + + // Genera el token + var token = Jwt.Generate(response.Model, 0); + + + response.Token = token; + return response; + + } + + + + /// + /// Inicia una sesión de usuario por medio del token + /// + /// Token de acceso + [HttpGet("LoginWithToken")] + public async Task> LoginWithToken([FromHeader] string token) + { + + // Información del token de acceso. + var (isValid, _, user, _, _) = Jwt.Validate(token); + + // Si el token es invalido. + if (!isValid) + return new(Responses.InvalidParam) + { + Message = "El token proporcionado no es valido." + }; + + // Obtiene el usuario + var response = await Data.Accounts.Read(user, new() + { + SensibleInfo = true, + IsAdmin = true, + IncludeOrg = FilterModels.IncludeOrg.Include, + OrgLevel = FilterModels.IncludeOrgLevel.Advance, + FindOn = FilterModels.FindOn.StableAccounts + }); + + if (response.Response != Responses.Success) + return new(response.Response); + + if (response.Model.Estado != AccountStatus.Normal) + return new(Responses.NotExistAccount); + + response.Token = token; + return response; + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Authentication/IntentsController.cs b/LIN.Identity/Areas/V1/Authentication/IntentsController.cs new file mode 100644 index 0000000..802a2fd --- /dev/null +++ b/LIN.Identity/Areas/V1/Authentication/IntentsController.cs @@ -0,0 +1,58 @@ +namespace LIN.Identity.Areas.V1.Authentication; + + +[Route("v1/Intents")] +public class IntentsController : ControllerBase +{ + + /// + /// Obtiene la lista de intentos de llaves de paso están activos. + /// + /// Token de acceso + [HttpGet] + public HttpReadAllResponse GetAll([FromHeader] string token) + { + try + { + + // Info del token + var (isValid, user, _, _, _) = Jwt.Validate(token); + + // Si el token es invalido + if (!isValid) + return new ReadAllResponse + { + Message = "Invalid Token", + Response = Responses.Unauthorized + }; + + + // Cuenta + var account = (from a in PassKeyHub.Attempts + where a.Key == user.ToLower() + select a).FirstOrDefault().Value ?? new(); + + // Hora actual + var timeNow = DateTime.Now; + + // Intentos + var intentos = (from I in account + where I.Status == PassKeyStatus.Undefined + where I.Expiración > timeNow + select I).ToList(); + + // Retorna + return new(Responses.Success, intentos); + } + catch + { + return new(Responses.Undefined) + { + Message = "Hubo un error al obtener los intentos de passkey" + }; + } + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Organizations/ApplicationOrgsController.cs b/LIN.Identity/Areas/V1/Organizations/ApplicationOrgsController.cs new file mode 100644 index 0000000..8c15621 --- /dev/null +++ b/LIN.Identity/Areas/V1/Organizations/ApplicationOrgsController.cs @@ -0,0 +1,164 @@ +namespace LIN.Identity.Areas.V1.Organizations; + + +[Route("v1/orgs/applications")] +public class ApplicationOrgsController : ControllerBase +{ + + + /// + /// Obtiene la lista de aplicaciones asociadas a una organización + /// + /// Token de acceso + [HttpGet] + public async Task> ReadApps([FromHeader] string token) + { + + // Token + var (isValid, _, _, orgId, _) = Jwt.Validate(token); + + // Token es invalido + if (!isValid) + return new ReadAllResponse + { + Message = "Token invalido.", + Response = Responses.Unauthorized + }; + + + // Si no tiene ninguna organización + if (orgId <= 0) + return new ReadAllResponse + { + Message = "No estas vinculado con ninguna organización.", + Response = Responses.Unauthorized + }; + + + // Obtiene las aplicaciones + var org = await Data.Organizations.Organizations.ReadApps(orgId); + + // Su no se encontraron aplicaciones + if (org.Response != Responses.Success) + return new ReadAllResponse + { + Message = "No found Organization", + Response = Responses.Unauthorized + }; + + // Conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + context.CloseActions(connectionKey); + + // Retorna el resultado + return org; + + } + + + + /// + /// Insertar una aplicación en una organización + /// + /// UId de la aplicación + /// Token de acceso + [HttpPost("insert")] + public async Task InsertApp([FromQuery] string appUid, [FromHeader] string token) + { + + // Token + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + + // Si el token es invalido + if (!isValid) + return new CreateResponse + { + Message = "Token invalido", + Response = Responses.Unauthorized + }; + + // Información del usuario + var userData = await Data.Accounts.ReadBasic(userId); + + // Si no existe el usuario + if (userData.Response != Responses.Success) + return new CreateResponse + { + Message = "No se encontró el usuario, talvez fue eliminado o desactivado.", + Response = Responses.NotExistAccount + }; + + + // Si no tiene organización + if (userData.Model.OrganizationAccess == null || userData.Model.OrganizationAccess?.Organization == null) + return new CreateResponse + { + Message = $"El usuario '{userData.Model.Usuario}' no pertenece a una organización.", + Response = Responses.Unauthorized + }; + + // Si el usuario no es admin en la organización + if (!userData.Model.OrganizationAccess.Rol.IsAdmin()) + return new CreateResponse + { + Message = $"El usuario '{userData.Model.Usuario}' no tiene un rol administrador en la organización '{userData.Model.OrganizationAccess.Organization.Name}'", + Response = Responses.Unauthorized + }; + + // Crea la aplicación en la organización + var res = await Data.Organizations.Applications.Create(appUid, userData.Model.OrganizationAccess.Organization.ID); + + // Si hubo une error + if (res.Response != Responses.Success) + return new CreateResponse + { + Message = $"Hubo un error al insertar esta aplicación en la lista blanca permitidas de {userData.Model.OrganizationAccess.Organization.Name}", + Response = Responses.Unauthorized + }; + + + // Conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + context.CloseActions(connectionKey); + + // Retorna el resultado + return new CreateResponse + { + LastID = res.LastID, + Message = "", + Response = Responses.Success + }; + } + + + + /// + /// Buscar aplicaciones que no están vinculadas a una organización por medio del un parámetro + /// + /// Parámetro de búsqueda + /// Token de acceso + [HttpGet("search")] + public async Task> Search([FromQuery] string param, [FromHeader] string token) + { + + // Token + var (isValid, _, _, orgId, _) = Jwt.Validate(token); + + // Valida el token + if (!isValid || orgId <= 0) + { + return new(Responses.Unauthorized); + } + + // Encuentra las apps + var finds = await Data.Organizations.Applications.Search(param, orgId); + + return finds; + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/V1/Organizations/MemberController.cs similarity index 98% rename from LIN.Identity/Areas/Organizations/MemberController.cs rename to LIN.Identity/Areas/V1/Organizations/MemberController.cs index 414edbb..a916d95 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/V1/Organizations/MemberController.cs @@ -1,9 +1,9 @@ using LIN.Identity.Validations; -namespace LIN.Identity.Areas.Organizations; +namespace LIN.Identity.Areas.V1.Organizations; -[Route("orgs/members")] +[Route("v1/orgs/members")] public class MemberController : ControllerBase { @@ -25,7 +25,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr Response = Responses.InvalidParam, Message = "Uno o varios parámetros inválidos." }; - + // Visibilidad oculta modelo.Visibilidad = AccountVisibility.Hidden; @@ -146,7 +146,7 @@ public async Task> ReadAll([FromHeader] string Message = "El token es invalido.", Response = Responses.Unauthorized }; - + // Obtiene los miembros. var members = await Data.Organizations.Members.ReadAll(orgID); @@ -157,7 +157,7 @@ public async Task> ReadAll([FromHeader] string Message = "No se encontró la organización.", Response = Responses.Unauthorized }; - + // Retorna el resultado return members; diff --git a/LIN.Identity/Areas/V1/Organizations/OrganizationController.cs b/LIN.Identity/Areas/V1/Organizations/OrganizationController.cs new file mode 100644 index 0000000..5915305 --- /dev/null +++ b/LIN.Identity/Areas/V1/Organizations/OrganizationController.cs @@ -0,0 +1,234 @@ +using LIN.Identity.Validations; + +namespace LIN.Identity.Areas.V1.Organizations; + + +[Route("v1/organizations")] +public class OrganizationsController : ControllerBase +{ + + + /// + /// Crea una nueva organización. + /// + /// Modelo de la organización y el usuario administrador + [HttpPost("create")] + public async Task Create([FromBody] OrganizationModel modelo) + { + + // Comprobaciones + if (modelo == null || modelo.Domain.Length <= 0 || modelo.Name.Length <= 0 || modelo.Members.Count <= 0) + return new(Responses.InvalidParam); + + + // Conexi�n + var (context, connectionKey) = Conexión.GetOneConnection(); + + + // organización del modelo + modelo.ID = 0; + modelo.AppList = new(); + + modelo.Members[0].Member = Account.Process(modelo.Members[0].Member); + foreach (var member in modelo.Members) + { + member.Rol = OrgRoles.SuperManager; + member.Organization = modelo; + } + + // Creaci�n de la organización + var response = await Data.Organizations.Organizations.Create(modelo, context); + + // Evaluaci�n + if (response.Response != Responses.Success) + return new(response.Response); + + context.CloseActions(connectionKey); + + // Retorna el resultado + return new CreateResponse() + { + LastID = response.Model.ID, + Response = Responses.Success, + Message = "Success" + }; + + } + + + + /// + /// Obtiene una organización por medio del Id. + /// + /// ID de la organización + /// Token de acceso + [HttpGet("read/id")] + public async Task> ReadOneByID([FromQuery] int id, [FromHeader] string token) + { + + // Parámetros + if (id <= 0) + return new(Responses.InvalidParam); + + // Validar el token + var (isValid, _, _, orgID, _) = Jwt.Validate(token); + + + if (!isValid) + return new(Responses.Unauthorized); + + + // Obtiene la organización + var response = await Data.Organizations.Organizations.Read(id); + + // Organización no encontrada. + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = Responses.NotRows, + Message = "No se encontró la organización." + }; + + // No es publica y no pertenece a ella + if (!response.Model.IsPublic && orgID != response.Model.ID) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "Esta organización es privada y tu usuario no esta vinculado a ella.", + Model = new() + { + ID = response.Model.ID, + IsPublic = false, + Name = "Organización privada" + } + }; + + return new ReadOneResponse() + { + Response = Responses.Success, + Model = response.Model + }; + + + } + + + + /// + /// Actualiza si una organización tiene lista blanca. + /// + /// Toke de acceso administrador + /// Nuevo estado + [HttpPatch("update/whitelist")] + public async Task Update([FromHeader] string token, [FromQuery] bool haveWhite) + { + + + var (isValid, _, userID, _, _) = Jwt.Validate(token); + + + if (!isValid) + return new(Responses.Unauthorized); + + + var userContext = await Data.Accounts.ReadBasic(userID); + + // Error al encontrar el usuario + if (userContext.Response != Responses.Success) + { + return new ResponseBase + { + Message = "No se encontr� un usuario valido.", + Response = Responses.Unauthorized + }; + } + + // Si el usuario no tiene una organización + if (userContext.Model.OrganizationAccess == null) + { + return new ResponseBase + { + Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", + Response = Responses.Unauthorized + }; + } + + // Verificaci�n del rol dentro de la organización + if (!userContext.Model.OrganizationAccess.Rol.IsAdmin()) + { + return new ResponseBase + { + Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de la lista blanca de esta organización.", + Response = Responses.Unauthorized + }; + } + + + var response = await Data.Organizations.Organizations.UpdateState(userContext.Model.OrganizationAccess.Organization.ID, haveWhite); + + // Retorna el resultado + return response; + + } + + + + /// + /// Actualiza si los usuarios no admins de una organización tienen acceso a su cuenta. + /// + /// Token de acceso administrador + /// Nuevo estado + [HttpPatch("update/access")] + public async Task UpdateAccess([FromHeader] string token, [FromQuery] bool state) + { + + + var (isValid, _, userID, _, _) = Jwt.Validate(token); + + + if (!isValid) + return new(Responses.Unauthorized); + + + var userContext = await Data.Accounts.ReadBasic(userID); + + // Error al encontrar el usuario + if (userContext.Response != Responses.Success) + { + return new ResponseBase + { + Message = "No se encontr� un usuario valido.", + Response = Responses.Unauthorized + }; + } + + // Si el usuario no tiene una organización + if (userContext.Model.OrganizationAccess == null) + { + return new ResponseBase + { + Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", + Response = Responses.Unauthorized + }; + } + + // Verificaci�n del rol dentro de la organización + if (userContext.Model.OrganizationAccess.Rol != OrgRoles.SuperManager) + { + return new ResponseBase + { + Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de accesos de esta organización.", + Response = Responses.Unauthorized + }; + } + + + var response = await Data.Organizations.Organizations.UpdateAccess(userContext.Model.OrganizationAccess.Organization.ID, state); + + // Retorna el resultado + return response; + + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Security/Security.cs b/LIN.Identity/Areas/V1/Security/Security.cs new file mode 100644 index 0000000..c2a9c1f --- /dev/null +++ b/LIN.Identity/Areas/V1/Security/Security.cs @@ -0,0 +1,312 @@ +namespace LIN.Identity.Areas.V1.Security; + + +[Route("v1/security")] +public class Security : ControllerBase +{ + + + /// + /// Olvidar contraseña + /// + /// Usuario + [HttpPost("password/forget")] + public async Task> ForgetPassword([FromQuery] string user) + { + + // Nulo o vacío + if (string.IsNullOrWhiteSpace(user)) + return new(Responses.InvalidParam); + + + // Obtiene la conexión + var (context, contextKey) = Conexión.GetOneConnection(); + + // Obtiene la información de usuario + var userResponse = await Data.Accounts.ReadBasic(user, context); + + // Evalúa la respuesta + if (userResponse.Response != Responses.Success) + { + context.CloseActions(contextKey); + return new(userResponse.Response); + } + + // Modelo del usuario + var userData = userResponse.Model; + + // Obtiene los emails asociados + var emailResponse = await Data.Mails.ReadVerifiedEmails(userData.ID, context); + + // Evalúa la respuesta + if (emailResponse.Response != Responses.Success) + { + context.CloseActions(contextKey); + return new(emailResponse.Response); + } + + // Modelos de emails + var emailsData = emailResponse.Models; + + // Emails verificados + var verifiedMail = emailsData.FirstOrDefault(mail => mail.IsDefault); + + // Valida + if (verifiedMail == null || !Mail.Validar(verifiedMail.Email ?? string.Empty)) + { + context.CloseActions(contextKey); + return new(); + } + + // Modelo del link + var link = new UniqueLink() + { + Key = KeyGen.Generate(30, string.Empty), + AccountID = userData.ID, + Status = MagicLinkStatus.Activated, + Vencimiento = DateTime.Now.AddMinutes(30) + }; + + // Crea el nuevo link + var linkResponse = await Data.Links.Create(link); + + // Evalúa + if (linkResponse.Response != Responses.Success) + { + context.CloseActions(contextKey); + return new(Responses.NotRows); + } + + + // Envía el correo + await EmailWorker.SendPassword(verifiedMail?.Email!, userData.Usuario, $"http://linapps.co/resetpassword/{userData.ID}/{link.Key}"); + + + return new(Responses.Success, new() + { + Email = CensorEmail(verifiedMail?.Email ?? "") + }); + } + + + + private static string CensorEmail(string email) + { + var atIndex = email.IndexOf('@'); + + if (atIndex >= 0) + { + var dotIndex = email.IndexOf('.'); + if (dotIndex > atIndex + 3) + { + var username = email.Substring(0, atIndex - 3); // Censura los últimos 3 caracteres antes del "@". + var domain = email.Substring(atIndex); // Censura el dominio antes del primer punto. + return $"{username}***{domain}"; + } + } + return email; + } + + + + + + + + /// + /// Reestablece la contraseña + /// + /// Llave del link + /// Nuevo modelo + [HttpPatch("password/reset")] + public async Task ResetPassword([FromHeader] string key, [FromBody] UpdatePasswordModel modelo) + { + + // Nulo o vacío + if (string.IsNullOrWhiteSpace(key)) + return new(Responses.InvalidParam); + + // Link + var link = await Data.Links.ReadOneAnChange(key); + + // Evalúa + if (link.Response != Responses.Success) + { + return new(Responses.Unauthorized); + } + + // Establece el id + modelo.Account = link.Model.AccountID; + + // Respuesta + var updateResponse = await Data.Accounts.Update(modelo); + + if (updateResponse.Response != Responses.Success) + return new(); + + return new(Responses.Success); + } + + + + /// + /// Verifica un correo + /// + /// Key de acceso LINK + [HttpPost("mails/verify")] + public async Task VerifyEmail([FromHeader] string key) + { + + // Obtiene una conexión + var (context, contextKey) = Conexión.GetOneConnection(); + + // Obtiene un link + var linkResponse = await Data.MailLinks.ReadAndDisable(key); + + // Evalúa + if (linkResponse.Response != Responses.Success) + { + context.CloseActions(contextKey); + return new(); + } + + // Modelo del link + var linkData = linkResponse.Model; + + + // Obtiene el email + var emailResponse = await Data.Mails.Read(linkData.Email, context); + + // Evalúa + if (emailResponse.Response != Responses.Success) + { + context.CloseActions(contextKey); + return new(); + } + + // Modelo del mail + var emailData = emailResponse.Model; + + // Actualiza el estado del mail + var updateState = await Data.Mails.UpdateState(emailData.ID, EmailStatus.Verified, context); + + // Evalúa + if (updateState.Response != Responses.Success) + { + context.CloseActions(contextKey); + return new(); + } + + + // Comprobación de email default + { + + // Respuesta + var emails = await Data.Mails.ReadVerifiedEmails(emailData.UserID); + + // Evalúa + if (emails.Response == Responses.Success) + { + // Existe el mail default + var haveDefault = emails.Models.Where(E => E.IsDefault).Any(); + + // Si no existe + if (!haveDefault) + { + // Establece el mail actual + var res = await Data.Mails.SetDefaultEmail(emailData.UserID, emailData.ID); + + } + + } + + } + + return new(Responses.Success); + } + + + + /// + /// Reenvía el correo para la activación + /// + /// Contraseña de la cuenta + /// Modelo del email + [HttpPost("mails/resend")] + public async Task EmailResend([FromHeader] int mailID, [FromHeader] string token) + { + + + var (isValid, _, userID, _, _) = Jwt.Validate(token); + + if (!isValid) + { + return new(Responses.Unauthorized); + } + + var mailResponse = await Data.Mails.Read(mailID); + + // Evaluación de la respuesta + if (mailResponse.Response != Responses.Success) + { + return new(Responses.NotRows); + } + + var mailData = mailResponse.Model; + + if (mailData.Status == EmailStatus.Verified) + { + return new(Responses.Undefined); + } + + + + + // Crea el LINK + var emailLink = new MailMagicLink() + { + Email = mailData.ID, + Status = MagicLinkStatus.Activated, + Vencimiento = DateTime.Now.AddMinutes(10), + Key = KeyGen.Generate(20, "ml") + }; + + + try + { + + var add = await Data.MailLinks.Create(emailLink); + + + if (add.Response != Responses.Success) + { + return new(); + } + + var user = (await Data.Accounts.ReadBasic(userID)).Model; + + var bytes = Encoding.UTF8.GetBytes(mailData.Email); + var mail64 = Convert.ToBase64String(bytes); + + bytes = Encoding.UTF8.GetBytes(user.Usuario); + var user64 = Convert.ToBase64String(bytes); + + await EmailWorker.SendVerification(mailData.Email, $"http://linaccount.somee.com/verificate/{user64}/{mail64}/{emailLink.Key}", mailData.Email); + return new(Responses.Success); + + } + catch + { + + } + finally + { + } + + return new(); + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/V3/Accounts/AccountController.cs similarity index 99% rename from LIN.Identity/Areas/Accounts/AccountController.cs rename to LIN.Identity/Areas/V3/Accounts/AccountController.cs index f598d45..c68cf5f 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/V3/Accounts/AccountController.cs @@ -1,8 +1,8 @@ using LIN.Identity.Validations; -namespace LIN.Identity.Areas.Accounts; +namespace LIN.Identity.Areas.V3; -[Route("account")] +[Route("v3/account")] public class AccountController : ControllerBase { @@ -201,7 +201,7 @@ public async Task> ReadAll([FromBody] List Update([FromHeader] string token, [FromHeade // Token es invalido. if (!isValid) - return new() { + return new() + { Response = Responses.Unauthorized, Message = "Token invalido." }; - + // Actualización. return await Data.Accounts.Update(id, visibility); diff --git a/LIN.Identity/Areas/V3/Accounts/AdminController.cs b/LIN.Identity/Areas/V3/Accounts/AdminController.cs new file mode 100644 index 0000000..4bfcf83 --- /dev/null +++ b/LIN.Identity/Areas/V3/Accounts/AdminController.cs @@ -0,0 +1,266 @@ +namespace LIN.Identity.Areas.V3; + + +[Route("v3/administrator")] +public class AdminController : ControllerBase +{ + + + + /// + /// Obtiene la información de usuario. + /// + /// ID del usuario + /// Token de acceso + [HttpGet("read/id")] + public async Task> Read([FromQuery] int id, [FromHeader] string token) + { + + if (id <= 0) + return new(Responses.InvalidParam); + + + var (isValid, _, user, orgID, _) = Jwt.Validate(token); + + if (!isValid) + { + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + } + + var rol = (await Data.Accounts.Read(user, new() + { + IncludeOrg = FilterModels.IncludeOrg.None, + FindOn = FilterModels.FindOn.StableAccounts + })).Model.Rol; + + if (rol != AccountRoles.Admin) + { + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "Tienes que ser un administrador." + }; + } + + + // Obtiene el usuario + var response = await Data.Accounts.Read(id, new() + { + SensibleInfo = false, + ContextOrg = orgID, + ContextUser = user, + IsAdmin = true, + FindOn = FilterModels.FindOn.StableAccounts, + IncludeOrg = FilterModels.IncludeOrg.Include, + OrgLevel = FilterModels.IncludeOrgLevel.Advance + }); + + // Si es erróneo + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = response.Response, + Model = new() + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtiene la información de usuario. + /// + /// Usuario único + /// Token de acceso + [HttpGet("read/user")] + public async Task> Read([FromQuery] string user, [FromHeader] string token) + { + + // Validar el parámetro. + if (string.IsNullOrWhiteSpace(user)) + return new(Responses.InvalidParam); + + // Información del token. + var (isValid, _, userId, orgId, _) = Jwt.Validate(token); + + // Token es invalido. + if (!isValid) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + + + var rol = (await Data.Accounts.Read(userId, new() + { + IncludeOrg = FilterModels.IncludeOrg.None, + FindOn = FilterModels.FindOn.StableAccounts + })).Model.Rol; + + if (rol != AccountRoles.Admin) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "Tienes que ser un administrador." + }; + + + + + var response = await Data.Accounts.Read(user, new() + { + SensibleInfo = false, + ContextOrg = orgId, + IsAdmin = true, + ContextUser = userId, + FindOn = FilterModels.FindOn.StableAccounts, + IncludeOrg = FilterModels.IncludeOrg.Include, + OrgLevel = FilterModels.IncludeOrgLevel.Advance + }); + + + + // Si es erróneo + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = response.Response, + Model = new() + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Actualiza la contraseña de una cuenta + /// + /// Modelo de actualización + /// Token de acceso + [HttpPatch("update/password")] + public async Task Update([FromBody] UpdatePasswordModel modelo, [FromHeader] string token) + { + + if (modelo.OldPassword.Length < 4 || modelo.NewPassword.Length < 4) + return new(Responses.InvalidParam); + + + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + + if (!isValid) + { + return new(Responses.Unauthorized); + } + + modelo.Account = userId; + + var actualData = await Data.Accounts.ReadBasic(modelo.Account); + + if (actualData.Response != Responses.Success) + return new(Responses.NotExistAccount); + + var oldEncrypted = actualData.Model.Contraseña; + + + if (oldEncrypted != actualData.Model.Contraseña) + { + return new ResponseBase(Responses.InvalidPassword); + } + + return await Data.Accounts.Update(modelo); + + } + + + + /// + /// Elimina una cuenta + /// + /// Token de acceso + [HttpDelete("delete")] + public async Task Delete([FromHeader] string token) + { + + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + if (!isValid) + return new ResponseBase + { + Response = Responses.Unauthorized, + Message = "Token invalido" + }; + + if (userId <= 0) + return new(Responses.InvalidParam); + + var response = await Data.Accounts.Delete(userId); + return response; + } + + + + /// + /// Desactiva una cuenta + /// + /// Modelo + [HttpPatch("disable")] + public async Task Disable([FromBody] AccountModel user) + { + + if (user.ID <= 0) + { + return new(Responses.ExistAccount); + } + + // Modelo de usuario de la BD + var userModel = await Data.Accounts.ReadBasic(user.ID); + + if (userModel.Model.Contraseña != EncryptClass.Encrypt(user.Contraseña)) + { + return new(Responses.InvalidPassword); + } + + + return await Data.Accounts.Update(user.ID, AccountStatus.Disable); + + } + + + + /// + /// Actualiza la visibilidad de una cuenta + /// + /// Token de acceso + /// Nueva visibilidad + [HttpPatch("update/visibility")] + public async Task Update([FromHeader] string token, [FromHeader] AccountVisibility visibility) + { + + + var (isValid, _, id, _, _) = Jwt.Validate(token); + + if (!isValid) + { + return new(Responses.Unauthorized); + } + + return await Data.Accounts.Update(id, visibility); + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Accounts/DeviceController.cs b/LIN.Identity/Areas/V3/Accounts/DeviceController.cs similarity index 95% rename from LIN.Identity/Areas/Accounts/DeviceController.cs rename to LIN.Identity/Areas/V3/Accounts/DeviceController.cs index 81a707d..d46602c 100644 --- a/LIN.Identity/Areas/Accounts/DeviceController.cs +++ b/LIN.Identity/Areas/V3/Accounts/DeviceController.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.Accounts; +namespace LIN.Identity.Areas.V3; -[Route("devices")] +[Route("v3/devices")] public class DeviceController : ControllerBase { diff --git a/LIN.Identity/Areas/Accounts/LoginLogController.cs b/LIN.Identity/Areas/V3/Accounts/LoginLogController.cs similarity index 92% rename from LIN.Identity/Areas/Accounts/LoginLogController.cs rename to LIN.Identity/Areas/V3/Accounts/LoginLogController.cs index 143a22a..3e30b79 100644 --- a/LIN.Identity/Areas/Accounts/LoginLogController.cs +++ b/LIN.Identity/Areas/V3/Accounts/LoginLogController.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.Accounts; +namespace LIN.Identity.Areas.V3; -[Route("Account/logs")] +[Route("v3/Account/logs")] public class LoginLogController : ControllerBase { diff --git a/LIN.Identity/Areas/Accounts/MailController.cs b/LIN.Identity/Areas/V3/Accounts/MailController.cs similarity index 98% rename from LIN.Identity/Areas/Accounts/MailController.cs rename to LIN.Identity/Areas/V3/Accounts/MailController.cs index 91cc575..5c1d792 100644 --- a/LIN.Identity/Areas/Accounts/MailController.cs +++ b/LIN.Identity/Areas/V3/Accounts/MailController.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.Accounts; +namespace LIN.Identity.Areas.V3; -[Route("mails")] +[Route("v3/mails")] public class MailController : ControllerBase { diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/V3/Applications/ApplicationController.cs similarity index 98% rename from LIN.Identity/Areas/Applications/ApplicationController.cs rename to LIN.Identity/Areas/V3/Applications/ApplicationController.cs index 57d08a9..90a9668 100644 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/V3/Applications/ApplicationController.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.Applications; +namespace LIN.Identity.Areas.V3; -[Route("applications")] +[Route("v3/applications")] public class ApplicationController : ControllerBase { diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/V3/Authentication/AuthenticationController.cs similarity index 97% rename from LIN.Identity/Areas/Authentication/AuthenticationController.cs rename to LIN.Identity/Areas/V3/Authentication/AuthenticationController.cs index 9c6ff1b..f4ad975 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/V3/Authentication/AuthenticationController.cs @@ -1,9 +1,9 @@ using LIN.Identity.Services.Login; -namespace LIN.Identity.Areas.Authentication; +namespace LIN.Identity.Areas.V3; -[Route("authentication")] +[Route("v3/authentication")] public class AuthenticationController : ControllerBase { diff --git a/LIN.Identity/Areas/Authentication/IntentsController.cs b/LIN.Identity/Areas/V3/Authentication/IntentsController.cs similarity index 95% rename from LIN.Identity/Areas/Authentication/IntentsController.cs rename to LIN.Identity/Areas/V3/Authentication/IntentsController.cs index 128abe5..cef55d1 100644 --- a/LIN.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Identity/Areas/V3/Authentication/IntentsController.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.Authentication; +namespace LIN.Identity.Areas.V3; -[Route("Intents")] +[Route("v3/Intents")] public class IntentsController : ControllerBase { diff --git a/LIN.Identity/Areas/Organizations/ApplicationOrgsController.cs b/LIN.Identity/Areas/V3/Organizations/ApplicationOrgsController.cs similarity index 98% rename from LIN.Identity/Areas/Organizations/ApplicationOrgsController.cs rename to LIN.Identity/Areas/V3/Organizations/ApplicationOrgsController.cs index 4d00d6d..5637fbd 100644 --- a/LIN.Identity/Areas/Organizations/ApplicationOrgsController.cs +++ b/LIN.Identity/Areas/V3/Organizations/ApplicationOrgsController.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.Organizations; +namespace LIN.Identity.Areas.V3; -[Route("orgs/applications")] +[Route("v3/orgs/applications")] public class ApplicationOrgsController : ControllerBase { diff --git a/LIN.Identity/Areas/V3/Organizations/MemberController.cs b/LIN.Identity/Areas/V3/Organizations/MemberController.cs new file mode 100644 index 0000000..f0e7e3b --- /dev/null +++ b/LIN.Identity/Areas/V3/Organizations/MemberController.cs @@ -0,0 +1,168 @@ +using LIN.Identity.Validations; + +namespace LIN.Identity.Areas.V3; + + +[Route("v3/orgs/members")] +public class MemberController : ControllerBase +{ + + + /// + /// Crea un nuevo miembro en una organización. + /// + /// Modelo de la cuenta + /// Token de acceso de un administrador + /// Rol asignado + [HttpPost] + public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] OrgRoles rol) + { + + // Validación del modelo. + if (modelo == null || !modelo.Usuario.Trim().Any() || !modelo.Nombre.Trim().Any()) + return new CreateResponse + { + Response = Responses.InvalidParam, + Message = "Uno o varios parámetros inválidos." + }; + + + // Visibilidad oculta + modelo.Visibilidad = AccountVisibility.Hidden; + + // Organización del modelo + modelo = Account.Process(modelo); + + + // Establece la contraseña default + var password = $"ChangePwd@{modelo.Creación:dd.MM.yyyy}"; + + // Contraseña default + modelo.Contraseña = EncryptClass.Encrypt(password); + + // Validación del token + var (isValid, _, userID, _, _) = Jwt.Validate(token); + + // Token es invalido + if (!isValid) + { + return new CreateResponse + { + Message = "Token invalido.", + Response = Responses.Unauthorized + }; + } + + + // Obtiene el usuario + var userContext = await Data.Accounts.ReadBasic(userID); + + // Error al encontrar el usuario + if (userContext.Response != Responses.Success) + { + return new CreateResponse + { + Message = "No se encontró un usuario valido.", + Response = Responses.Unauthorized + }; + } + + // Si el usuario no tiene una organización + if (userContext.Model.OrganizationAccess == null) + { + return new CreateResponse + { + Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", + Response = Responses.Unauthorized + }; + } + + // Verificación del rol dentro de la organización + if (!userContext.Model.OrganizationAccess.Rol.IsAdmin()) + { + return new CreateResponse + { + Message = $"El usuario '{userContext.Model.Usuario}' no puede crear nuevos usuarios en esta organización.", + Response = Responses.Unauthorized + }; + } + + + // Verificación del rol dentro de la organización + if (userContext.Model.OrganizationAccess.Rol.IsGretter(rol)) + { + return new CreateResponse + { + Message = $"El '{userContext.Model.Usuario}' no puede crear nuevos usuarios con mas privilegios de los propios.", + Response = Responses.Unauthorized + }; + } + + + // ID de la organización + var org = userContext.Model.OrganizationAccess.Organization.ID; + + + // Conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + // Creación del usuario + var response = await Data.Organizations.Members.Create(modelo, org, rol, context); + + // Evaluación + if (response.Response != Responses.Success) + return new(response.Response); + + // Cierra la conexión + context.CloseActions(connectionKey); + + // Retorna el resultado + return new CreateResponse() + { + LastID = response.Model.ID, + Response = Responses.Success, + Message = "Success" + }; + + } + + + + /// + /// Obtiene la lista de integrantes asociados a una organización. + /// + /// Token de acceso + [HttpGet] + public async Task> ReadAll([FromHeader] string token) + { + + // Información del token. + var (isValid, _, _, orgID, _) = Jwt.Validate(token); + + // Si el token es invalido. + if (!isValid) + return new ReadAllResponse + { + Message = "El token es invalido.", + Response = Responses.Unauthorized + }; + + // Obtiene los miembros. + var members = await Data.Organizations.Members.ReadAll(orgID); + + // Error al obtener los integrantes. + if (members.Response != Responses.Success) + return new ReadAllResponse + { + Message = "No se encontró la organización.", + Response = Responses.Unauthorized + }; + + // Retorna el resultado + return members; + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/V3/Organizations/OrganizationController.cs similarity index 99% rename from LIN.Identity/Areas/Organizations/OrganizationController.cs rename to LIN.Identity/Areas/V3/Organizations/OrganizationController.cs index 31a8da0..3bc99aa 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/V3/Organizations/OrganizationController.cs @@ -1,9 +1,9 @@ using LIN.Identity.Validations; -namespace LIN.Identity.Areas.Organizations; +namespace LIN.Identity.Areas.V3; -[Route("organizations")] +[Route("v3/organizations")] public class OrganizationsController : ControllerBase { diff --git a/LIN.Identity/Areas/Security/Security.cs b/LIN.Identity/Areas/V3/Security/Security.cs similarity index 99% rename from LIN.Identity/Areas/Security/Security.cs rename to LIN.Identity/Areas/V3/Security/Security.cs index f048855..2f46284 100644 --- a/LIN.Identity/Areas/Security/Security.cs +++ b/LIN.Identity/Areas/V3/Security/Security.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.Security; +namespace LIN.Identity.Areas.V3; -[Route("security")] +[Route("v3/security")] public class Security : ControllerBase { diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index 88a208b..0c1b6e6 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -1,4 +1,7 @@ using LIN.Identity.Data; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.OpenApi.Models; +using System.ComponentModel; { @@ -24,6 +27,11 @@ + builder.Services.AddControllers(options => + { + options.Conventions.Add(new GroupingByNamespaceConvention()); + }); + var sqlConnection = builder.Configuration["ConnectionStrings:somee"] ?? string.Empty; @@ -38,7 +46,42 @@ builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); + builder.Services.AddSwaggerGen((config) => + { + var titleBase = "LIN Identity"; + var description = "IDENTITY"; + var TermsOfService = new Uri("http://linapps.co/"); + var License = new OpenApiLicense() + { + Name = "MIT" + }; + + var Contact = new OpenApiContact() + { + Name = "Alexander Giraldo", + Email = "", + Url = new Uri("http://linapps.co/") + }; + + config.SwaggerDoc("v1", new OpenApiInfo + { + Version = "v1", + Title = titleBase + " v1", + Description = description, + TermsOfService = TermsOfService, + License = License, + Contact = Contact + }); + config.SwaggerDoc("v3", new OpenApiInfo + { + Version = "v3", + Title = titleBase + " v3", + Description = description, + TermsOfService = TermsOfService, + License = License, + Contact = Contact + }); + }); var app = builder.Build(); @@ -62,7 +105,11 @@ app.MapHub("/realTime/auth/passkey"); app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwaggerUI(config => + { + config.SwaggerEndpoint("/swagger/v1/swagger.json", "MoviesAPI v1"); + config.SwaggerEndpoint("/swagger/v3/swagger.json", "MoviesAPI v3"); + }); Conexión.SetStringConnection(sqlConnection); diff --git a/LIN.Identity/Services/Swagger.cs b/LIN.Identity/Services/Swagger.cs new file mode 100644 index 0000000..a2a6e82 --- /dev/null +++ b/LIN.Identity/Services/Swagger.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace LIN.Identity.Services; + +public class GroupingByNamespaceConvention : IControllerModelConvention +{ + public void Apply(ControllerModel controller) + { + var controllerNamespace = controller.ControllerType.Namespace; + var apiVersion = controllerNamespace.Split(".").Last().ToLower(); + if (!apiVersion.StartsWith("v")) { apiVersion = "v1"; } + controller.ApiExplorer.GroupName = apiVersion; + } +} From 71f92a1eee96188b6759d6e29dfdbe30df5689cc Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 6 Dec 2023 15:32:45 -0500 Subject: [PATCH 016/178] Identity V3 --- LIN.Identity.sln | 20 +- .../{V3 => }/Accounts/AccountController.cs | 110 +--- .../Applications/ApplicationController.cs | 45 +- .../AuthenticationController.cs | 4 +- .../Authentication/IntentsController.cs | 4 +- .../Areas/Directories/DirectoryController.cs | 47 ++ .../Organizations/MemberController.cs | 12 +- .../Organizations/OrganizationController.cs | 140 ++++++ LIN.Identity/Areas/Security/Security.cs | 14 + .../Areas/V1/Accounts/AccountController.cs | 474 ------------------ .../Areas/V1/Accounts/AdminController.cs | 266 ---------- .../Areas/V1/Accounts/DeviceController.cs | 54 -- .../Areas/V1/Accounts/LoginLogController.cs | 37 -- .../Areas/V1/Accounts/MailController.cs | 135 ----- .../V1/Applications/ApplicationController.cs | 119 ----- .../AuthenticationController.cs | 123 ----- .../V1/Authentication/IntentsController.cs | 58 --- .../ApplicationOrgsController.cs | 164 ------ .../V1/Organizations/MemberController.cs | 168 ------- .../Organizations/OrganizationController.cs | 234 --------- LIN.Identity/Areas/V1/Security/Security.cs | 312 ------------ .../Areas/V3/Accounts/AdminController.cs | 266 ---------- .../Areas/V3/Accounts/DeviceController.cs | 54 -- .../Areas/V3/Accounts/LoginLogController.cs | 37 -- .../Areas/V3/Accounts/MailController.cs | 135 ----- .../ApplicationOrgsController.cs | 164 ------ .../Organizations/OrganizationController.cs | 234 --------- LIN.Identity/Areas/V3/Security/Security.cs | 312 ------------ LIN.Identity/Data/Accounts/AccountsGet.cs | 16 +- LIN.Identity/Data/Accounts/AccountsPost.cs | 2 + LIN.Identity/Data/Accounts/AccountsUpdate.cs | 51 +- LIN.Identity/Data/Applications.cs | 91 +--- LIN.Identity/Data/Context.cs | 148 +++--- LIN.Identity/Data/Directories/Directories.cs | 50 ++ LIN.Identity/Data/Links.cs | 164 ------ LIN.Identity/Data/Logins.cs | 1 - LIN.Identity/Data/MailLinks.cs | 115 ----- .../Data/Organizations/Applications.cs | 183 ------- LIN.Identity/Data/Organizations/Members.cs | 9 +- .../Data/Organizations/Organizations.cs | 265 ++++------ LIN.Identity/Hubs/AccountHub.cs | 209 -------- LIN.Identity/Hubs/PasskeyHub.cs | 42 +- LIN.Identity/LIN.Identity.csproj | 2 +- LIN.Identity/Program.cs | 52 +- LIN.Identity/Queries/Accounts.cs | 74 ++- LIN.Identity/Services/Iam/Applications.cs | 30 +- LIN.Identity/Services/Jwt.cs | 2 +- LIN.Identity/Services/Login/LoginOnOrg.cs | 58 +-- LIN.Identity/Services/Login/LoginService.cs | 72 +-- LIN.Identity/Usings.cs | 4 +- LIN.Identity/Validations/Account.cs | 8 +- 51 files changed, 634 insertions(+), 4756 deletions(-) rename LIN.Identity/Areas/{V3 => }/Accounts/AccountController.cs (75%) rename LIN.Identity/Areas/{V3 => }/Applications/ApplicationController.cs (61%) rename LIN.Identity/Areas/{V3 => }/Authentication/AuthenticationController.cs (97%) rename LIN.Identity/Areas/{V3 => }/Authentication/IntentsController.cs (95%) create mode 100644 LIN.Identity/Areas/Directories/DirectoryController.cs rename LIN.Identity/Areas/{V3 => }/Organizations/MemberController.cs (88%) create mode 100644 LIN.Identity/Areas/Organizations/OrganizationController.cs create mode 100644 LIN.Identity/Areas/Security/Security.cs delete mode 100644 LIN.Identity/Areas/V1/Accounts/AccountController.cs delete mode 100644 LIN.Identity/Areas/V1/Accounts/AdminController.cs delete mode 100644 LIN.Identity/Areas/V1/Accounts/DeviceController.cs delete mode 100644 LIN.Identity/Areas/V1/Accounts/LoginLogController.cs delete mode 100644 LIN.Identity/Areas/V1/Accounts/MailController.cs delete mode 100644 LIN.Identity/Areas/V1/Applications/ApplicationController.cs delete mode 100644 LIN.Identity/Areas/V1/Authentication/AuthenticationController.cs delete mode 100644 LIN.Identity/Areas/V1/Authentication/IntentsController.cs delete mode 100644 LIN.Identity/Areas/V1/Organizations/ApplicationOrgsController.cs delete mode 100644 LIN.Identity/Areas/V1/Organizations/MemberController.cs delete mode 100644 LIN.Identity/Areas/V1/Organizations/OrganizationController.cs delete mode 100644 LIN.Identity/Areas/V1/Security/Security.cs delete mode 100644 LIN.Identity/Areas/V3/Accounts/AdminController.cs delete mode 100644 LIN.Identity/Areas/V3/Accounts/DeviceController.cs delete mode 100644 LIN.Identity/Areas/V3/Accounts/LoginLogController.cs delete mode 100644 LIN.Identity/Areas/V3/Accounts/MailController.cs delete mode 100644 LIN.Identity/Areas/V3/Organizations/ApplicationOrgsController.cs delete mode 100644 LIN.Identity/Areas/V3/Organizations/OrganizationController.cs delete mode 100644 LIN.Identity/Areas/V3/Security/Security.cs create mode 100644 LIN.Identity/Data/Directories/Directories.cs delete mode 100644 LIN.Identity/Data/Links.cs delete mode 100644 LIN.Identity/Data/MailLinks.cs delete mode 100644 LIN.Identity/Data/Organizations/Applications.cs delete mode 100644 LIN.Identity/Hubs/AccountHub.cs diff --git a/LIN.Identity.sln b/LIN.Identity.sln index 7d1a5f7..7caf9f5 100644 --- a/LIN.Identity.sln +++ b/LIN.Identity.sln @@ -11,10 +11,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Modules", "..\..\Tipos\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http", "..\..\Tipos\Http\Http.csproj", "{13CF4A38-8857-442E-A496-76982F0A1D48}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types.Auth", "..\..\Tipos\LIN.Types.Auth\LIN.Types.Auth.csproj", "{CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Access.Logger", "..\..\AccesoAPI\LIN.Access.Logger\LIN.Access.Logger.csproj", "{6CB65EA1-51C6-4450-B174-F0A04C489FB5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types.Identity", "..\..\Tipos\LIN.Types.Identity\LIN.Types.Identity\LIN.Types.Identity.csproj", "{F88DF354-579F-4079-8A74-15F5CC4BEDBA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,14 +55,6 @@ Global {13CF4A38-8857-442E-A496-76982F0A1D48}.Release|Any CPU.Build.0 = Release|Any CPU {13CF4A38-8857-442E-A496-76982F0A1D48}.Release|x86.ActiveCfg = Release|x86 {13CF4A38-8857-442E-A496-76982F0A1D48}.Release|x86.Build.0 = Release|x86 - {CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}.Debug|x86.ActiveCfg = Debug|Any CPU - {CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}.Debug|x86.Build.0 = Debug|Any CPU - {CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}.Release|Any CPU.Build.0 = Release|Any CPU - {CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}.Release|x86.ActiveCfg = Release|Any CPU - {CDED4C37-3998-4F64-8C87-85A9D0AB6CBD}.Release|x86.Build.0 = Release|Any CPU {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -71,6 +63,14 @@ Global {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Release|Any CPU.Build.0 = Release|Any CPU {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Release|x86.ActiveCfg = Release|Any CPU {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Release|x86.Build.0 = Release|Any CPU + {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Debug|x86.ActiveCfg = Debug|Any CPU + {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Debug|x86.Build.0 = Debug|Any CPU + {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Release|Any CPU.Build.0 = Release|Any CPU + {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Release|x86.ActiveCfg = Release|Any CPU + {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LIN.Identity/Areas/V3/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs similarity index 75% rename from LIN.Identity/Areas/V3/Accounts/AccountController.cs rename to LIN.Identity/Areas/Accounts/AccountController.cs index c68cf5f..9480a19 100644 --- a/LIN.Identity/Areas/V3/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -1,8 +1,8 @@ using LIN.Identity.Validations; -namespace LIN.Identity.Areas.V3; +namespace LIN.Identity.Areas.Accounts; -[Route("v3/account")] +[Route("account")] public class AccountController : ControllerBase { @@ -16,7 +16,7 @@ public async Task Create([FromBody] AccountModel? modelo) { // Comprobaciones - if (modelo == null || modelo.Contraseña.Length < 4 || modelo.Nombre.Length <= 0 || modelo.Usuario.Length <= 0) + if (modelo == null || modelo.Identity == null || modelo.Contraseña.Length < 4 || modelo.Nombre.Length <= 0 || modelo.Identity.Unique.Length <= 0) return new(Responses.InvalidParam); // Organización del modelo @@ -35,7 +35,7 @@ public async Task Create([FromBody] AccountModel? modelo) // Retorna el resultado return new CreateResponse() { - LastID = response.Model.ID, + LastID = response.Model.Identity.Id, Response = Responses.Success, Token = token, Message = "Success" @@ -218,70 +218,6 @@ public async Task> ReadAll([FromBody] List - /// Actualiza la contraseña de una cuenta - /// - /// Modelo de actualización - /// Token de acceso - [HttpPatch("update/password")] - public async Task Update([FromBody] UpdatePasswordModel modelo, [FromHeader] string token) - { - - // Validar parámetros. - if (modelo == null) - return new ResponseBase() - { - Message = "Parámetro para nueva actualización de contraseña es invalido.", - Response = Responses.InvalidParam - }; - - // Validar de la nueva contraseña. - if (modelo.OldPassword.Length < 4 || modelo.NewPassword.Length < 4) - return new ResponseBase(Responses.InvalidParam) - { - Message = "Antigua contraseña o nueva contraseña tienen una longitud invalida." - }; - - // Validar el token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - // No es valido. - if (!isValid) - return new ResponseBase(Responses.Unauthorized) - { - Message = "Token invalido" - }; - - // Obtener datos antiguos. - var actualData = await Data.Accounts.ReadBasic(userId); - - // Error al encontrar usuario. - if (actualData.Response != Responses.Success) - return new ResponseBase(Responses.Unauthorized) - { - Message = $"Error al encontrar el usuario con ID '{userId}'" - }; - - - // Encriptar la contraseña - modelo.OldPassword = EncryptClass.Encrypt(modelo.OldPassword); - - // Valida la contraseña actual - if (modelo.OldPassword != actualData.Model.Contraseña) - return new ResponseBase(Responses.InvalidPassword) - { - Message = $"Las contraseñas no coinciden." - }; - - // Actualiza el ID. - modelo.Account = userId; - - // Actualizar la contraseña - return await Data.Accounts.Update(modelo); - - } - - /// /// Elimina una cuenta @@ -311,33 +247,7 @@ public async Task Delete([FromHeader] string token) - /// - /// Desactiva una cuenta - /// - /// Modelo - [HttpPatch("disable")] - public async Task Disable([FromBody] AccountModel user) - { - - if (user.ID <= 0) - { - return new(Responses.ExistAccount); - } - - // Modelo de usuario de la BD - var userModel = await Data.Accounts.ReadBasic(user.ID); - - if (userModel.Model.Contraseña != EncryptClass.Encrypt(user.Contraseña)) - { - return new(Responses.InvalidPassword); - } - - - return await Data.Accounts.Update(user.ID, AccountStatus.Disable); - - } - - + /// /// (ADMIN) encuentra diez (10) usuarios que coincidan con el patron @@ -357,11 +267,7 @@ public async Task> FindAll([FromQuery] string } - var rol = (await Data.Accounts.Read(id, new() - { - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.None - })).Model.Rol; + var rol = AccountRoles.User; if (rol != AccountRoles.Admin) @@ -405,10 +311,10 @@ public async Task Update([FromBody] AccountModel modelo, [From }; // Organizar el modelo. - modelo.ID = userId; + modelo.Identity.Id = userId; modelo.Perfil = Image.Zip(modelo.Perfil); - if (modelo.ID <= 0 || modelo.Nombre.Any()) + if (modelo.Identity.Id <= 0 || modelo.Nombre.Any()) return new(Responses.InvalidParam); return await Data.Accounts.Update(modelo); diff --git a/LIN.Identity/Areas/V3/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs similarity index 61% rename from LIN.Identity/Areas/V3/Applications/ApplicationController.cs rename to LIN.Identity/Areas/Applications/ApplicationController.cs index 90a9668..77e3584 100644 --- a/LIN.Identity/Areas/V3/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.V3; +namespace LIN.Identity.Areas.Applications; -[Route("v3/applications")] +[Route("applications")] public class ApplicationController : ControllerBase { @@ -74,46 +74,5 @@ public async Task> GetAll([FromHeader] str - /// - /// Crear acceso permitido a una app. - /// - /// Token de acceso. - /// ID de la aplicación. - /// ID del integrante. - [HttpPut] - public async Task> InsertAllow([FromHeader] string token, [FromHeader] int appId, [FromHeader] int accountId) - { - - // Información del token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - // Si el token es invalido. - if (!isValid) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "El token es invalido." - }; - - // Respuesta de Iam. - var iam = await Services.Iam.Applications.ValidateAccess(userId, appId); - - // Validación de Iam - if (iam.Model != IamLevels.Privileged) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "No tienes autorización para modificar este recurso." - }; - - - // Enviar la actualización - var data = await Data.Applications.AllowTo(appId, accountId); - - return data; - - } - - } \ No newline at end of file diff --git a/LIN.Identity/Areas/V3/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs similarity index 97% rename from LIN.Identity/Areas/V3/Authentication/AuthenticationController.cs rename to LIN.Identity/Areas/Authentication/AuthenticationController.cs index f4ad975..9c6ff1b 100644 --- a/LIN.Identity/Areas/V3/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,9 +1,9 @@ using LIN.Identity.Services.Login; -namespace LIN.Identity.Areas.V3; +namespace LIN.Identity.Areas.Authentication; -[Route("v3/authentication")] +[Route("authentication")] public class AuthenticationController : ControllerBase { diff --git a/LIN.Identity/Areas/V3/Authentication/IntentsController.cs b/LIN.Identity/Areas/Authentication/IntentsController.cs similarity index 95% rename from LIN.Identity/Areas/V3/Authentication/IntentsController.cs rename to LIN.Identity/Areas/Authentication/IntentsController.cs index cef55d1..128abe5 100644 --- a/LIN.Identity/Areas/V3/Authentication/IntentsController.cs +++ b/LIN.Identity/Areas/Authentication/IntentsController.cs @@ -1,7 +1,7 @@ -namespace LIN.Identity.Areas.V3; +namespace LIN.Identity.Areas.Authentication; -[Route("v3/Intents")] +[Route("Intents")] public class IntentsController : ControllerBase { diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs new file mode 100644 index 0000000..61e43c9 --- /dev/null +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -0,0 +1,47 @@ +namespace LIN.Identity.Areas.Directories; + + +[Route("directory")] +public class DirectoryController : ControllerBase +{ + + /// + /// Obtiene la información de usuario. + /// + /// ID del usuario + /// Token de acceso + [HttpGet("read/id")] + public async Task> Read([FromQuery] int id, [FromHeader] string token) + { + + // Id es invalido. + if (id <= 0) + return new(Responses.InvalidParam); + + // Información del token. + var (isValid, _, user, orgId, _) = Jwt.Validate(token); + + // Token es invalido. + if (!isValid) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + // Obtiene el usuario. + var response = await Data.Directories.Read(id); + + // Si es erróneo + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + +} diff --git a/LIN.Identity/Areas/V3/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs similarity index 88% rename from LIN.Identity/Areas/V3/Organizations/MemberController.cs rename to LIN.Identity/Areas/Organizations/MemberController.cs index f0e7e3b..5862aad 100644 --- a/LIN.Identity/Areas/V3/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -1,9 +1,9 @@ using LIN.Identity.Validations; -namespace LIN.Identity.Areas.V3; +namespace LIN.Identity.Areas.Organizations; -[Route("v3/orgs/members")] +[Route("orgs/members")] public class MemberController : ControllerBase { @@ -19,7 +19,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr { // Validación del modelo. - if (modelo == null || !modelo.Usuario.Trim().Any() || !modelo.Nombre.Trim().Any()) + if (modelo == null || !modelo.Identity.Unique.Trim().Any() || !modelo.Nombre.Trim().Any()) return new CreateResponse { Response = Responses.InvalidParam, @@ -72,7 +72,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr { return new CreateResponse { - Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", + Message = $"El usuario '{userContext.Model.Identity.Unique}' no pertenece a una organización.", Response = Responses.Unauthorized }; } @@ -82,7 +82,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr { return new CreateResponse { - Message = $"El usuario '{userContext.Model.Usuario}' no puede crear nuevos usuarios en esta organización.", + Message = $"El usuario '{userContext.Model.Identity.Unique}' no puede crear nuevos usuarios en esta organización.", Response = Responses.Unauthorized }; } @@ -93,7 +93,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr { return new CreateResponse { - Message = $"El '{userContext.Model.Usuario}' no puede crear nuevos usuarios con mas privilegios de los propios.", + Message = $"El '{userContext.Model.Identity.Unique}' no puede crear nuevos usuarios con mas privilegios de los propios.", Response = Responses.Unauthorized }; } diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs new file mode 100644 index 0000000..342808f --- /dev/null +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -0,0 +1,140 @@ +using LIN.Identity.Validations; + +namespace LIN.Identity.Areas.Organizations; + + +[Route("organizations")] +public class OrganizationsController : ControllerBase +{ + + + /// + /// Crea una nueva organización. + /// + /// Modelo de la organización y el usuario administrador + [HttpPost("create")] + public async Task Create([FromBody] OrganizationModel modelo) + { + + // Comprobaciones + if (modelo == null || modelo.Domain.Length <= 0 || modelo.Name.Length <= 0 || modelo.Members.Count <= 0) + return new(Responses.InvalidParam); + + + // Conexi�n + var (context, connectionKey) = Conexión.GetOneConnection(); + + + // organización del modelo + modelo.ID = 0; + + // Directorio + modelo.Directory = new() + { + ID = 0, + Creación = DateTime.Now, + Nombre = "Directorio General: " + modelo.Name, + Identity = new() + { + Id = 0, + Type = IdentityTypes.Directory, + Unique = "d_" + modelo.Domain + }, + IdentityId = 0 + }; + + + + foreach (var member in modelo.Members) + { + modelo.Directory.Members.Add(new() + { + Account = member.Member, + AccountId = 0, + Directory = modelo.Directory, + DirectoryId = 0 + }); + member.Member = Account.Process(member.Member); + member.Rol = OrgRoles.SuperManager; + member.Organization = modelo; + } + + // Creaci�n de la organización + var response = await Data.Organizations.Organizations.Create(modelo, context); + + // Evaluaci�n + if (response.Response != Responses.Success) + return new(response.Response); + + context.CloseActions(connectionKey); + + // Retorna el resultado + return new CreateResponse() + { + LastID = response.Model.ID, + Response = Responses.Success, + Message = "Success" + }; + + } + + + + /// + /// Obtiene una organización por medio del Id. + /// + /// ID de la organización + /// Token de acceso + [HttpGet("read/id")] + public async Task> ReadOneByID([FromQuery] int id, [FromHeader] string token) + { + + // Parámetros + if (id <= 0) + return new(Responses.InvalidParam); + + // Validar el token + var (isValid, _, _, orgID, _) = Jwt.Validate(token); + + + if (!isValid) + return new(Responses.Unauthorized); + + + // Obtiene la organización + var response = await Data.Organizations.Organizations.Read(id); + + // Organización no encontrada. + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = Responses.NotRows, + Message = "No se encontró la organización." + }; + + // No es publica y no pertenece a ella + if (!response.Model.IsPublic && orgID != response.Model.ID) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "Esta organización es privada y tu usuario no esta vinculado a ella.", + Model = new() + { + ID = response.Model.ID, + IsPublic = false, + Name = "Organización privada" + } + }; + + return new ReadOneResponse() + { + Response = Responses.Success, + Model = response.Model + }; + + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Security/Security.cs b/LIN.Identity/Areas/Security/Security.cs new file mode 100644 index 0000000..bfa4e4c --- /dev/null +++ b/LIN.Identity/Areas/Security/Security.cs @@ -0,0 +1,14 @@ +namespace LIN.Identity.Areas.Security; + + +[Route("security")] +public class Security : ControllerBase +{ + + + + + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Accounts/AccountController.cs b/LIN.Identity/Areas/V1/Accounts/AccountController.cs deleted file mode 100644 index 506dd1f..0000000 --- a/LIN.Identity/Areas/V1/Accounts/AccountController.cs +++ /dev/null @@ -1,474 +0,0 @@ -using LIN.Identity.Validations; -namespace LIN.Identity.Areas.V1.Accounts; - - -[Route("v1/account")] -public class AccountController : ControllerBase -{ - - - /// - /// Crear nueva cuenta (Cuenta de LIN). - /// - /// Modelo de la cuenta - [HttpPost("create")] - public async Task Create([FromBody] AccountModel? modelo) - { - - // Comprobaciones - if (modelo == null || modelo.Contraseña.Length < 4 || modelo.Nombre.Length <= 0 || modelo.Usuario.Length <= 0) - return new(Responses.InvalidParam); - - // Organización del modelo - modelo = Account.Process(modelo); - - // Creación del usuario - var response = await Data.Accounts.Create(modelo); - - // Evaluación - if (response.Response != Responses.Success) - return new(response.Response); - - // Obtiene el usuario - var token = Jwt.Generate(response.Model, 0); - - // Retorna el resultado - return new CreateResponse() - { - LastID = response.Model.ID, - Response = Responses.Success, - Token = token, - Message = "Success" - }; - - } - - - - /// - /// Obtiene la información de usuario. - /// - /// ID del usuario - /// Token de acceso - [HttpGet("read/id")] - public async Task> Read([FromQuery] int id, [FromHeader] string token) - { - - // Id es invalido. - if (id <= 0) - return new(Responses.InvalidParam); - - // Información del token. - var (isValid, _, user, orgId, _) = Jwt.Validate(token); - - // Token es invalido. - if (!isValid) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - // Obtiene el usuario. - var response = await Data.Accounts.Read(id, new() - { - ContextOrg = orgId, - ContextUser = user, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.IncludeIf, - IsAdmin = false, - OrgLevel = FilterModels.IncludeOrgLevel.Advance - }); - - // Si es erróneo - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = response.Response - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Obtiene la información de usuario. - /// - /// Usuario único - /// Token de acceso - [HttpGet("read/user")] - public async Task> Read([FromQuery] string user, [FromHeader] string token) - { - - // Usuario es invalido. - if (string.IsNullOrWhiteSpace(user)) - return new(Responses.InvalidParam); - - // Información del token. - var (isValid, _, userId, orgId, _) = Jwt.Validate(token); - - // Token es invalido. - if (!isValid) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - // Obtiene el usuario. - var response = await Data.Accounts.Read(user, new() - { - ContextOrg = orgId, - ContextUser = userId, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.IncludeIf, - OrgLevel = FilterModels.IncludeOrgLevel.Advance - }); - - // Si es erróneo - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = response.Response, - Model = new() - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Obtiene una lista de diez (10) usuarios que coincidan con un patron - /// - /// Patron - /// Token de acceso - [HttpGet("search")] - public async Task> Search([FromQuery] string pattern, [FromHeader] string token) - { - - // Comprobación - if (pattern.Trim().Length <= 0) - return new(Responses.InvalidParam); - - // Info del token - var (isValid, _, userId, orgId, _) = Jwt.Validate(token); - - // Token es invalido - if (!isValid) - return new ReadAllResponse - { - Message = "Token es invalido", - Response = Responses.Unauthorized - }; - - // Obtiene el usuario - var response = await Data.Accounts.Search(pattern, new() - { - ContextOrg = orgId, - ContextUser = userId, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.IncludeIf, - OrgLevel = FilterModels.IncludeOrgLevel.Advance - }); - - return response; - } - - - - /// - /// Obtiene una lista cuentas - /// - /// IDs de las cuentas - /// Token de acceso - [HttpPost("findAll")] - public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) - { - - // Información del token. - var (isValid, _, userId, orgId, _) = Jwt.Validate(token); - - // Es invalido. - if (!isValid) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - // Obtiene el usuario - var response = await Data.Accounts.FindAll(ids, new() - { - ContextOrg = orgId, - ContextUser = userId, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.Include, - OrgLevel = FilterModels.IncludeOrgLevel.Advance - }); - - return response; - - } - - - - /// - /// Actualiza la contraseña de una cuenta - /// - /// Modelo de actualización - /// Token de acceso - [HttpPatch("update/password")] - public async Task Update([FromBody] UpdatePasswordModel modelo, [FromHeader] string token) - { - - // Validar parámetros. - if (modelo == null) - return new ResponseBase() - { - Message = "Parámetro para nueva actualización de contraseña es invalido.", - Response = Responses.InvalidParam - }; - - // Validar de la nueva contraseña. - if (modelo.OldPassword.Length < 4 || modelo.NewPassword.Length < 4) - return new ResponseBase(Responses.InvalidParam) - { - Message = "Antigua contraseña o nueva contraseña tienen una longitud invalida." - }; - - // Validar el token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - // No es valido. - if (!isValid) - return new ResponseBase(Responses.Unauthorized) - { - Message = "Token invalido" - }; - - // Obtener datos antiguos. - var actualData = await Data.Accounts.ReadBasic(userId); - - // Error al encontrar usuario. - if (actualData.Response != Responses.Success) - return new ResponseBase(Responses.Unauthorized) - { - Message = $"Error al encontrar el usuario con ID '{userId}'" - }; - - - // Encriptar la contraseña - modelo.OldPassword = EncryptClass.Encrypt(modelo.OldPassword); - - // Valida la contraseña actual - if (modelo.OldPassword != actualData.Model.Contraseña) - return new ResponseBase(Responses.InvalidPassword) - { - Message = $"Las contraseñas no coinciden." - }; - - // Actualiza el ID. - modelo.Account = userId; - - // Actualizar la contraseña - return await Data.Accounts.Update(modelo); - - } - - - - /// - /// Elimina una cuenta - /// - /// Token de acceso - [HttpDelete("delete")] - public async Task Delete([FromHeader] string token) - { - - // Información del token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - // Si es invalido. - if (!isValid) - return new ResponseBase - { - Response = Responses.Unauthorized, - Message = "Token invalido" - }; - - if (userId <= 0) - return new(Responses.InvalidParam); - - var response = await Data.Accounts.Delete(userId); - return response; - } - - - - /// - /// Desactiva una cuenta - /// - /// Modelo - [HttpPatch("disable")] - public async Task Disable([FromBody] AccountModel user) - { - - if (user.ID <= 0) - { - return new(Responses.ExistAccount); - } - - // Modelo de usuario de la BD - var userModel = await Data.Accounts.ReadBasic(user.ID); - - if (userModel.Model.Contraseña != EncryptClass.Encrypt(user.Contraseña)) - { - return new(Responses.InvalidPassword); - } - - - return await Data.Accounts.Update(user.ID, AccountStatus.Disable); - - } - - - - /// - /// (ADMIN) encuentra diez (10) usuarios que coincidan con el patron - /// - /// - /// - [HttpGet("admin/search")] - public async Task> FindAll([FromQuery] string pattern, [FromHeader] string token) - { - - var (isValid, _, id, _, _) = Jwt.Validate(token); - - - if (!isValid) - { - return new(Responses.Unauthorized); - } - - - var rol = (await Data.Accounts.Read(id, new() - { - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.None - })).Model.Rol; - - - if (rol != AccountRoles.Admin) - return new(Responses.Unauthorized); - - // Obtiene el usuario - var response = await Data.Accounts.Search(pattern, new() - { - ContextOrg = 0, - OrgLevel = FilterModels.IncludeOrgLevel.Advance, - ContextUser = 0, - FindOn = FilterModels.FindOn.AllAccount, - IncludeOrg = FilterModels.IncludeOrg.Include, - IsAdmin = true - }); - - return response; - - } - - - - /// - /// Actualiza la información de una cuenta. - /// - /// Modelo - /// Token de acceso - [HttpPut("update")] - public async Task Update([FromBody] AccountModel modelo, [FromHeader] string token) - { - - // Información del token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - // Es invalido. - if (!isValid) - return new ResponseBase - { - Response = Responses.Unauthorized, - Message = "Token Invalido" - }; - - // Organizar el modelo. - modelo.ID = userId; - modelo.Perfil = Image.Zip(modelo.Perfil); - - if (modelo.ID <= 0 || modelo.Nombre.Any()) - return new(Responses.InvalidParam); - - return await Data.Accounts.Update(modelo); - - } - - - - /// - /// Actualiza el genero de un usuario - /// - /// Token de acceso - /// Nuevo genero - [HttpPatch("update/gender")] - public async Task Update([FromHeader] string token, [FromHeader] Genders genero) - { - - // Información del token. - var (isValid, _, id, _, _) = Jwt.Validate(token); - - // Token es invalido. - if (!isValid) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - // Realizar actualización. - return await Data.Accounts.Update(id, genero); - - } - - - - /// - /// Actualiza la visibilidad de una cuenta. - /// - /// Token de acceso. - /// Nueva visibilidad. - [HttpPatch("update/visibility")] - public async Task Update([FromHeader] string token, [FromHeader] AccountVisibility visibility) - { - - // Información del token. - var (isValid, _, id, _, _) = Jwt.Validate(token); - - // Token es invalido. - if (!isValid) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - // Actualización. - return await Data.Accounts.Update(id, visibility); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Accounts/AdminController.cs b/LIN.Identity/Areas/V1/Accounts/AdminController.cs deleted file mode 100644 index a5b797a..0000000 --- a/LIN.Identity/Areas/V1/Accounts/AdminController.cs +++ /dev/null @@ -1,266 +0,0 @@ -namespace LIN.Identity.Areas.V1.Accounts; - - -[Route("v1/administrator")] -public class AdminController : ControllerBase -{ - - - - /// - /// Obtiene la información de usuario. - /// - /// ID del usuario - /// Token de acceso - [HttpGet("read/id")] - public async Task> Read([FromQuery] int id, [FromHeader] string token) - { - - if (id <= 0) - return new(Responses.InvalidParam); - - - var (isValid, _, user, orgID, _) = Jwt.Validate(token); - - if (!isValid) - { - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - } - - var rol = (await Data.Accounts.Read(user, new() - { - IncludeOrg = FilterModels.IncludeOrg.None, - FindOn = FilterModels.FindOn.StableAccounts - })).Model.Rol; - - if (rol != AccountRoles.Admin) - { - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Tienes que ser un administrador." - }; - } - - - // Obtiene el usuario - var response = await Data.Accounts.Read(id, new() - { - SensibleInfo = false, - ContextOrg = orgID, - ContextUser = user, - IsAdmin = true, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.Include, - OrgLevel = FilterModels.IncludeOrgLevel.Advance - }); - - // Si es erróneo - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = response.Response, - Model = new() - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Obtiene la información de usuario. - /// - /// Usuario único - /// Token de acceso - [HttpGet("read/user")] - public async Task> Read([FromQuery] string user, [FromHeader] string token) - { - - // Validar el parámetro. - if (string.IsNullOrWhiteSpace(user)) - return new(Responses.InvalidParam); - - // Información del token. - var (isValid, _, userId, orgId, _) = Jwt.Validate(token); - - // Token es invalido. - if (!isValid) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - - - var rol = (await Data.Accounts.Read(userId, new() - { - IncludeOrg = FilterModels.IncludeOrg.None, - FindOn = FilterModels.FindOn.StableAccounts - })).Model.Rol; - - if (rol != AccountRoles.Admin) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Tienes que ser un administrador." - }; - - - - - var response = await Data.Accounts.Read(user, new() - { - SensibleInfo = false, - ContextOrg = orgId, - IsAdmin = true, - ContextUser = userId, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.Include, - OrgLevel = FilterModels.IncludeOrgLevel.Advance - }); - - - - // Si es erróneo - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = response.Response, - Model = new() - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Actualiza la contraseña de una cuenta - /// - /// Modelo de actualización - /// Token de acceso - [HttpPatch("update/password")] - public async Task Update([FromBody] UpdatePasswordModel modelo, [FromHeader] string token) - { - - if (modelo.OldPassword.Length < 4 || modelo.NewPassword.Length < 4) - return new(Responses.InvalidParam); - - - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - - if (!isValid) - { - return new(Responses.Unauthorized); - } - - modelo.Account = userId; - - var actualData = await Data.Accounts.ReadBasic(modelo.Account); - - if (actualData.Response != Responses.Success) - return new(Responses.NotExistAccount); - - var oldEncrypted = actualData.Model.Contraseña; - - - if (oldEncrypted != actualData.Model.Contraseña) - { - return new ResponseBase(Responses.InvalidPassword); - } - - return await Data.Accounts.Update(modelo); - - } - - - - /// - /// Elimina una cuenta - /// - /// Token de acceso - [HttpDelete("delete")] - public async Task Delete([FromHeader] string token) - { - - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - if (!isValid) - return new ResponseBase - { - Response = Responses.Unauthorized, - Message = "Token invalido" - }; - - if (userId <= 0) - return new(Responses.InvalidParam); - - var response = await Data.Accounts.Delete(userId); - return response; - } - - - - /// - /// Desactiva una cuenta - /// - /// Modelo - [HttpPatch("disable")] - public async Task Disable([FromBody] AccountModel user) - { - - if (user.ID <= 0) - { - return new(Responses.ExistAccount); - } - - // Modelo de usuario de la BD - var userModel = await Data.Accounts.ReadBasic(user.ID); - - if (userModel.Model.Contraseña != EncryptClass.Encrypt(user.Contraseña)) - { - return new(Responses.InvalidPassword); - } - - - return await Data.Accounts.Update(user.ID, AccountStatus.Disable); - - } - - - - /// - /// Actualiza la visibilidad de una cuenta - /// - /// Token de acceso - /// Nueva visibilidad - [HttpPatch("update/visibility")] - public async Task Update([FromHeader] string token, [FromHeader] AccountVisibility visibility) - { - - - var (isValid, _, id, _, _) = Jwt.Validate(token); - - if (!isValid) - { - return new(Responses.Unauthorized); - } - - return await Data.Accounts.Update(id, visibility); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Accounts/DeviceController.cs b/LIN.Identity/Areas/V1/Accounts/DeviceController.cs deleted file mode 100644 index 39a55e9..0000000 --- a/LIN.Identity/Areas/V1/Accounts/DeviceController.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace LIN.Identity.Areas.V1.Accounts; - - -[Route("v1/devices")] -public class DeviceController : ControllerBase -{ - - - /// - /// Obtiene la lista de dispositivos asociados a una cuenta en tiempo real - /// - /// Token de acceso - [HttpGet] - public HttpReadAllResponse GetAll([FromHeader] string token) - { - try - { - - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - if (!isValid) - { - return new ReadAllResponse - { - Message = "Token Invalido", - Response = Responses.Unauthorized - }; - } - - - var devices = (from account in AccountHub.Cuentas - where account.Key == userId - select account).FirstOrDefault().Value ?? new(); - - - var filter = (from device in devices - where device.Estado == DeviceState.Actived - select device).ToList(); - - // Retorna - return new(Responses.Success, filter ?? new()); - } - catch - { - return new(Responses.Undefined) - { - Message = "Hubo un error al obtener los dispositivos asociados." - }; - } - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Accounts/LoginLogController.cs b/LIN.Identity/Areas/V1/Accounts/LoginLogController.cs deleted file mode 100644 index 81be12a..0000000 --- a/LIN.Identity/Areas/V1/Accounts/LoginLogController.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace LIN.Identity.Areas.V1.Accounts; - - -[Route("v1/Account/logs")] -public class LoginLogController : ControllerBase -{ - - - /// - /// Obtienes la lista de accesos asociados a una cuenta - /// - /// Token de acceso - [HttpGet] - public async Task> GetAll([FromHeader] string token) - { - - // JWT. - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - // Validación. - if (!isValid) - return new(Responses.Unauthorized) - { - Message = "Token invalido." - }; - - // Obtiene el usuario. - var result = await Data.Logins.ReadAll(userId); - - // Retorna el resultado. - return result ?? new(); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Accounts/MailController.cs b/LIN.Identity/Areas/V1/Accounts/MailController.cs deleted file mode 100644 index 957cb85..0000000 --- a/LIN.Identity/Areas/V1/Accounts/MailController.cs +++ /dev/null @@ -1,135 +0,0 @@ -namespace LIN.Identity.Areas.V1.Accounts; - - -[Route("v1/mails")] -public class MailController : ControllerBase -{ - - - /// - /// Obtiene los mails asociados a una cuenta. - /// - /// Token de acceso - [HttpGet("all")] - public async Task> GetMails([FromHeader] string token) - { - - // Información del token. - var (isValid, _, id, _, _) = Jwt.Validate(token); - - // Validación del token - if (!isValid) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - return await Data.Mails.ReadAll(id); - - } - - - - /// - /// Agrega un nuevo email a una cuenta. - /// - /// Token de acceso. - /// Contraseña de la cuenta. - /// Modelo del email. - [HttpPost("add")] - public async Task EmailAdd([FromHeader] string token, [FromHeader] string password, [FromBody] EmailModel model) - { - - // Obtener el usuario - var userData = await Data.Accounts.ReadBasic(model.UserID); - - // Evaluación de la respuesta - if (userData.Response != Responses.Success) - { - return new(Responses.NotExistAccount); - } - - // Evaluación de la contraseña - if (userData.Model.Contraseña != EncryptClass.Encrypt(password)) - return new(Responses.Unauthorized); - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - // Obtiene los mails actuales - var mails = await Data.Mails.ReadAll(userData.Model.ID, context); - - // Evalúa la respuesta - if (mails.Response != Responses.Success) - { - context.CloseActions(connectionKey); - return new(); - } - - // Este email ya esta en la cuenta? - var countEmail = mails.Models.Where(T => T.Email == model.Email).Count(); - - // Si ya existía - if (countEmail > 0) - { - context.CloseActions(connectionKey); - return new(); - } - - // Agrega el mail - var addMail = await Data.Mails.Create(new() - { - Status = EmailStatus.Unverified, - Email = model.Email, - ID = 0, - UserID = userData.Model.ID - }, context); - - - // Evalúa la respuesta - if (addMail.Response != Responses.Success) - { - context.CloseActions(connectionKey); - return new(); - } - - //Crea el LINK - var emailLink = new MailMagicLink() - { - Email = addMail.LastID, - Status = MagicLinkStatus.Activated, - Vencimiento = DateTime.Now.AddMinutes(10), - Key = KeyGen.Generate(20, "eml") - }; - - try - { - // Agrega y guarda el link - context.DataBase.MailMagicLinks.Add(emailLink); - - - var bytes = Encoding.UTF8.GetBytes(model.Email); - var mail64 = Convert.ToBase64String(bytes); - - bytes = Encoding.UTF8.GetBytes(userData.Model.Usuario); - var user64 = Convert.ToBase64String(bytes); - - await EmailWorker.SendVerification(model.Email, $"http://linaccount.somee.com/verificate/{user64}/{mail64}/{emailLink.Key}", model.Email); - return new(Responses.Success); - - } - catch - { - } - finally - { - context.DataBase.SaveChanges(); - } - - return new(); - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Applications/ApplicationController.cs b/LIN.Identity/Areas/V1/Applications/ApplicationController.cs deleted file mode 100644 index 86193bf..0000000 --- a/LIN.Identity/Areas/V1/Applications/ApplicationController.cs +++ /dev/null @@ -1,119 +0,0 @@ -namespace LIN.Identity.Areas.V1.Applications; - - -[Route("v1/applications")] -public class ApplicationController : ControllerBase -{ - - - /// - /// Crear nueva aplicación. - /// - /// Modelo. - /// Token de acceso. - [HttpPost] - public async Task Create([FromBody] ApplicationModel applicationModel, [FromHeader] string token) - { - - // Información del token. - var (isValid, _, userID, _, _) = Jwt.Validate(token); - - // Si el token es invalido. - if (!isValid) - return new CreateResponse() - { - Response = Responses.Unauthorized, - Message = "El token es invalido." - }; - - // Validaciones. - if (applicationModel == null || applicationModel.ApplicationUid.Trim().Length < 4 || applicationModel.Name.Trim().Length < 4) - return new CreateResponse() - { - Response = Responses.InvalidParam, - Message = "Parámetros inválidos." - }; - - // Preparar el modelo - applicationModel.ApplicationUid = applicationModel.ApplicationUid.Trim().ToLower(); - applicationModel.Name = applicationModel.Name.Trim().ToLower(); - applicationModel.AccountID = userID; - - // Crear la aplicación. - return await Data.Applications.Create(applicationModel); - - } - - - - /// - /// Obtener las aplicaciones asociadas - /// - /// Token de acceso - [HttpGet] - public async Task> GetAll([FromHeader] string token) - { - - // Información del token. - var (isValid, _, userID, _, _) = Jwt.Validate(token); - - // Si el token es invalido. - if (!isValid) - return new ReadAllResponse() - { - Response = Responses.Unauthorized, - Message = "El token es invalido." - }; - - // Obtiene la data. - var data = await Data.Applications.ReadAll(userID); - - return data; - - } - - - - /// - /// Crear acceso permitido a una app. - /// - /// Token de acceso. - /// ID de la aplicación. - /// ID del integrante. - [HttpPut] - public async Task> InsertAllow([FromHeader] string token, [FromHeader] int appId, [FromHeader] int accountId) - { - - // Información del token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - // Si el token es invalido. - if (!isValid) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "El token es invalido." - }; - - // Respuesta de Iam. - var iam = await Services.Iam.Applications.ValidateAccess(userId, appId); - - // Validación de Iam - if (iam.Model != IamLevels.Privileged) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "No tienes autorización para modificar este recurso." - }; - - - // Enviar la actualización - var data = await Data.Applications.AllowTo(appId, accountId); - - return data; - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/V1/Authentication/AuthenticationController.cs deleted file mode 100644 index 067e2e8..0000000 --- a/LIN.Identity/Areas/V1/Authentication/AuthenticationController.cs +++ /dev/null @@ -1,123 +0,0 @@ -using LIN.Identity.Services.Login; - -namespace LIN.Identity.Areas.V1.Authentication; - - -[Route("v1/authentication")] -public class AuthenticationController : ControllerBase -{ - - - /// - /// Inicia una sesión de usuario - /// - /// Usuario único - /// Contraseña del usuario - /// Key de aplicación - [HttpGet("login")] - public async Task> Login([FromQuery] string user, [FromQuery] string password, [FromHeader] string application) - { - - // Validación de parámetros. - if (!user.Any() || !password.Any() || !application.Any()) - return new(Responses.InvalidParam); - - // Obtiene el usuario. - var response = await Data.Accounts.Read(user, new() - { - SensibleInfo = true, - IsAdmin = true, - IncludeOrg = FilterModels.IncludeOrg.Include, - OrgLevel = FilterModels.IncludeOrgLevel.Advance, - FindOn = FilterModels.FindOn.StableAccounts - }); - - // Validación al obtener el usuario - switch (response.Response) - { - // Correcto - case Responses.Success: - break; - - // Incorrecto - default: - return new(response.Response) - { - Message = "Hubo un error grave." - }; - } - - - // Estrategia de login - LoginService strategy; - - // Definir la estrategia - strategy = response.Model.OrganizationAccess == null ? new LoginNormal(response.Model, application, password) - : new LoginOnOrg(response.Model, application, password); - - // Respuesta del login - var loginResponse = await strategy.Login(); - - - // Respuesta - if (loginResponse.Response != Responses.Success) - return new ReadOneResponse() - { - Message = loginResponse.Message, - Response = loginResponse.Response - }; - - - // Genera el token - var token = Jwt.Generate(response.Model, 0); - - - response.Token = token; - return response; - - } - - - - /// - /// Inicia una sesión de usuario por medio del token - /// - /// Token de acceso - [HttpGet("LoginWithToken")] - public async Task> LoginWithToken([FromHeader] string token) - { - - // Información del token de acceso. - var (isValid, _, user, _, _) = Jwt.Validate(token); - - // Si el token es invalido. - if (!isValid) - return new(Responses.InvalidParam) - { - Message = "El token proporcionado no es valido." - }; - - // Obtiene el usuario - var response = await Data.Accounts.Read(user, new() - { - SensibleInfo = true, - IsAdmin = true, - IncludeOrg = FilterModels.IncludeOrg.Include, - OrgLevel = FilterModels.IncludeOrgLevel.Advance, - FindOn = FilterModels.FindOn.StableAccounts - }); - - if (response.Response != Responses.Success) - return new(response.Response); - - if (response.Model.Estado != AccountStatus.Normal) - return new(Responses.NotExistAccount); - - response.Token = token; - return response; - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Authentication/IntentsController.cs b/LIN.Identity/Areas/V1/Authentication/IntentsController.cs deleted file mode 100644 index 802a2fd..0000000 --- a/LIN.Identity/Areas/V1/Authentication/IntentsController.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace LIN.Identity.Areas.V1.Authentication; - - -[Route("v1/Intents")] -public class IntentsController : ControllerBase -{ - - /// - /// Obtiene la lista de intentos de llaves de paso están activos. - /// - /// Token de acceso - [HttpGet] - public HttpReadAllResponse GetAll([FromHeader] string token) - { - try - { - - // Info del token - var (isValid, user, _, _, _) = Jwt.Validate(token); - - // Si el token es invalido - if (!isValid) - return new ReadAllResponse - { - Message = "Invalid Token", - Response = Responses.Unauthorized - }; - - - // Cuenta - var account = (from a in PassKeyHub.Attempts - where a.Key == user.ToLower() - select a).FirstOrDefault().Value ?? new(); - - // Hora actual - var timeNow = DateTime.Now; - - // Intentos - var intentos = (from I in account - where I.Status == PassKeyStatus.Undefined - where I.Expiración > timeNow - select I).ToList(); - - // Retorna - return new(Responses.Success, intentos); - } - catch - { - return new(Responses.Undefined) - { - Message = "Hubo un error al obtener los intentos de passkey" - }; - } - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Organizations/ApplicationOrgsController.cs b/LIN.Identity/Areas/V1/Organizations/ApplicationOrgsController.cs deleted file mode 100644 index 8c15621..0000000 --- a/LIN.Identity/Areas/V1/Organizations/ApplicationOrgsController.cs +++ /dev/null @@ -1,164 +0,0 @@ -namespace LIN.Identity.Areas.V1.Organizations; - - -[Route("v1/orgs/applications")] -public class ApplicationOrgsController : ControllerBase -{ - - - /// - /// Obtiene la lista de aplicaciones asociadas a una organización - /// - /// Token de acceso - [HttpGet] - public async Task> ReadApps([FromHeader] string token) - { - - // Token - var (isValid, _, _, orgId, _) = Jwt.Validate(token); - - // Token es invalido - if (!isValid) - return new ReadAllResponse - { - Message = "Token invalido.", - Response = Responses.Unauthorized - }; - - - // Si no tiene ninguna organización - if (orgId <= 0) - return new ReadAllResponse - { - Message = "No estas vinculado con ninguna organización.", - Response = Responses.Unauthorized - }; - - - // Obtiene las aplicaciones - var org = await Data.Organizations.Organizations.ReadApps(orgId); - - // Su no se encontraron aplicaciones - if (org.Response != Responses.Success) - return new ReadAllResponse - { - Message = "No found Organization", - Response = Responses.Unauthorized - }; - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - context.CloseActions(connectionKey); - - // Retorna el resultado - return org; - - } - - - - /// - /// Insertar una aplicación en una organización - /// - /// UId de la aplicación - /// Token de acceso - [HttpPost("insert")] - public async Task InsertApp([FromQuery] string appUid, [FromHeader] string token) - { - - // Token - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - - // Si el token es invalido - if (!isValid) - return new CreateResponse - { - Message = "Token invalido", - Response = Responses.Unauthorized - }; - - // Información del usuario - var userData = await Data.Accounts.ReadBasic(userId); - - // Si no existe el usuario - if (userData.Response != Responses.Success) - return new CreateResponse - { - Message = "No se encontró el usuario, talvez fue eliminado o desactivado.", - Response = Responses.NotExistAccount - }; - - - // Si no tiene organización - if (userData.Model.OrganizationAccess == null || userData.Model.OrganizationAccess?.Organization == null) - return new CreateResponse - { - Message = $"El usuario '{userData.Model.Usuario}' no pertenece a una organización.", - Response = Responses.Unauthorized - }; - - // Si el usuario no es admin en la organización - if (!userData.Model.OrganizationAccess.Rol.IsAdmin()) - return new CreateResponse - { - Message = $"El usuario '{userData.Model.Usuario}' no tiene un rol administrador en la organización '{userData.Model.OrganizationAccess.Organization.Name}'", - Response = Responses.Unauthorized - }; - - // Crea la aplicación en la organización - var res = await Data.Organizations.Applications.Create(appUid, userData.Model.OrganizationAccess.Organization.ID); - - // Si hubo une error - if (res.Response != Responses.Success) - return new CreateResponse - { - Message = $"Hubo un error al insertar esta aplicación en la lista blanca permitidas de {userData.Model.OrganizationAccess.Organization.Name}", - Response = Responses.Unauthorized - }; - - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - context.CloseActions(connectionKey); - - // Retorna el resultado - return new CreateResponse - { - LastID = res.LastID, - Message = "", - Response = Responses.Success - }; - } - - - - /// - /// Buscar aplicaciones que no están vinculadas a una organización por medio del un parámetro - /// - /// Parámetro de búsqueda - /// Token de acceso - [HttpGet("search")] - public async Task> Search([FromQuery] string param, [FromHeader] string token) - { - - // Token - var (isValid, _, _, orgId, _) = Jwt.Validate(token); - - // Valida el token - if (!isValid || orgId <= 0) - { - return new(Responses.Unauthorized); - } - - // Encuentra las apps - var finds = await Data.Organizations.Applications.Search(param, orgId); - - return finds; - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Organizations/MemberController.cs b/LIN.Identity/Areas/V1/Organizations/MemberController.cs deleted file mode 100644 index a916d95..0000000 --- a/LIN.Identity/Areas/V1/Organizations/MemberController.cs +++ /dev/null @@ -1,168 +0,0 @@ -using LIN.Identity.Validations; - -namespace LIN.Identity.Areas.V1.Organizations; - - -[Route("v1/orgs/members")] -public class MemberController : ControllerBase -{ - - - /// - /// Crea un nuevo miembro en una organización. - /// - /// Modelo de la cuenta - /// Token de acceso de un administrador - /// Rol asignado - [HttpPost] - public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] OrgRoles rol) - { - - // Validación del modelo. - if (modelo == null || !modelo.Usuario.Trim().Any() || !modelo.Nombre.Trim().Any()) - return new CreateResponse - { - Response = Responses.InvalidParam, - Message = "Uno o varios parámetros inválidos." - }; - - - // Visibilidad oculta - modelo.Visibilidad = AccountVisibility.Hidden; - - // Organización del modelo - modelo = Account.Process(modelo); - - - // Establece la contraseña default - var password = $"ChangePwd@{modelo.Creación:dd.MM.yyyy}"; - - // Contraseña default - modelo.Contraseña = EncryptClass.Encrypt(password); - - // Validación del token - var (isValid, _, userID, _, _) = Jwt.Validate(token); - - // Token es invalido - if (!isValid) - { - return new CreateResponse - { - Message = "Token invalido.", - Response = Responses.Unauthorized - }; - } - - - // Obtiene el usuario - var userContext = await Data.Accounts.ReadBasic(userID); - - // Error al encontrar el usuario - if (userContext.Response != Responses.Success) - { - return new CreateResponse - { - Message = "No se encontró un usuario valido.", - Response = Responses.Unauthorized - }; - } - - // Si el usuario no tiene una organización - if (userContext.Model.OrganizationAccess == null) - { - return new CreateResponse - { - Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", - Response = Responses.Unauthorized - }; - } - - // Verificación del rol dentro de la organización - if (!userContext.Model.OrganizationAccess.Rol.IsAdmin()) - { - return new CreateResponse - { - Message = $"El usuario '{userContext.Model.Usuario}' no puede crear nuevos usuarios en esta organización.", - Response = Responses.Unauthorized - }; - } - - - // Verificación del rol dentro de la organización - if (userContext.Model.OrganizationAccess.Rol.IsGretter(rol)) - { - return new CreateResponse - { - Message = $"El '{userContext.Model.Usuario}' no puede crear nuevos usuarios con mas privilegios de los propios.", - Response = Responses.Unauthorized - }; - } - - - // ID de la organización - var org = userContext.Model.OrganizationAccess.Organization.ID; - - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - // Creación del usuario - var response = await Data.Organizations.Members.Create(modelo, org, rol, context); - - // Evaluación - if (response.Response != Responses.Success) - return new(response.Response); - - // Cierra la conexión - context.CloseActions(connectionKey); - - // Retorna el resultado - return new CreateResponse() - { - LastID = response.Model.ID, - Response = Responses.Success, - Message = "Success" - }; - - } - - - - /// - /// Obtiene la lista de integrantes asociados a una organización. - /// - /// Token de acceso - [HttpGet] - public async Task> ReadAll([FromHeader] string token) - { - - // Información del token. - var (isValid, _, _, orgID, _) = Jwt.Validate(token); - - // Si el token es invalido. - if (!isValid) - return new ReadAllResponse - { - Message = "El token es invalido.", - Response = Responses.Unauthorized - }; - - // Obtiene los miembros. - var members = await Data.Organizations.Members.ReadAll(orgID); - - // Error al obtener los integrantes. - if (members.Response != Responses.Success) - return new ReadAllResponse - { - Message = "No se encontró la organización.", - Response = Responses.Unauthorized - }; - - // Retorna el resultado - return members; - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Organizations/OrganizationController.cs b/LIN.Identity/Areas/V1/Organizations/OrganizationController.cs deleted file mode 100644 index 5915305..0000000 --- a/LIN.Identity/Areas/V1/Organizations/OrganizationController.cs +++ /dev/null @@ -1,234 +0,0 @@ -using LIN.Identity.Validations; - -namespace LIN.Identity.Areas.V1.Organizations; - - -[Route("v1/organizations")] -public class OrganizationsController : ControllerBase -{ - - - /// - /// Crea una nueva organización. - /// - /// Modelo de la organización y el usuario administrador - [HttpPost("create")] - public async Task Create([FromBody] OrganizationModel modelo) - { - - // Comprobaciones - if (modelo == null || modelo.Domain.Length <= 0 || modelo.Name.Length <= 0 || modelo.Members.Count <= 0) - return new(Responses.InvalidParam); - - - // Conexi�n - var (context, connectionKey) = Conexión.GetOneConnection(); - - - // organización del modelo - modelo.ID = 0; - modelo.AppList = new(); - - modelo.Members[0].Member = Account.Process(modelo.Members[0].Member); - foreach (var member in modelo.Members) - { - member.Rol = OrgRoles.SuperManager; - member.Organization = modelo; - } - - // Creaci�n de la organización - var response = await Data.Organizations.Organizations.Create(modelo, context); - - // Evaluaci�n - if (response.Response != Responses.Success) - return new(response.Response); - - context.CloseActions(connectionKey); - - // Retorna el resultado - return new CreateResponse() - { - LastID = response.Model.ID, - Response = Responses.Success, - Message = "Success" - }; - - } - - - - /// - /// Obtiene una organización por medio del Id. - /// - /// ID de la organización - /// Token de acceso - [HttpGet("read/id")] - public async Task> ReadOneByID([FromQuery] int id, [FromHeader] string token) - { - - // Parámetros - if (id <= 0) - return new(Responses.InvalidParam); - - // Validar el token - var (isValid, _, _, orgID, _) = Jwt.Validate(token); - - - if (!isValid) - return new(Responses.Unauthorized); - - - // Obtiene la organización - var response = await Data.Organizations.Organizations.Read(id); - - // Organización no encontrada. - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = Responses.NotRows, - Message = "No se encontró la organización." - }; - - // No es publica y no pertenece a ella - if (!response.Model.IsPublic && orgID != response.Model.ID) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Esta organización es privada y tu usuario no esta vinculado a ella.", - Model = new() - { - ID = response.Model.ID, - IsPublic = false, - Name = "Organización privada" - } - }; - - return new ReadOneResponse() - { - Response = Responses.Success, - Model = response.Model - }; - - - } - - - - /// - /// Actualiza si una organización tiene lista blanca. - /// - /// Toke de acceso administrador - /// Nuevo estado - [HttpPatch("update/whitelist")] - public async Task Update([FromHeader] string token, [FromQuery] bool haveWhite) - { - - - var (isValid, _, userID, _, _) = Jwt.Validate(token); - - - if (!isValid) - return new(Responses.Unauthorized); - - - var userContext = await Data.Accounts.ReadBasic(userID); - - // Error al encontrar el usuario - if (userContext.Response != Responses.Success) - { - return new ResponseBase - { - Message = "No se encontr� un usuario valido.", - Response = Responses.Unauthorized - }; - } - - // Si el usuario no tiene una organización - if (userContext.Model.OrganizationAccess == null) - { - return new ResponseBase - { - Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", - Response = Responses.Unauthorized - }; - } - - // Verificaci�n del rol dentro de la organización - if (!userContext.Model.OrganizationAccess.Rol.IsAdmin()) - { - return new ResponseBase - { - Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de la lista blanca de esta organización.", - Response = Responses.Unauthorized - }; - } - - - var response = await Data.Organizations.Organizations.UpdateState(userContext.Model.OrganizationAccess.Organization.ID, haveWhite); - - // Retorna el resultado - return response; - - } - - - - /// - /// Actualiza si los usuarios no admins de una organización tienen acceso a su cuenta. - /// - /// Token de acceso administrador - /// Nuevo estado - [HttpPatch("update/access")] - public async Task UpdateAccess([FromHeader] string token, [FromQuery] bool state) - { - - - var (isValid, _, userID, _, _) = Jwt.Validate(token); - - - if (!isValid) - return new(Responses.Unauthorized); - - - var userContext = await Data.Accounts.ReadBasic(userID); - - // Error al encontrar el usuario - if (userContext.Response != Responses.Success) - { - return new ResponseBase - { - Message = "No se encontr� un usuario valido.", - Response = Responses.Unauthorized - }; - } - - // Si el usuario no tiene una organización - if (userContext.Model.OrganizationAccess == null) - { - return new ResponseBase - { - Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", - Response = Responses.Unauthorized - }; - } - - // Verificaci�n del rol dentro de la organización - if (userContext.Model.OrganizationAccess.Rol != OrgRoles.SuperManager) - { - return new ResponseBase - { - Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de accesos de esta organización.", - Response = Responses.Unauthorized - }; - } - - - var response = await Data.Organizations.Organizations.UpdateAccess(userContext.Model.OrganizationAccess.Organization.ID, state); - - // Retorna el resultado - return response; - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V1/Security/Security.cs b/LIN.Identity/Areas/V1/Security/Security.cs deleted file mode 100644 index c2a9c1f..0000000 --- a/LIN.Identity/Areas/V1/Security/Security.cs +++ /dev/null @@ -1,312 +0,0 @@ -namespace LIN.Identity.Areas.V1.Security; - - -[Route("v1/security")] -public class Security : ControllerBase -{ - - - /// - /// Olvidar contraseña - /// - /// Usuario - [HttpPost("password/forget")] - public async Task> ForgetPassword([FromQuery] string user) - { - - // Nulo o vacío - if (string.IsNullOrWhiteSpace(user)) - return new(Responses.InvalidParam); - - - // Obtiene la conexión - var (context, contextKey) = Conexión.GetOneConnection(); - - // Obtiene la información de usuario - var userResponse = await Data.Accounts.ReadBasic(user, context); - - // Evalúa la respuesta - if (userResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(userResponse.Response); - } - - // Modelo del usuario - var userData = userResponse.Model; - - // Obtiene los emails asociados - var emailResponse = await Data.Mails.ReadVerifiedEmails(userData.ID, context); - - // Evalúa la respuesta - if (emailResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(emailResponse.Response); - } - - // Modelos de emails - var emailsData = emailResponse.Models; - - // Emails verificados - var verifiedMail = emailsData.FirstOrDefault(mail => mail.IsDefault); - - // Valida - if (verifiedMail == null || !Mail.Validar(verifiedMail.Email ?? string.Empty)) - { - context.CloseActions(contextKey); - return new(); - } - - // Modelo del link - var link = new UniqueLink() - { - Key = KeyGen.Generate(30, string.Empty), - AccountID = userData.ID, - Status = MagicLinkStatus.Activated, - Vencimiento = DateTime.Now.AddMinutes(30) - }; - - // Crea el nuevo link - var linkResponse = await Data.Links.Create(link); - - // Evalúa - if (linkResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(Responses.NotRows); - } - - - // Envía el correo - await EmailWorker.SendPassword(verifiedMail?.Email!, userData.Usuario, $"http://linapps.co/resetpassword/{userData.ID}/{link.Key}"); - - - return new(Responses.Success, new() - { - Email = CensorEmail(verifiedMail?.Email ?? "") - }); - } - - - - private static string CensorEmail(string email) - { - var atIndex = email.IndexOf('@'); - - if (atIndex >= 0) - { - var dotIndex = email.IndexOf('.'); - if (dotIndex > atIndex + 3) - { - var username = email.Substring(0, atIndex - 3); // Censura los últimos 3 caracteres antes del "@". - var domain = email.Substring(atIndex); // Censura el dominio antes del primer punto. - return $"{username}***{domain}"; - } - } - return email; - } - - - - - - - - /// - /// Reestablece la contraseña - /// - /// Llave del link - /// Nuevo modelo - [HttpPatch("password/reset")] - public async Task ResetPassword([FromHeader] string key, [FromBody] UpdatePasswordModel modelo) - { - - // Nulo o vacío - if (string.IsNullOrWhiteSpace(key)) - return new(Responses.InvalidParam); - - // Link - var link = await Data.Links.ReadOneAnChange(key); - - // Evalúa - if (link.Response != Responses.Success) - { - return new(Responses.Unauthorized); - } - - // Establece el id - modelo.Account = link.Model.AccountID; - - // Respuesta - var updateResponse = await Data.Accounts.Update(modelo); - - if (updateResponse.Response != Responses.Success) - return new(); - - return new(Responses.Success); - } - - - - /// - /// Verifica un correo - /// - /// Key de acceso LINK - [HttpPost("mails/verify")] - public async Task VerifyEmail([FromHeader] string key) - { - - // Obtiene una conexión - var (context, contextKey) = Conexión.GetOneConnection(); - - // Obtiene un link - var linkResponse = await Data.MailLinks.ReadAndDisable(key); - - // Evalúa - if (linkResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(); - } - - // Modelo del link - var linkData = linkResponse.Model; - - - // Obtiene el email - var emailResponse = await Data.Mails.Read(linkData.Email, context); - - // Evalúa - if (emailResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(); - } - - // Modelo del mail - var emailData = emailResponse.Model; - - // Actualiza el estado del mail - var updateState = await Data.Mails.UpdateState(emailData.ID, EmailStatus.Verified, context); - - // Evalúa - if (updateState.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(); - } - - - // Comprobación de email default - { - - // Respuesta - var emails = await Data.Mails.ReadVerifiedEmails(emailData.UserID); - - // Evalúa - if (emails.Response == Responses.Success) - { - // Existe el mail default - var haveDefault = emails.Models.Where(E => E.IsDefault).Any(); - - // Si no existe - if (!haveDefault) - { - // Establece el mail actual - var res = await Data.Mails.SetDefaultEmail(emailData.UserID, emailData.ID); - - } - - } - - } - - return new(Responses.Success); - } - - - - /// - /// Reenvía el correo para la activación - /// - /// Contraseña de la cuenta - /// Modelo del email - [HttpPost("mails/resend")] - public async Task EmailResend([FromHeader] int mailID, [FromHeader] string token) - { - - - var (isValid, _, userID, _, _) = Jwt.Validate(token); - - if (!isValid) - { - return new(Responses.Unauthorized); - } - - var mailResponse = await Data.Mails.Read(mailID); - - // Evaluación de la respuesta - if (mailResponse.Response != Responses.Success) - { - return new(Responses.NotRows); - } - - var mailData = mailResponse.Model; - - if (mailData.Status == EmailStatus.Verified) - { - return new(Responses.Undefined); - } - - - - - // Crea el LINK - var emailLink = new MailMagicLink() - { - Email = mailData.ID, - Status = MagicLinkStatus.Activated, - Vencimiento = DateTime.Now.AddMinutes(10), - Key = KeyGen.Generate(20, "ml") - }; - - - try - { - - var add = await Data.MailLinks.Create(emailLink); - - - if (add.Response != Responses.Success) - { - return new(); - } - - var user = (await Data.Accounts.ReadBasic(userID)).Model; - - var bytes = Encoding.UTF8.GetBytes(mailData.Email); - var mail64 = Convert.ToBase64String(bytes); - - bytes = Encoding.UTF8.GetBytes(user.Usuario); - var user64 = Convert.ToBase64String(bytes); - - await EmailWorker.SendVerification(mailData.Email, $"http://linaccount.somee.com/verificate/{user64}/{mail64}/{emailLink.Key}", mailData.Email); - return new(Responses.Success); - - } - catch - { - - } - finally - { - } - - return new(); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V3/Accounts/AdminController.cs b/LIN.Identity/Areas/V3/Accounts/AdminController.cs deleted file mode 100644 index 4bfcf83..0000000 --- a/LIN.Identity/Areas/V3/Accounts/AdminController.cs +++ /dev/null @@ -1,266 +0,0 @@ -namespace LIN.Identity.Areas.V3; - - -[Route("v3/administrator")] -public class AdminController : ControllerBase -{ - - - - /// - /// Obtiene la información de usuario. - /// - /// ID del usuario - /// Token de acceso - [HttpGet("read/id")] - public async Task> Read([FromQuery] int id, [FromHeader] string token) - { - - if (id <= 0) - return new(Responses.InvalidParam); - - - var (isValid, _, user, orgID, _) = Jwt.Validate(token); - - if (!isValid) - { - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - } - - var rol = (await Data.Accounts.Read(user, new() - { - IncludeOrg = FilterModels.IncludeOrg.None, - FindOn = FilterModels.FindOn.StableAccounts - })).Model.Rol; - - if (rol != AccountRoles.Admin) - { - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Tienes que ser un administrador." - }; - } - - - // Obtiene el usuario - var response = await Data.Accounts.Read(id, new() - { - SensibleInfo = false, - ContextOrg = orgID, - ContextUser = user, - IsAdmin = true, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.Include, - OrgLevel = FilterModels.IncludeOrgLevel.Advance - }); - - // Si es erróneo - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = response.Response, - Model = new() - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Obtiene la información de usuario. - /// - /// Usuario único - /// Token de acceso - [HttpGet("read/user")] - public async Task> Read([FromQuery] string user, [FromHeader] string token) - { - - // Validar el parámetro. - if (string.IsNullOrWhiteSpace(user)) - return new(Responses.InvalidParam); - - // Información del token. - var (isValid, _, userId, orgId, _) = Jwt.Validate(token); - - // Token es invalido. - if (!isValid) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - - - var rol = (await Data.Accounts.Read(userId, new() - { - IncludeOrg = FilterModels.IncludeOrg.None, - FindOn = FilterModels.FindOn.StableAccounts - })).Model.Rol; - - if (rol != AccountRoles.Admin) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Tienes que ser un administrador." - }; - - - - - var response = await Data.Accounts.Read(user, new() - { - SensibleInfo = false, - ContextOrg = orgId, - IsAdmin = true, - ContextUser = userId, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.Include, - OrgLevel = FilterModels.IncludeOrgLevel.Advance - }); - - - - // Si es erróneo - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = response.Response, - Model = new() - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Actualiza la contraseña de una cuenta - /// - /// Modelo de actualización - /// Token de acceso - [HttpPatch("update/password")] - public async Task Update([FromBody] UpdatePasswordModel modelo, [FromHeader] string token) - { - - if (modelo.OldPassword.Length < 4 || modelo.NewPassword.Length < 4) - return new(Responses.InvalidParam); - - - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - - if (!isValid) - { - return new(Responses.Unauthorized); - } - - modelo.Account = userId; - - var actualData = await Data.Accounts.ReadBasic(modelo.Account); - - if (actualData.Response != Responses.Success) - return new(Responses.NotExistAccount); - - var oldEncrypted = actualData.Model.Contraseña; - - - if (oldEncrypted != actualData.Model.Contraseña) - { - return new ResponseBase(Responses.InvalidPassword); - } - - return await Data.Accounts.Update(modelo); - - } - - - - /// - /// Elimina una cuenta - /// - /// Token de acceso - [HttpDelete("delete")] - public async Task Delete([FromHeader] string token) - { - - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - if (!isValid) - return new ResponseBase - { - Response = Responses.Unauthorized, - Message = "Token invalido" - }; - - if (userId <= 0) - return new(Responses.InvalidParam); - - var response = await Data.Accounts.Delete(userId); - return response; - } - - - - /// - /// Desactiva una cuenta - /// - /// Modelo - [HttpPatch("disable")] - public async Task Disable([FromBody] AccountModel user) - { - - if (user.ID <= 0) - { - return new(Responses.ExistAccount); - } - - // Modelo de usuario de la BD - var userModel = await Data.Accounts.ReadBasic(user.ID); - - if (userModel.Model.Contraseña != EncryptClass.Encrypt(user.Contraseña)) - { - return new(Responses.InvalidPassword); - } - - - return await Data.Accounts.Update(user.ID, AccountStatus.Disable); - - } - - - - /// - /// Actualiza la visibilidad de una cuenta - /// - /// Token de acceso - /// Nueva visibilidad - [HttpPatch("update/visibility")] - public async Task Update([FromHeader] string token, [FromHeader] AccountVisibility visibility) - { - - - var (isValid, _, id, _, _) = Jwt.Validate(token); - - if (!isValid) - { - return new(Responses.Unauthorized); - } - - return await Data.Accounts.Update(id, visibility); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V3/Accounts/DeviceController.cs b/LIN.Identity/Areas/V3/Accounts/DeviceController.cs deleted file mode 100644 index d46602c..0000000 --- a/LIN.Identity/Areas/V3/Accounts/DeviceController.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace LIN.Identity.Areas.V3; - - -[Route("v3/devices")] -public class DeviceController : ControllerBase -{ - - - /// - /// Obtiene la lista de dispositivos asociados a una cuenta en tiempo real - /// - /// Token de acceso - [HttpGet] - public HttpReadAllResponse GetAll([FromHeader] string token) - { - try - { - - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - if (!isValid) - { - return new ReadAllResponse - { - Message = "Token Invalido", - Response = Responses.Unauthorized - }; - } - - - var devices = (from account in AccountHub.Cuentas - where account.Key == userId - select account).FirstOrDefault().Value ?? new(); - - - var filter = (from device in devices - where device.Estado == DeviceState.Actived - select device).ToList(); - - // Retorna - return new(Responses.Success, filter ?? new()); - } - catch - { - return new(Responses.Undefined) - { - Message = "Hubo un error al obtener los dispositivos asociados." - }; - } - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V3/Accounts/LoginLogController.cs b/LIN.Identity/Areas/V3/Accounts/LoginLogController.cs deleted file mode 100644 index 3e30b79..0000000 --- a/LIN.Identity/Areas/V3/Accounts/LoginLogController.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace LIN.Identity.Areas.V3; - - -[Route("v3/Account/logs")] -public class LoginLogController : ControllerBase -{ - - - /// - /// Obtienes la lista de accesos asociados a una cuenta - /// - /// Token de acceso - [HttpGet] - public async Task> GetAll([FromHeader] string token) - { - - // JWT. - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - // Validación. - if (!isValid) - return new(Responses.Unauthorized) - { - Message = "Token invalido." - }; - - // Obtiene el usuario. - var result = await Data.Logins.ReadAll(userId); - - // Retorna el resultado. - return result ?? new(); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V3/Accounts/MailController.cs b/LIN.Identity/Areas/V3/Accounts/MailController.cs deleted file mode 100644 index 5c1d792..0000000 --- a/LIN.Identity/Areas/V3/Accounts/MailController.cs +++ /dev/null @@ -1,135 +0,0 @@ -namespace LIN.Identity.Areas.V3; - - -[Route("v3/mails")] -public class MailController : ControllerBase -{ - - - /// - /// Obtiene los mails asociados a una cuenta. - /// - /// Token de acceso - [HttpGet("all")] - public async Task> GetMails([FromHeader] string token) - { - - // Información del token. - var (isValid, _, id, _, _) = Jwt.Validate(token); - - // Validación del token - if (!isValid) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - return await Data.Mails.ReadAll(id); - - } - - - - /// - /// Agrega un nuevo email a una cuenta. - /// - /// Token de acceso. - /// Contraseña de la cuenta. - /// Modelo del email. - [HttpPost("add")] - public async Task EmailAdd([FromHeader] string token, [FromHeader] string password, [FromBody] EmailModel model) - { - - // Obtener el usuario - var userData = await Data.Accounts.ReadBasic(model.UserID); - - // Evaluación de la respuesta - if (userData.Response != Responses.Success) - { - return new(Responses.NotExistAccount); - } - - // Evaluación de la contraseña - if (userData.Model.Contraseña != EncryptClass.Encrypt(password)) - return new(Responses.Unauthorized); - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - // Obtiene los mails actuales - var mails = await Data.Mails.ReadAll(userData.Model.ID, context); - - // Evalúa la respuesta - if (mails.Response != Responses.Success) - { - context.CloseActions(connectionKey); - return new(); - } - - // Este email ya esta en la cuenta? - var countEmail = mails.Models.Where(T => T.Email == model.Email).Count(); - - // Si ya existía - if (countEmail > 0) - { - context.CloseActions(connectionKey); - return new(); - } - - // Agrega el mail - var addMail = await Data.Mails.Create(new() - { - Status = EmailStatus.Unverified, - Email = model.Email, - ID = 0, - UserID = userData.Model.ID - }, context); - - - // Evalúa la respuesta - if (addMail.Response != Responses.Success) - { - context.CloseActions(connectionKey); - return new(); - } - - //Crea el LINK - var emailLink = new MailMagicLink() - { - Email = addMail.LastID, - Status = MagicLinkStatus.Activated, - Vencimiento = DateTime.Now.AddMinutes(10), - Key = KeyGen.Generate(20, "eml") - }; - - try - { - // Agrega y guarda el link - context.DataBase.MailMagicLinks.Add(emailLink); - - - var bytes = Encoding.UTF8.GetBytes(model.Email); - var mail64 = Convert.ToBase64String(bytes); - - bytes = Encoding.UTF8.GetBytes(userData.Model.Usuario); - var user64 = Convert.ToBase64String(bytes); - - await EmailWorker.SendVerification(model.Email, $"http://linaccount.somee.com/verificate/{user64}/{mail64}/{emailLink.Key}", model.Email); - return new(Responses.Success); - - } - catch - { - } - finally - { - context.DataBase.SaveChanges(); - } - - return new(); - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V3/Organizations/ApplicationOrgsController.cs b/LIN.Identity/Areas/V3/Organizations/ApplicationOrgsController.cs deleted file mode 100644 index 5637fbd..0000000 --- a/LIN.Identity/Areas/V3/Organizations/ApplicationOrgsController.cs +++ /dev/null @@ -1,164 +0,0 @@ -namespace LIN.Identity.Areas.V3; - - -[Route("v3/orgs/applications")] -public class ApplicationOrgsController : ControllerBase -{ - - - /// - /// Obtiene la lista de aplicaciones asociadas a una organización - /// - /// Token de acceso - [HttpGet] - public async Task> ReadApps([FromHeader] string token) - { - - // Token - var (isValid, _, _, orgId, _) = Jwt.Validate(token); - - // Token es invalido - if (!isValid) - return new ReadAllResponse - { - Message = "Token invalido.", - Response = Responses.Unauthorized - }; - - - // Si no tiene ninguna organización - if (orgId <= 0) - return new ReadAllResponse - { - Message = "No estas vinculado con ninguna organización.", - Response = Responses.Unauthorized - }; - - - // Obtiene las aplicaciones - var org = await Data.Organizations.Organizations.ReadApps(orgId); - - // Su no se encontraron aplicaciones - if (org.Response != Responses.Success) - return new ReadAllResponse - { - Message = "No found Organization", - Response = Responses.Unauthorized - }; - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - context.CloseActions(connectionKey); - - // Retorna el resultado - return org; - - } - - - - /// - /// Insertar una aplicación en una organización - /// - /// UId de la aplicación - /// Token de acceso - [HttpPost("insert")] - public async Task InsertApp([FromQuery] string appUid, [FromHeader] string token) - { - - // Token - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - - // Si el token es invalido - if (!isValid) - return new CreateResponse - { - Message = "Token invalido", - Response = Responses.Unauthorized - }; - - // Información del usuario - var userData = await Data.Accounts.ReadBasic(userId); - - // Si no existe el usuario - if (userData.Response != Responses.Success) - return new CreateResponse - { - Message = "No se encontró el usuario, talvez fue eliminado o desactivado.", - Response = Responses.NotExistAccount - }; - - - // Si no tiene organización - if (userData.Model.OrganizationAccess == null || userData.Model.OrganizationAccess?.Organization == null) - return new CreateResponse - { - Message = $"El usuario '{userData.Model.Usuario}' no pertenece a una organización.", - Response = Responses.Unauthorized - }; - - // Si el usuario no es admin en la organización - if (!userData.Model.OrganizationAccess.Rol.IsAdmin()) - return new CreateResponse - { - Message = $"El usuario '{userData.Model.Usuario}' no tiene un rol administrador en la organización '{userData.Model.OrganizationAccess.Organization.Name}'", - Response = Responses.Unauthorized - }; - - // Crea la aplicación en la organización - var res = await Data.Organizations.Applications.Create(appUid, userData.Model.OrganizationAccess.Organization.ID); - - // Si hubo une error - if (res.Response != Responses.Success) - return new CreateResponse - { - Message = $"Hubo un error al insertar esta aplicación en la lista blanca permitidas de {userData.Model.OrganizationAccess.Organization.Name}", - Response = Responses.Unauthorized - }; - - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - context.CloseActions(connectionKey); - - // Retorna el resultado - return new CreateResponse - { - LastID = res.LastID, - Message = "", - Response = Responses.Success - }; - } - - - - /// - /// Buscar aplicaciones que no están vinculadas a una organización por medio del un parámetro - /// - /// Parámetro de búsqueda - /// Token de acceso - [HttpGet("search")] - public async Task> Search([FromQuery] string param, [FromHeader] string token) - { - - // Token - var (isValid, _, _, orgId, _) = Jwt.Validate(token); - - // Valida el token - if (!isValid || orgId <= 0) - { - return new(Responses.Unauthorized); - } - - // Encuentra las apps - var finds = await Data.Organizations.Applications.Search(param, orgId); - - return finds; - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V3/Organizations/OrganizationController.cs b/LIN.Identity/Areas/V3/Organizations/OrganizationController.cs deleted file mode 100644 index 3bc99aa..0000000 --- a/LIN.Identity/Areas/V3/Organizations/OrganizationController.cs +++ /dev/null @@ -1,234 +0,0 @@ -using LIN.Identity.Validations; - -namespace LIN.Identity.Areas.V3; - - -[Route("v3/organizations")] -public class OrganizationsController : ControllerBase -{ - - - /// - /// Crea una nueva organización. - /// - /// Modelo de la organización y el usuario administrador - [HttpPost("create")] - public async Task Create([FromBody] OrganizationModel modelo) - { - - // Comprobaciones - if (modelo == null || modelo.Domain.Length <= 0 || modelo.Name.Length <= 0 || modelo.Members.Count <= 0) - return new(Responses.InvalidParam); - - - // Conexi�n - var (context, connectionKey) = Conexión.GetOneConnection(); - - - // organización del modelo - modelo.ID = 0; - modelo.AppList = new(); - - modelo.Members[0].Member = Account.Process(modelo.Members[0].Member); - foreach (var member in modelo.Members) - { - member.Rol = OrgRoles.SuperManager; - member.Organization = modelo; - } - - // Creaci�n de la organización - var response = await Data.Organizations.Organizations.Create(modelo, context); - - // Evaluaci�n - if (response.Response != Responses.Success) - return new(response.Response); - - context.CloseActions(connectionKey); - - // Retorna el resultado - return new CreateResponse() - { - LastID = response.Model.ID, - Response = Responses.Success, - Message = "Success" - }; - - } - - - - /// - /// Obtiene una organización por medio del Id. - /// - /// ID de la organización - /// Token de acceso - [HttpGet("read/id")] - public async Task> ReadOneByID([FromQuery] int id, [FromHeader] string token) - { - - // Parámetros - if (id <= 0) - return new(Responses.InvalidParam); - - // Validar el token - var (isValid, _, _, orgID, _) = Jwt.Validate(token); - - - if (!isValid) - return new(Responses.Unauthorized); - - - // Obtiene la organización - var response = await Data.Organizations.Organizations.Read(id); - - // Organización no encontrada. - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = Responses.NotRows, - Message = "No se encontró la organización." - }; - - // No es publica y no pertenece a ella - if (!response.Model.IsPublic && orgID != response.Model.ID) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Esta organización es privada y tu usuario no esta vinculado a ella.", - Model = new() - { - ID = response.Model.ID, - IsPublic = false, - Name = "Organización privada" - } - }; - - return new ReadOneResponse() - { - Response = Responses.Success, - Model = response.Model - }; - - - } - - - - /// - /// Actualiza si una organización tiene lista blanca. - /// - /// Toke de acceso administrador - /// Nuevo estado - [HttpPatch("update/whitelist")] - public async Task Update([FromHeader] string token, [FromQuery] bool haveWhite) - { - - - var (isValid, _, userID, _, _) = Jwt.Validate(token); - - - if (!isValid) - return new(Responses.Unauthorized); - - - var userContext = await Data.Accounts.ReadBasic(userID); - - // Error al encontrar el usuario - if (userContext.Response != Responses.Success) - { - return new ResponseBase - { - Message = "No se encontr� un usuario valido.", - Response = Responses.Unauthorized - }; - } - - // Si el usuario no tiene una organización - if (userContext.Model.OrganizationAccess == null) - { - return new ResponseBase - { - Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", - Response = Responses.Unauthorized - }; - } - - // Verificaci�n del rol dentro de la organización - if (!userContext.Model.OrganizationAccess.Rol.IsAdmin()) - { - return new ResponseBase - { - Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de la lista blanca de esta organización.", - Response = Responses.Unauthorized - }; - } - - - var response = await Data.Organizations.Organizations.UpdateState(userContext.Model.OrganizationAccess.Organization.ID, haveWhite); - - // Retorna el resultado - return response; - - } - - - - /// - /// Actualiza si los usuarios no admins de una organización tienen acceso a su cuenta. - /// - /// Token de acceso administrador - /// Nuevo estado - [HttpPatch("update/access")] - public async Task UpdateAccess([FromHeader] string token, [FromQuery] bool state) - { - - - var (isValid, _, userID, _, _) = Jwt.Validate(token); - - - if (!isValid) - return new(Responses.Unauthorized); - - - var userContext = await Data.Accounts.ReadBasic(userID); - - // Error al encontrar el usuario - if (userContext.Response != Responses.Success) - { - return new ResponseBase - { - Message = "No se encontr� un usuario valido.", - Response = Responses.Unauthorized - }; - } - - // Si el usuario no tiene una organización - if (userContext.Model.OrganizationAccess == null) - { - return new ResponseBase - { - Message = $"El usuario '{userContext.Model.Usuario}' no pertenece a una organización.", - Response = Responses.Unauthorized - }; - } - - // Verificaci�n del rol dentro de la organización - if (userContext.Model.OrganizationAccess.Rol != OrgRoles.SuperManager) - { - return new ResponseBase - { - Message = $"El usuario '{userContext.Model.Usuario}' no puede actualizar el estado de accesos de esta organización.", - Response = Responses.Unauthorized - }; - } - - - var response = await Data.Organizations.Organizations.UpdateAccess(userContext.Model.OrganizationAccess.Organization.ID, state); - - // Retorna el resultado - return response; - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/V3/Security/Security.cs b/LIN.Identity/Areas/V3/Security/Security.cs deleted file mode 100644 index 2f46284..0000000 --- a/LIN.Identity/Areas/V3/Security/Security.cs +++ /dev/null @@ -1,312 +0,0 @@ -namespace LIN.Identity.Areas.V3; - - -[Route("v3/security")] -public class Security : ControllerBase -{ - - - /// - /// Olvidar contraseña - /// - /// Usuario - [HttpPost("password/forget")] - public async Task> ForgetPassword([FromQuery] string user) - { - - // Nulo o vacío - if (string.IsNullOrWhiteSpace(user)) - return new(Responses.InvalidParam); - - - // Obtiene la conexión - var (context, contextKey) = Conexión.GetOneConnection(); - - // Obtiene la información de usuario - var userResponse = await Data.Accounts.ReadBasic(user, context); - - // Evalúa la respuesta - if (userResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(userResponse.Response); - } - - // Modelo del usuario - var userData = userResponse.Model; - - // Obtiene los emails asociados - var emailResponse = await Data.Mails.ReadVerifiedEmails(userData.ID, context); - - // Evalúa la respuesta - if (emailResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(emailResponse.Response); - } - - // Modelos de emails - var emailsData = emailResponse.Models; - - // Emails verificados - var verifiedMail = emailsData.FirstOrDefault(mail => mail.IsDefault); - - // Valida - if (verifiedMail == null || !Mail.Validar(verifiedMail.Email ?? string.Empty)) - { - context.CloseActions(contextKey); - return new(); - } - - // Modelo del link - var link = new UniqueLink() - { - Key = KeyGen.Generate(30, string.Empty), - AccountID = userData.ID, - Status = MagicLinkStatus.Activated, - Vencimiento = DateTime.Now.AddMinutes(30) - }; - - // Crea el nuevo link - var linkResponse = await Data.Links.Create(link); - - // Evalúa - if (linkResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(Responses.NotRows); - } - - - // Envía el correo - await EmailWorker.SendPassword(verifiedMail?.Email!, userData.Usuario, $"http://linapps.co/resetpassword/{userData.ID}/{link.Key}"); - - - return new(Responses.Success, new() - { - Email = CensorEmail(verifiedMail?.Email ?? "") - }); - } - - - - private static string CensorEmail(string email) - { - var atIndex = email.IndexOf('@'); - - if (atIndex >= 0) - { - var dotIndex = email.IndexOf('.'); - if (dotIndex > atIndex + 3) - { - var username = email.Substring(0, atIndex - 3); // Censura los últimos 3 caracteres antes del "@". - var domain = email.Substring(atIndex); // Censura el dominio antes del primer punto. - return $"{username}***{domain}"; - } - } - return email; - } - - - - - - - - /// - /// Reestablece la contraseña - /// - /// Llave del link - /// Nuevo modelo - [HttpPatch("password/reset")] - public async Task ResetPassword([FromHeader] string key, [FromBody] UpdatePasswordModel modelo) - { - - // Nulo o vacío - if (string.IsNullOrWhiteSpace(key)) - return new(Responses.InvalidParam); - - // Link - var link = await Data.Links.ReadOneAnChange(key); - - // Evalúa - if (link.Response != Responses.Success) - { - return new(Responses.Unauthorized); - } - - // Establece el id - modelo.Account = link.Model.AccountID; - - // Respuesta - var updateResponse = await Data.Accounts.Update(modelo); - - if (updateResponse.Response != Responses.Success) - return new(); - - return new(Responses.Success); - } - - - - /// - /// Verifica un correo - /// - /// Key de acceso LINK - [HttpPost("mails/verify")] - public async Task VerifyEmail([FromHeader] string key) - { - - // Obtiene una conexión - var (context, contextKey) = Conexión.GetOneConnection(); - - // Obtiene un link - var linkResponse = await Data.MailLinks.ReadAndDisable(key); - - // Evalúa - if (linkResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(); - } - - // Modelo del link - var linkData = linkResponse.Model; - - - // Obtiene el email - var emailResponse = await Data.Mails.Read(linkData.Email, context); - - // Evalúa - if (emailResponse.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(); - } - - // Modelo del mail - var emailData = emailResponse.Model; - - // Actualiza el estado del mail - var updateState = await Data.Mails.UpdateState(emailData.ID, EmailStatus.Verified, context); - - // Evalúa - if (updateState.Response != Responses.Success) - { - context.CloseActions(contextKey); - return new(); - } - - - // Comprobación de email default - { - - // Respuesta - var emails = await Data.Mails.ReadVerifiedEmails(emailData.UserID); - - // Evalúa - if (emails.Response == Responses.Success) - { - // Existe el mail default - var haveDefault = emails.Models.Where(E => E.IsDefault).Any(); - - // Si no existe - if (!haveDefault) - { - // Establece el mail actual - var res = await Data.Mails.SetDefaultEmail(emailData.UserID, emailData.ID); - - } - - } - - } - - return new(Responses.Success); - } - - - - /// - /// Reenvía el correo para la activación - /// - /// Contraseña de la cuenta - /// Modelo del email - [HttpPost("mails/resend")] - public async Task EmailResend([FromHeader] int mailID, [FromHeader] string token) - { - - - var (isValid, _, userID, _, _) = Jwt.Validate(token); - - if (!isValid) - { - return new(Responses.Unauthorized); - } - - var mailResponse = await Data.Mails.Read(mailID); - - // Evaluación de la respuesta - if (mailResponse.Response != Responses.Success) - { - return new(Responses.NotRows); - } - - var mailData = mailResponse.Model; - - if (mailData.Status == EmailStatus.Verified) - { - return new(Responses.Undefined); - } - - - - - // Crea el LINK - var emailLink = new MailMagicLink() - { - Email = mailData.ID, - Status = MagicLinkStatus.Activated, - Vencimiento = DateTime.Now.AddMinutes(10), - Key = KeyGen.Generate(20, "ml") - }; - - - try - { - - var add = await Data.MailLinks.Create(emailLink); - - - if (add.Response != Responses.Success) - { - return new(); - } - - var user = (await Data.Accounts.ReadBasic(userID)).Model; - - var bytes = Encoding.UTF8.GetBytes(mailData.Email); - var mail64 = Convert.ToBase64String(bytes); - - bytes = Encoding.UTF8.GetBytes(user.Usuario); - var user64 = Convert.ToBase64String(bytes); - - await EmailWorker.SendVerification(mailData.Email, $"http://linaccount.somee.com/verificate/{user64}/{mail64}/{emailLink.Key}", mailData.Email); - return new(Responses.Success); - - } - catch - { - - } - finally - { - } - - return new(); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Accounts/AccountsGet.cs b/LIN.Identity/Data/Accounts/AccountsGet.cs index bca3115..0397b19 100644 --- a/LIN.Identity/Data/Accounts/AccountsGet.cs +++ b/LIN.Identity/Data/Accounts/AccountsGet.cs @@ -278,7 +278,12 @@ public static async Task> ReadBasic(int id, Conexi select new AccountModel { ID = account.ID, - Usuario = account.Usuario, + Identity = new() + { + Id = account.Identity.Id, + Type = account.Identity.Type, + Unique = account.Identity.Unique + }, Contraseña = account.Contraseña, Estado = account.Estado, Nombre = account.Nombre, @@ -316,11 +321,16 @@ public static async Task> ReadBasic(string user, C { var query = from account in Queries.Accounts.GetValidAccounts(context) - where account.Usuario == user + where account.Identity.Unique == user select new AccountModel { ID = account.ID, - Usuario = account.Usuario, + Identity = new() + { + Id = account.Identity.Id, + Type = account.Identity.Type, + Unique = account.Identity.Unique + }, Contraseña = account.Contraseña, Estado = account.Estado, Nombre = account.Nombre, diff --git a/LIN.Identity/Data/Accounts/AccountsPost.cs b/LIN.Identity/Data/Accounts/AccountsPost.cs index af6c5b6..6374e4d 100644 --- a/LIN.Identity/Data/Accounts/AccountsPost.cs +++ b/LIN.Identity/Data/Accounts/AccountsPost.cs @@ -36,6 +36,8 @@ public static async Task> Create(AccountModel data public static async Task> Create(AccountModel data, Conexión context) { + // Identidad. + data.Identity.Id = 0; data.ID = 0; // Ejecución diff --git a/LIN.Identity/Data/Accounts/AccountsUpdate.cs b/LIN.Identity/Data/Accounts/AccountsUpdate.cs index c21cb6d..44d911f 100644 --- a/LIN.Identity/Data/Accounts/AccountsUpdate.cs +++ b/LIN.Identity/Data/Accounts/AccountsUpdate.cs @@ -40,22 +40,7 @@ public static async Task Update(AccountModel modelo) - /// - /// Actualiza las credenciales (Contraseña de un usuario) - /// - /// Nuevas credenciales - public static async Task Update(UpdatePasswordModel newData) - { - - var (context, key) = Conexión.GetOneConnection(); - - var res = await Update(newData, context); - context.CloseActions(key); - return res; - - } - - + /// /// Actualiza el estado de un usuario @@ -171,7 +156,6 @@ public static async Task Update(AccountModel modelo, Conexión con // Nuevos datos user.Perfil = modelo.Perfil; user.Nombre = modelo.Nombre; - user.Genero = modelo.Genero; context.DataBase.SaveChanges(); return new(Responses.Success); @@ -185,37 +169,6 @@ public static async Task Update(AccountModel modelo, Conexión con - /// - /// Actualiza la contraseña - /// - /// Nuevas credenciales - /// Contexto de conexión con la BD - public static async Task Update(UpdatePasswordModel newData, Conexión context) - { - - // Encontrar el usuario - var usuario = await (from U in context.DataBase.Accounts - where U.ID == newData.Account - select U).FirstOrDefaultAsync(); - - // Si el usuario no existe - if (usuario == null) - { - return new(Responses.NotExistAccount); - } - - // Confirmar contraseña - var newEncrypted = EncryptClass.Encrypt(newData.NewPassword); - - // Cambiar Contraseña - usuario.Contraseña = newEncrypted; - - context.DataBase.SaveChanges(); - return new(Responses.Success); - - } - - /// /// Actualiza la organización de una cuenta @@ -305,7 +258,7 @@ public static async Task Update(int user, Genders genero, Conexió } // Cambiar Contraseña - usuario.Genero = genero; + // usuario.Genero = genero; context.DataBase.SaveChanges(); return new(Responses.Success); diff --git a/LIN.Identity/Data/Applications.cs b/LIN.Identity/Data/Applications.cs index c65ff84..7c2dfda 100644 --- a/LIN.Identity/Data/Applications.cs +++ b/LIN.Identity/Data/Applications.cs @@ -38,25 +38,6 @@ public static async Task> ReadAll(int id) - public static async Task> AllowTo(int appId, int accountId) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await AllowTo(appId, accountId, context); - context.CloseActions(contextKey); - return res; - } - - - public static async Task> IsAllow(int appId, int accountId) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await IsAllow(appId, accountId, context); - context.CloseActions(contextKey); - return res; - } - @@ -111,11 +92,7 @@ public static async Task Create(ApplicationModel data, Conexión try { - foreach (var account in data.Allowed) - { - context.DataBase.Attach(account.Account); - account.App = data; - } + var res = await context.DataBase.Applications.AddAsync(data); context.DataBase.SaveChanges(); @@ -264,73 +241,9 @@ public static async Task> ReadByAppUid(string - public static async Task> IsAllow(int appId, int accountId, Conexión context) - { - - // Ejecución - try - { - - // Query - var has = from access in context.DataBase.ApplicationAccess - where access.AppID == appId && access.AccountID == accountId - select access; - - var s = has.ToQueryString(); - - var result = await has.FirstOrDefaultAsync(); - - // Email no existe - if (result == null) - { - return new(Responses.Unauthorized); - } - - return new(Responses.Success, true); - } - catch - { - } - - return new(); - } - - - - public static async Task> AllowTo(int appId, int accountId, Conexión context) - { - - // Ejecución - try - { - - - var access = new AppAccessModel() - { - Account = new() - { - ID = accountId - }, - App = new() - { - ID = appId - } - }; - - context.DataBase.Attach(access.Account); - context.DataBase.Attach(access.App); - - await context.DataBase.AddAsync(access); - context.DataBase.SaveChanges(); - return new(Responses.Success, true); - } - catch - { - } - return new(); - } + diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index 3b38c4c..aa7aaca 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -6,41 +6,40 @@ public class Context : DbContext /// - /// Tabla de cuentas + /// Identidades. /// - public DbSet Accounts { get; set; } + public DbSet Identities { get; set; } /// - /// Tabla de organizaciones + /// Cuentas de usuario. /// - public DbSet Organizations { get; set; } - + public DbSet Accounts { get; set; } /// - /// Tabla de accesos a organizaciones + /// Organizaciones. /// - public DbSet OrganizationAccess { get; set; } - - - + public DbSet Organizations { get; set; } - public DbSet ApplicationAccess { get; set; } + /// + /// Directorios. + /// + public DbSet Directories { get; set; } /// - /// Tabla de aplicaciones + /// Accesos a organizaciones. /// - public DbSet Applications { get; set; } - + public DbSet OrganizationAccess { get; set; } /// /// Tabla de aplicaciones /// - public DbSet AppOnOrg { get; set; } + public DbSet Applications { get; set; } + /// @@ -57,18 +56,6 @@ public class Context : DbContext - /// - /// Tabla de links únicos - /// - public DbSet UniqueLinks { get; set; } - - - - /// - /// Tabla de links únicos para email - /// - public DbSet MailMagicLinks { get; set; } - @@ -81,111 +68,108 @@ public Context(DbContextOptions options) : base(options) { } + /// /// Naming DB /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - // Indices y identidad - modelBuilder.Entity() - .HasIndex(e => e.Usuario) + + // Indices y identidad. + modelBuilder.Entity() + .HasIndex(e => e.Unique) .IsUnique(); - // Indices y identidad + // Indices y identidad. modelBuilder.Entity() .HasIndex(e => e.Domain) .IsUnique(); - // Indices y identidad + // Indices y identidad. modelBuilder.Entity() .HasIndex(e => e.Key) .IsUnique(); - // Indices y identidad + // Indices y identidad. modelBuilder.Entity() .HasIndex(e => e.ApplicationUid) .IsUnique(); - - // Indices y identidad + // Indices y identidad. modelBuilder.Entity() .HasIndex(e => e.Email) - .IsUnique(); + .IsUnique(); - // Indices y identidad - modelBuilder.Entity() - .HasIndex(e => e.Key) - .IsUnique(); - // Indices y identidad - modelBuilder.Entity() - .HasIndex(e => e.Key) - .IsUnique(); - // Indices - modelBuilder.Entity().HasIndex(e => e.ID); - modelBuilder.Entity() - .HasOne(a => a.OrganizationAccess) - .WithOne(oa => oa.Member) - .HasForeignKey(oa => oa.ID); - modelBuilder.Entity() - .HasKey(a => new - { - a.AppID, - a.OrgID - }); - modelBuilder.Entity() - .HasOne(p => p.App) - .WithMany() - .HasForeignKey(p => p.AppID); - modelBuilder.Entity() - .HasOne(p => p.Organization) + modelBuilder.Entity() + .HasOne(p => p.Directory) .WithMany() - .HasForeignKey(p => p.OrgID); + .HasForeignKey(p => p.DirectoryId); - modelBuilder.Entity() - .HasKey(a => new - { - a.AppID, - a.AccountID - }); - modelBuilder.Entity() - .HasOne(p => p.App) - .WithMany() - .HasForeignKey(p => p.AppID); + modelBuilder.Entity() + .HasOne(oa => oa.Organization) + .WithMany(o => o.Members) + .HasForeignKey(oa => oa.OrganizationId) + .OnDelete(DeleteBehavior.Restrict); - modelBuilder.Entity() - .HasOne(p => p.Account) - .WithMany() - .HasForeignKey(p => p.AccountID); + modelBuilder.Entity() + .HasOne(dm => dm.Directory) + .WithMany(d => d.Members) + .HasForeignKey(dm => dm.DirectoryId) + .OnDelete(DeleteBehavior.Restrict); // You can a + modelBuilder.Entity() + .HasOne(p => p.Account) + .WithMany(d => d.DirectoryMembers) + .HasForeignKey(p => p.AccountId) + .OnDelete(DeleteBehavior.Restrict); ; - // Nombre de la tablas - modelBuilder.Entity().ToTable("ACCOUNTS"); + // Configure OrganizationAccessModel + modelBuilder.Entity() + .HasOne(o => o.Member) + .WithOne(a => a.OrganizationAccess) // Assuming OrganizationAccess is the navigation property in AccountModel + .HasForeignKey(o => o.MemberId) + .IsRequired(); + + + + + + + modelBuilder.Entity() + .HasKey(t => new + { + t.AccountId, + t.DirectoryId + }); + + + // Nombre de la identidades. + modelBuilder.Entity().ToTable("IDENTITIES"); modelBuilder.Entity().ToTable("ORGANIZATIONS"); + modelBuilder.Entity().ToTable("ACCOUNTS"); modelBuilder.Entity().ToTable("APPLICATIONS"); modelBuilder.Entity().ToTable("EMAILS"); - modelBuilder.Entity().ToTable("LOGIN_LOGS"); - modelBuilder.Entity().ToTable("UNIQUE_LINKS"); - modelBuilder.Entity().ToTable("EMAIL_MAGIC_LINKS"); + modelBuilder.Entity().ToTable("DIRECTORIES"); } diff --git a/LIN.Identity/Data/Directories/Directories.cs b/LIN.Identity/Data/Directories/Directories.cs new file mode 100644 index 0000000..711aafb --- /dev/null +++ b/LIN.Identity/Data/Directories/Directories.cs @@ -0,0 +1,50 @@ +namespace LIN.Identity.Data; + + +public class Directories +{ + + + public static async Task> Read(int id) + { + + // Obtiene la conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + var res = await Read(id, context); + context.CloseActions(connectionKey); + return res; + + } + + + + + public static async Task> Read(int id, Conexión context) + { + + // Ejecución + try + { + + var query = Queries.Accounts.GetDirectory(id, context); + + // Obtiene el usuario + var result = await query.FirstOrDefaultAsync(); + + // Si no existe el modelo + if (result == null) + return new(Responses.NotExistAccount); + + return new(Responses.Success, result); + } + catch + { + } + + return new(); + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Data/Links.cs b/LIN.Identity/Data/Links.cs deleted file mode 100644 index febf93c..0000000 --- a/LIN.Identity/Data/Links.cs +++ /dev/null @@ -1,164 +0,0 @@ -namespace LIN.Identity.Data; - - -public class Links -{ - - - #region Abstracciones - - - /// - /// Crea un nuevo LINK - /// - /// Modelo del link - public static async Task Create(UniqueLink data) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await Create(data, context); - context.CloseActions(connectionKey); - return res; - } - - - - /// - /// Obtiene la lista de links asociados a una cuenta - /// - /// ID de la cuenta - public static async Task> ReadAll(int id) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadAll(id, context); - context.CloseActions(connectionKey); - return res; - - } - - - - /// - /// Obtiene un link y cambia su estado - /// - /// - public static async Task> ReadOneAnChange(string value) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadOneAnChange(value, context); - context.CloseActions(connectionKey); - return res; - - } - - - #endregion - - - /// - /// Crea un nuevo enlace - /// - /// Modelo del enlace - /// Contexto de conexión - public static async Task Create(UniqueLink data, Conexión context) - { - // ID en 0 - data.ID = 0; - - // Ejecución - try - { - var res = context.DataBase.UniqueLinks.Add(data); - await context.DataBase.SaveChangesAsync(); - return new(Responses.Success, data.ID); - } - catch - { - context.DataBase.Remove(data); - } - return new(); - } - - - - /// - /// Obtiene la lista de links activos asociados a una cuenta - /// - /// ID de la cuenta - /// Contexto de conexión - public static async Task> ReadAll(int id, Conexión context) - { - - // Ejecución - try - { - - var now = DateTime.Now; - - var activos = await (from L in context.DataBase.UniqueLinks - where L.AccountID == id - where L.Vencimiento > now - where L.Status == MagicLinkStatus.Activated - select L).ToListAsync(); - - var lista = activos; - - return new(Responses.Success, lista); - } - catch - { - } - return new(); - - } - - - - /// - /// Obtiene un Magic Link y cambia su estado - /// - /// - /// Contexto de conexión - public static async Task> ReadOneAnChange(string value, Conexión context) - { - - // Ejecución - try - { - - // Fecha actual - var now = DateTime.Now; - - // Consulta - var elemento = await (from L in context.DataBase.UniqueLinks - where L.Vencimiento > now - where L.Status == MagicLinkStatus.Activated - where L.Key == value - select L).FirstOrDefaultAsync(); - // SI es null - if (elemento == null) - return new(Responses.NotRows); - - // Cambia el estado - elemento.Status = MagicLinkStatus.None; - context.DataBase.SaveChanges(); - - return new(Responses.Success, elemento); - } - catch - { - } - return new(); - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Logins.cs b/LIN.Identity/Data/Logins.cs index d90072d..c5b4248 100644 --- a/LIN.Identity/Data/Logins.cs +++ b/LIN.Identity/Data/Logins.cs @@ -96,7 +96,6 @@ orderby L.Date descending ID = L.ID, Type = L.Type, Date = L.Date, - Platform = L.Platform, Application = new() { Name = L.Application.Name, diff --git a/LIN.Identity/Data/MailLinks.cs b/LIN.Identity/Data/MailLinks.cs deleted file mode 100644 index 5547bfc..0000000 --- a/LIN.Identity/Data/MailLinks.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace LIN.Identity.Data; - - -public class MailLinks -{ - - - #region Abstracciones - - - /// - /// Crea un nuevo LINK - /// - /// Modelo del link - public static async Task Create(MailMagicLink data) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - var res = await Create(data, context); - context.CloseActions(connectionKey); - return res; - } - - - - /// - /// Obtiene un link activo según su key - /// - /// - public static async Task> ReadAndDisable(string value) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadAndDisable(value, context); - context.CloseActions(connectionKey); - return res; - - } - - - #endregion - - - - /// - /// Crea un nuevo enlace para email - /// - /// Modelo del link - /// Contexto de conexión - public static async Task Create(MailMagicLink data, Conexión context) - { - // ID en 0 - data.ID = 0; - - // Ejecución - try - { - var res = context.DataBase.MailMagicLinks.Add(data); - await context.DataBase.SaveChangesAsync(); - - return new(Responses.Success, data.ID); - } - catch - { - context.DataBase.Remove(data); - } - return new(); - } - - - - /// - /// Obtiene un link activo según su key - /// - /// ID de la cuenta - /// Contexto de conexión - /// Llave para cerrar la conexión - public static async Task> ReadAndDisable(string key, Conexión context) - { - - // Ejecución - try - { - - var now = DateTime.Now; - var verification = await (from L in context.DataBase.MailMagicLinks - where L.Key == key - where L.Vencimiento > now - where L.Status == MagicLinkStatus.Activated - select L).FirstOrDefaultAsync(); - - - if (verification == null) - { - return new(); - } - - verification.Status = MagicLinkStatus.Deactivated; - context.DataBase.SaveChanges(); - - return new(Responses.Success, verification); - } - catch - { - } - - return new(); - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Organizations/Applications.cs b/LIN.Identity/Data/Organizations/Applications.cs deleted file mode 100644 index bea9507..0000000 --- a/LIN.Identity/Data/Organizations/Applications.cs +++ /dev/null @@ -1,183 +0,0 @@ -namespace LIN.Identity.Data.Organizations; - - -public class Applications -{ - - - - #region Abstractions - - - /// - /// Obtiene la lista de apps que coincidan con un patron y que no estén agregadas a una organización - /// - /// Parámetro de búsqueda - /// ID de la organización - public static async Task> Search(string param, int org) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await Search(param, org, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Crea una pp en la lista blanca de una organización - /// - /// UID de la aplicación - /// ID de la organización - public static async Task Create(string appUid, int org) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await Create(appUid, org, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Encuentra una app en una organización - /// - /// Key de la app - /// ID de la organización - public static async Task> AppOnOrg(string key, int org) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await AppOnOrg(key, org, context); - context.CloseActions(contextKey); - return res; - } - - - #endregion - - - - /// - /// Encuentra una app en una organización - /// - /// Key de la app - /// ID de la organización - /// Contexto de conexión - public static async Task> AppOnOrg(string key, int org, Conexión context) - { - - // Ejecución - try - { - - // Query - var app = await (from E in context.DataBase.AppOnOrg - where E.Organization.ID == org - where E.App.Key == key - select E).FirstOrDefaultAsync(); - - if (app == null) - return new(Responses.NotRows); - - return new(Responses.Success, app); - } - catch - { - } - - return new(); - } - - - - /// - /// Crea una app en una organización - /// - /// Key de la app - /// ID de la organización - /// Contexto de conexión - public static async Task Create(string appUid, int org, Conexión context) - { - - // Ejecución - try - { - - // Query - var app = await (from A in context.DataBase.Applications - where A.ApplicationUid == appUid - select A).FirstOrDefaultAsync(); - - - if (app == null) - return new(Responses.NotRows); - - - var onOrg = new AppOnOrgModel() - { - State = AppOnOrgStates.Activated, - App = app, - Organization = new() - { - ID = org - } - }; - context.DataBase.Attach(onOrg.Organization); - - await context.DataBase.AddAsync(onOrg); - - context.DataBase.SaveChanges(); - - return new(Responses.Success, onOrg.ID); - } - catch - { - } - - return new(); - } - - - - /// - /// Obtiene la lista de apps que coincidan con un patron y que no estén agregadas a una organización - /// - /// Parámetro de búsqueda - /// ID de la organización - /// Contexto de conexión - public static async Task> Search(string param, int org, Conexión context) - { - - // Ejecución - try - { - - // Query - var apps = await (from A in context.DataBase.Applications - where !context.DataBase.AppOnOrg.Any(aog => aog.AppID == A.ID && aog.OrgID == org) - where A.Name.ToLower().Contains(param.ToLower()) - || A.ApplicationUid.ToLower().Contains(param.ToLower()) - select new ApplicationModel - { - ID = A.ID, - ApplicationUid = A.ApplicationUid, - Badge = A.Badge, - Name = A.Name - }).Take(10).ToListAsync(); - - - return new(Responses.Success, apps); - } - catch - { - } - - return new(); - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Organizations/Members.cs b/LIN.Identity/Data/Organizations/Members.cs index 8aec9e0..af2acaf 100644 --- a/LIN.Identity/Data/Organizations/Members.cs +++ b/LIN.Identity/Data/Organizations/Members.cs @@ -124,8 +124,13 @@ public static async Task> ReadAll(int id, Conexió Creación = O.Member.Creación, ID = O.Member.ID, Nombre = O.Member.Nombre, - Genero = O.Member.Genero, - Usuario = O.Member.Usuario, + Identity = new() + { + Id = O.Member.Identity.Id, + Type = O.Member.Identity.Type, + Unique = O.Member.Identity.Unique + }, + OrganizationAccess = new() { ID = O.Member.OrganizationAccess == null ? 0 : O.Member.OrganizationAccess.ID, diff --git a/LIN.Identity/Data/Organizations/Organizations.cs b/LIN.Identity/Data/Organizations/Organizations.cs index c895447..4e8a6a4 100644 --- a/LIN.Identity/Data/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Organizations/Organizations.cs @@ -37,51 +37,6 @@ public static async Task> Read(int id) - /// - /// Obtiene la lista de aplicaciones permitidas en una organización. - /// - /// ID de la organización - public static async Task> ReadApps(int id) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await ReadApps(id, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Actualiza el estado de la lista blanca de una organización - /// - /// ID de la organización - /// Nuevo estado - public static async Task UpdateState(int id, bool estado) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await UpdateState(id, estado, context); - context.CloseActions(contextKey); - return res; - } - - - - - /// - /// Actualiza el estado de logins una organización - /// - /// ID de la organización - /// Nuevo estado - public static async Task UpdateAccess(int id, bool estado) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await UpdateAccess(id, estado, context); - context.CloseActions(contextKey); - return res; - } #endregion @@ -104,170 +59,118 @@ public static async Task> Create(Organization try { - string[] defaultApps = - { - "linauthenticator", "linorgsapp" - }; + List accounts = []; - foreach (var app in defaultApps) + // Crear cuentas + foreach (var account in data.Members.Select(t => t.Member)) { - var appData = await Data.Applications.ReadByAppUid(app, context); - - if (appData.Response == Responses.Success) - data.AppList.Add(new() - { - Organization = data, - State = AppOnOrgStates.Activated, - App = appData.Model - }); + AccountModel accountModel = new() + { + Birthday = account.Birthday, + Contraseña = account.Contraseña, + Creación = account.Creación, + Estado = account.Estado, + ID = 0, + Identity = account.Identity, + Insignia = account.Insignia, + Nombre = account.Nombre, + Visibilidad = account.Visibilidad, + Rol = account.Rol, + Perfil = account.Perfil, + OrganizationAccess = null, + DirectoryMembers = [], + IdentityId = 0, + }; + + accounts.Add(accountModel); +context.DataBase.Accounts.Add(accountModel); } - var res = await context.DataBase.Organizations.AddAsync(data); + context.DataBase.SaveChanges(); - transaction.Commit(); - return new(Responses.Success, data); - } - catch (Exception ex) - { - transaction.Rollback(); - if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) - return new(Responses.Undefined); - - } - } - return new(); - } - - - - - /// - /// Obtiene una organización. - /// - /// ID de la organización - /// Contexto de conexión - public static async Task> Read(int id, Conexión context) - { - - // Ejecución - try - { - - // Query - var org = await (from E in context.DataBase.Organizations - where E.ID == id - select E).FirstOrDefaultAsync(); - - // Email no existe - if (org == null) - { - return new(Responses.NotRows); - } - - return new(Responses.Success, org); - } - catch - { - } - - return new(); - } + OrganizationModel org = new() + { + Domain = data.Domain, + Name = data.Name, + IsPublic = data.IsPublic, + ID = 0, + Directory = new() + { + Creación = DateTime.Now, + ID = 0, + Identity = new() + { + Unique = data.Directory.Identity.Unique, + Type = IdentityTypes.Directory + }, + Nombre = data.Directory.Nombre, + IdentityId = 0, + } + }; - /// - /// Obtiene la lista de aplicaciones permitidas en una organización. - /// - /// ID de la organización - /// Contexto de conexión - public static async Task> ReadApps(int id, Conexión context) - { - - // Ejecución - try - { - - // Organización - var apps = from ORG in context.DataBase.AppOnOrg - where ORG.Organization.ID == id - select new ApplicationModel - { - ID = ORG.App.ID, - Name = ORG.App.Name, - Badge = ORG.App.Badge, - ApplicationUid = ORG.App.ApplicationUid - }; + org.Members = []; + foreach(var x in accounts) + { + org.Members.Add(new() + { + ID = 0, + Member = x, + Organization = org, + Rol = OrgRoles.SuperManager + }); + } - var lista = await apps.ToListAsync(); - // Email no existe - if (lista == null) - return new(Responses.NotRows); - return new(Responses.Success, lista); - } - catch - { - } + var res = await context.DataBase.Organizations.AddAsync(org); - return new(); - } + context.DataBase.SaveChanges(); + foreach (var x in org.Members) + { + org.Directory.Members.Add(new() + { + Account = x.Member, + Directory = org.Directory + }); + } + context.DataBase.SaveChanges(); - /// - /// Actualiza el estado de la lista blanca de una organización - /// - /// ID de la organización - /// Nuevo estado - /// Contexto de conexión - public static async Task UpdateState(int id, bool estado, Conexión context) - { - // Ejecución - try - { - // Query - var org = await (from E in context.DataBase.Organizations - where E.ID == id - select E).FirstOrDefaultAsync(); + transaction.Commit(); - // Email no existe - if (org == null) - { - return new(Responses.NotRows); + return new(Responses.Success, data); } + catch (Exception ex) + { + transaction.Rollback(); + if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) + return new(Responses.Undefined); - org.HaveWhiteList = estado; - context.DataBase.SaveChanges(); - - return new(Responses.Success); - } - catch - { + } } - return new(); } - /// - /// Actualiza el estado de accesos de una organización + /// Obtiene una organización. /// /// ID de la organización - /// Nuevo estado /// Contexto de conexión - public static async Task UpdateAccess(int id, bool estado, Conexión context) + public static async Task> Read(int id, Conexión context) { // Ejecución @@ -277,7 +180,17 @@ public static async Task UpdateAccess(int id, bool estado, Conexi // Query var org = await (from E in context.DataBase.Organizations where E.ID == id - select E).FirstOrDefaultAsync(); + + select new OrganizationModel + { + Directory = null!, + DirectoryId = id, + Domain = E.Domain, + ID = E.ID, + IsPublic = E.IsPublic, + Members = null!, + Name = E.Name, + }).FirstOrDefaultAsync(); // Email no existe if (org == null) @@ -285,10 +198,7 @@ public static async Task UpdateAccess(int id, bool estado, Conexi return new(Responses.NotRows); } - org.LoginAccess = estado; - context.DataBase.SaveChanges(); - - return new(Responses.Success); + return new(Responses.Success, org); } catch { @@ -299,4 +209,5 @@ public static async Task UpdateAccess(int id, bool estado, Conexi + } \ No newline at end of file diff --git a/LIN.Identity/Hubs/AccountHub.cs b/LIN.Identity/Hubs/AccountHub.cs deleted file mode 100644 index b293497..0000000 --- a/LIN.Identity/Hubs/AccountHub.cs +++ /dev/null @@ -1,209 +0,0 @@ -using LIN.Access.Logger; - -namespace LIN.Identity.Hubs; - - -public class AccountHub : Hub -{ - - - /// - /// Lista de cuentas y dispositivos - /// - public readonly static Dictionary> Cuentas = new(); - - - - /// - /// Unir un dispositivo a la lista - /// - /// Modelo del dispositivo - public async Task Join(DeviceModel modelo) - { - - // Validación del token - var (isValid, _, id, _, _) = Jwt.Validate(modelo.Token); - - if (!isValid) - return; - - // Estados - modelo.ID = Context.ConnectionId; - modelo.Estado = DeviceState.Actived; - modelo.Cuenta = id; - - // Agrega el modelo - if (!Cuentas.ContainsKey(modelo.Cuenta)) - Cuentas.Add(modelo.Cuenta, new() - { - modelo - }); - - else - { - - var models = Cuentas[modelo.Cuenta]; - - var devices = models.Where(x => x.DeviceKey == modelo.DeviceKey).ToList(); - - foreach (var device in devices) - device.Estado = DeviceState.Disconnected; - - Cuentas[modelo.Cuenta].Add(modelo); - - } - - // Grupo de la cuenta - await Groups.AddToGroupAsync(Context.ConnectionId, modelo.Cuenta.ToString()); - - // Envía el nuevo dispositivo - await RegisterDevice(modelo); - - // Testea la conexión - await TestDevices(modelo.Cuenta); - - } - - - - /// - /// Evento Salir - /// - /// ID de la cuenta - public async Task Leave(int cuenta) - { - Cuentas[cuenta] = Cuentas[cuenta].Where(T => T.ID != Context.ConnectionId).ToList(); - await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"{cuenta}"); - await Clients.Group(cuenta.ToString()).SendAsync("leaveevent", Context.ConnectionId); - } - - - - /// - /// Obtener la lista de dispositivos de una cuenta - /// - /// ID de la cuenta - public async Task GetDevicesList(int cuenta) - { - var devices = Cuentas[cuenta] ?? new(); - await Clients.Caller.SendAsync("devicesList", devices.Where(model => model.Estado == DeviceState.Actived), Context.ConnectionId); - } - - - - /// - /// Envía a los clientes cuando se agrega un nuevo dispositivo - /// - /// Modelo del nuevo dispositivo - public async Task RegisterDevice(DeviceModel modelo) - { - await Clients.Group(modelo.Cuenta.ToString()).SendAsync("newdevice", modelo); - } - - - - /// - /// Testea la conexión de los dispositivos - /// - /// ID de la cuenta - public async Task TestDevices(int account) - { - // Cambia el estado de los dispositivos - Cuentas[account].ForEach(model => model.Estado = DeviceState.WaitingResponse); - await Clients.Group(account.ToString()).SendAsync("ontest"); - } - - - - - public async void ReceiveTestStatus(int account, int battery, bool bateryConected) - { - - try - { - // Obtiene la cuenta - var cuenta = Cuentas[account]; - - // Obtiene el dispositivo - var device = cuenta.Where(T => T.ID == Context.ConnectionId).ToList().FirstOrDefault(); - - if (device == null) - return; - - // Cambia los estados - device.Estado = DeviceState.Actived; - device.BateryLevel = battery; - device.BateryConected = bateryConected; - - // Envía el cambio de dispositivos - await RegisterDevice(device); - } - catch (Exception ex) - { - _ = Logger.Log(ex, 2); - } - - - - } - - - - public async Task SendDeviceCommand(string receiver, string command) - { - await Clients.Client(receiver).SendAsync("devicecommand", command); - } - - - - public async Task SendAccountCommand(int receiver, string command) - { - await Clients.GroupExcept(receiver.ToString(), new[] - { - Context.ConnectionId - }).SendAsync("accountcommand", command); - } - - - - - - - public void AddInvitation(List users) - { - try - { - - foreach (var user in users) - { - _ = Clients.Group(user.ToString()).SendAsync("invitate", "retrievedata"); - } - - } - catch (Exception ex) - { - _ = Logger.Log(ex, 2); - } - - } - - - public override Task OnDisconnectedAsync(Exception? exception) - { - - var cuenta = Cuentas.Values.Where(T => T.Where(T => T.ID == Context.ConnectionId).Any()).FirstOrDefault(); - - if (cuenta == null) - return base.OnDisconnectedAsync(exception); - - var xx = cuenta.FirstOrDefault()?.Cuenta ?? 0; - _ = Leave(xx); - return base.OnDisconnectedAsync(exception); - - } - - - - - -} \ No newline at end of file diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs index 8fb0e38..cd63991 100644 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ b/LIN.Identity/Hubs/PasskeyHub.cs @@ -202,27 +202,27 @@ public async Task ReceiveRequest(PassKeyModel modelo) var organization = await Data.Organizations.Organizations.Read(orgID); // Si tiene lista blanca - if (organization.Model.HaveWhiteList) - { - // Validación de la app - var applicationOnOrg = await Data.Organizations.Applications.AppOnOrg(attempt.Application.Key, orgID); + //if (organization.Model.HaveWhiteList) + //{ + // //// Validación de la app + // //var applicationOnOrg = await Data.Organizations.Applications.AppOnOrg(attempt.Application.Key, orgID); - // Si la app no existe o no esta activa - if (applicationOnOrg.Response != Responses.Success) - { - // Modelo de falla - PassKeyModel badPass = new() - { - Status = PassKeyStatus.BlockedByOrg, - User = modelo.User - }; + // //// Si la app no existe o no esta activa + // //if (applicationOnOrg.Response != Responses.Success) + // //{ + // // // Modelo de falla + // // PassKeyModel badPass = new() + // // { + // // Status = PassKeyStatus.BlockedByOrg, + // // User = modelo.User + // // }; - // comunica la respuesta - await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", badPass); - return; + // // // comunica la respuesta + // // await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", badPass); + // // return; - } - } + // //} + //} } @@ -254,7 +254,6 @@ public async Task ReceiveRequest(PassKeyModel modelo) ID = app.Model.ID }, Date = DateTime.Now, - Platform = Platforms.Undefined, Type = LoginTypes.Passkey, ID = 0 }; @@ -266,7 +265,10 @@ public async Task ReceiveRequest(PassKeyModel modelo) var newToken = Jwt.Generate(new() { ID = userID, - Usuario = userUnique, + Identity = new() + { + Unique = userUnique + }, OrganizationAccess = new() { Organization = new() diff --git a/LIN.Identity/LIN.Identity.csproj b/LIN.Identity/LIN.Identity.csproj index 74bdea4..7977807 100644 --- a/LIN.Identity/LIN.Identity.csproj +++ b/LIN.Identity/LIN.Identity.csproj @@ -23,7 +23,7 @@ - + diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index 0c1b6e6..a09ce45 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -6,7 +6,7 @@ { - LIN.Access.Logger.Logger.AppName = "LIN.IDENTITY"; + LIN.Access.Logger.Logger.AppName = "LIN.IDENTITY.V3"; var builder = WebApplication.CreateBuilder(args); @@ -27,12 +27,6 @@ - builder.Services.AddControllers(options => - { - options.Conventions.Add(new GroupingByNamespaceConvention()); - }); - - var sqlConnection = builder.Configuration["ConnectionStrings:somee"] ?? string.Empty; // Servicio de BD @@ -46,42 +40,7 @@ builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen((config) => - { - var titleBase = "LIN Identity"; - var description = "IDENTITY"; - var TermsOfService = new Uri("http://linapps.co/"); - var License = new OpenApiLicense() - { - Name = "MIT" - }; - - var Contact = new OpenApiContact() - { - Name = "Alexander Giraldo", - Email = "", - Url = new Uri("http://linapps.co/") - }; - - config.SwaggerDoc("v1", new OpenApiInfo - { - Version = "v1", - Title = titleBase + " v1", - Description = description, - TermsOfService = TermsOfService, - License = License, - Contact = Contact - }); - config.SwaggerDoc("v3", new OpenApiInfo - { - Version = "v3", - Title = titleBase + " v3", - Description = description, - TermsOfService = TermsOfService, - License = License, - Contact = Contact - }); - }); + builder.Services.AddSwaggerGen(); var app = builder.Build(); @@ -101,15 +60,10 @@ app.UseCors("AllowAnyOrigin"); - app.MapHub("/realTime/service"); app.MapHub("/realTime/auth/passkey"); app.UseSwagger(); - app.UseSwaggerUI(config => - { - config.SwaggerEndpoint("/swagger/v1/swagger.json", "MoviesAPI v1"); - config.SwaggerEndpoint("/swagger/v3/swagger.json", "MoviesAPI v3"); - }); + app.UseSwaggerUI(); Conexión.SetStringConnection(sqlConnection); diff --git a/LIN.Identity/Queries/Accounts.cs b/LIN.Identity/Queries/Accounts.cs index 64b81e7..e6955c0 100644 --- a/LIN.Identity/Queries/Accounts.cs +++ b/LIN.Identity/Queries/Accounts.cs @@ -78,11 +78,11 @@ public static IQueryable GetAccounts(string user, FilterModels.Acc if (filters.FindOn == FilterModels.FindOn.StableAccounts) accounts = from account in GetValidAccounts(context) - where account.Usuario == user + where account.Identity.Unique == user select account; else accounts = from account in GetAccounts(context) - where account.Usuario == user + where account.Identity.Unique == user select account; // Armar el modelo @@ -116,7 +116,7 @@ public static IQueryable Search(string pattern, FilterModels.Accou // Query general IQueryable accounts = from account in GetValidAccounts(context) - where account.Usuario.Contains(pattern) + where account.Identity.Unique.Contains(pattern) select account; // Armar el modelo @@ -130,8 +130,27 @@ where account.Usuario.Contains(pattern) + public static IQueryable GetDirectory(int id, Conexión context) + { + + // Query general + IQueryable accounts; + var directories = from directory in context.DataBase.Directories + where directory.ID == id + select directory; + + + + // Armar el modelo + accounts = BuildModel(directories); + + // Retorno + return accounts; + + } + @@ -165,15 +184,18 @@ private static IQueryable BuildModel(IQueryable quer Rol = account.Rol, Insignia = account.Insignia, Estado = account.Estado, - Usuario = account.Usuario, + Identity = new() + { + Id = account.Identity.Id, + Unique = account.Identity.Unique, + Type = account.Identity.Type, + }, Contraseña = filters.SensibleInfo ? account.Contraseña : "", Visibilidad = account.Visibilidad, Birthday = account.Visibilidad == AccountVisibility.Visible || account.OrganizationAccess != null && account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser || filters.IsAdmin ? account.Birthday : default, - Genero = account.Visibilidad == AccountVisibility.Visible || account.OrganizationAccess != null && account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser || filters.IsAdmin - ? account.Genero - : Genders.Undefined, + Creación = account.Visibilidad == AccountVisibility.Visible || account.OrganizationAccess != null && account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser || filters.IsAdmin ? account.Creación : default, @@ -194,7 +216,8 @@ private static IQueryable BuildModel(IQueryable quer : account.OrganizationAccess.Organization.Domain, Name = !account.OrganizationAccess.Organization.IsPublic && !filters.IsAdmin && filters.IncludeOrg == FilterModels.IncludeOrg.IncludeIf && filters.ContextOrg != account.OrganizationAccess.Organization.ID ? "Organización privada" : account.OrganizationAccess.Organization.Name - } : new() + } : new(), + Member = null!, } : null }; @@ -202,6 +225,41 @@ private static IQueryable BuildModel(IQueryable quer + /// + /// Construir la consulta + /// + /// Query base + /// Filtros + private static IQueryable BuildModel(IQueryable query) + { + + byte[] profile = + { + }; + try + { + profile = File.ReadAllBytes("wwwroot/user.png"); + } + catch { } + + return from d in query + select new DirectoryModel + { + Creación = d.Creación, + ID = d.ID, + Identity = new() + { + Id = d.Identity.Id, + Type = d.Identity.Type, + Unique = d.Identity.Unique + }, + IdentityId = d.Identity.Id, + Nombre = d.Nombre + }; + } + + + diff --git a/LIN.Identity/Services/Iam/Applications.cs b/LIN.Identity/Services/Iam/Applications.cs index e07564c..83e428e 100644 --- a/LIN.Identity/Services/Iam/Applications.cs +++ b/LIN.Identity/Services/Iam/Applications.cs @@ -34,23 +34,23 @@ public static async Task> ValidateAccess(int account, }; // Si es un recurso publico. - if (resource.Model.AllowAnyAccount) - return new() - { - Response = Responses.Success, - Model = IamLevels.Visualizer - }; + //if (resource.Model.AllowAnyAccount) + // return new() + // { + // Response = Responses.Success, + // Model = IamLevels.Visualizer + // }; - // Validar acceso al recurso privado. - var isAllowed = await Data.Applications.IsAllow(app, account); + //// Validar acceso al recurso privado. + //var isAllowed = await Data.Applications.IsAllow(app, account); - // No es permitido. - if (!isAllowed.Model) - return new() - { - Response = Responses.Success, - Model = IamLevels.NotAccess - }; + //// No es permitido. + //if (!isAllowed.Model) + // return new() + // { + // Response = Responses.Success, + // Model = IamLevels.NotAccess + // }; // Acceso visualizador. return new() diff --git a/LIN.Identity/Services/Jwt.cs b/LIN.Identity/Services/Jwt.cs index 0ba3144..0ea7704 100644 --- a/LIN.Identity/Services/Jwt.cs +++ b/LIN.Identity/Services/Jwt.cs @@ -44,7 +44,7 @@ internal static string Generate(AccountModel user, int appID) var claims = new[] { new Claim(ClaimTypes.PrimarySid, user.ID.ToString()), - new Claim(ClaimTypes.NameIdentifier, user.Usuario), + new Claim(ClaimTypes.NameIdentifier, user.Identity.Unique), new Claim(ClaimTypes.Role, ((int)user.Rol).ToString()), new Claim(ClaimTypes.UserData, (user.OrganizationAccess?.Organization.ID).ToString() ?? ""), new Claim(ClaimTypes.Authentication, appID.ToString()) diff --git a/LIN.Identity/Services/Login/LoginOnOrg.cs b/LIN.Identity/Services/Login/LoginOnOrg.cs index f0f7d33..02e71cd 100644 --- a/LIN.Identity/Services/Login/LoginOnOrg.cs +++ b/LIN.Identity/Services/Login/LoginOnOrg.cs @@ -68,26 +68,26 @@ private async Task LoadOrganization() private async Task ValidatePolicies() { - // Si el inicio de sesión fue desactivado por la organización - if (!OrganizationAccess!.Organization.LoginAccess && !OrganizationAccess.Rol.IsAdmin()) - return new() - { - Message = "Tu organización a deshabilitado el inicio de sesión temporalmente.", - Response = Responses.LoginBlockedByOrg - }; - - - // Si la organización tiene lista blanca - if (OrganizationAccess.Organization.HaveWhiteList) - { - var whiteList = await ValidateWhiteList(); - if (!whiteList) - return new() - { - Message = "Tu organización no permite iniciar sesión en esta aplicación.", - Response = Responses.UnauthorizedByOrg - }; - } + //// Si el inicio de sesión fue desactivado por la organización + //if (!OrganizationAccess!.Organization.LoginAccess && !OrganizationAccess.Rol.IsAdmin()) + // return new() + // { + // Message = "Tu organización a deshabilitado el inicio de sesión temporalmente.", + // Response = Responses.LoginBlockedByOrg + // }; + + + //// Si la organización tiene lista blanca + //if (OrganizationAccess.Organization.HaveWhiteList) + //{ + // var whiteList = await ValidateWhiteList(); + // if (!whiteList) + // return new() + // { + // Message = "Tu organización no permite iniciar sesión en esta aplicación.", + // Response = Responses.UnauthorizedByOrg + // }; + //} return new(Responses.Success); @@ -95,24 +95,6 @@ private async Task ValidatePolicies() - /// - /// Valida la app en la lista blanca - /// - private async Task ValidateWhiteList() - { - - // Busca la app en la organización - var appOnOrg = await Data.Organizations.Applications.AppOnOrg(ApplicationKey, OrganizationAccess!.Organization.ID); - - return appOnOrg.Response == Responses.Success; - - } - - - - /// - /// Iniciar sesión - /// public override async Task Login() { diff --git a/LIN.Identity/Services/Login/LoginService.cs b/LIN.Identity/Services/Login/LoginService.cs index e49ecc9..26834f2 100644 --- a/LIN.Identity/Services/Login/LoginService.cs +++ b/LIN.Identity/Services/Login/LoginService.cs @@ -73,7 +73,9 @@ public ResponseBase Validate() Message = "Esta cuenta fue eliminada o desactivada." }; + // Valida la contraseña + if (Account.Contraseña != EncryptClass.Encrypt(Password)) return new() { @@ -95,32 +97,32 @@ public ResponseBase Validate() public async Task ValidateApp() { // Obtiene la App. - var app = await Data.Applications.Read(ApplicationKey); - - // Verifica si la app existe. - if (app.Response != Responses.Success) - return new ReadOneResponse - { - Message = "La aplicación no esta autorizada para iniciar sesión en LIN Identity", - Response = Responses.Unauthorized - }; - - // Si es una app privada. - if (!app.Model.AllowAnyAccount) - { - var allow = await Data.Applications.IsAllow(app.Model.ID, Account.ID, Conexión.GetOneConnection().context); - - if (allow.Response != Responses.Success) - return new ReadOneResponse - { - Message = $"No tienes permiso para acceder a la aplicación '{app.Model.Name}'", - Response = Responses.Unauthorized - }; - } + //var app = await Data.Applications.Read(ApplicationKey); + + //// Verifica si la app existe. + //if (app.Response != Responses.Success) + // return new ReadOneResponse + // { + // Message = "La aplicación no esta autorizada para iniciar sesión en LIN Identity", + // Response = Responses.Unauthorized + // }; + + //// Si es una app privada. + //if (!app.Model.AllowAnyAccount) + //{ + // var allow = await Data.Applications.IsAllow(app.Model.ID, Account.ID, Conexión.GetOneConnection().context); + + // if (allow.Response != Responses.Success) + // return new ReadOneResponse + // { + // Message = $"No tienes permiso para acceder a la aplicación '{app.Model.Name}'", + // Response = Responses.Unauthorized + // }; + //} // Establece la aplicación - Application = app.Model; + // Application = app.Model; // Correcto return new(Responses.Success); @@ -136,19 +138,19 @@ public async void GenerateLogin() { - var app = await Data.Applications.Read(ApplicationKey); + //var app = await Data.Applications.Read(ApplicationKey); - // Crea registro del login - _ = Data.Logins.Create(new() - { - Date = DateTime.Now, - AccountID = Account.ID, - Type = LoginType, - Application = new() - { - ID = app.Model.ID - } - }); + //// Crea registro del login + //_ = Data.Logins.Create(new() + //{ + // Date = DateTime.Now, + // AccountID = Account.ID, + // Type = LoginType, + // Application = new() + // { + // ID = app.Model.ID + // } + //}); } diff --git a/LIN.Identity/Usings.cs b/LIN.Identity/Usings.cs index e4c8642..a124d41 100644 --- a/LIN.Identity/Usings.cs +++ b/LIN.Identity/Usings.cs @@ -3,8 +3,8 @@ global using LIN.Identity.Hubs; global using LIN.Identity.Services; global using LIN.Modules; -global using LIN.Types.Auth.Enumerations; -global using LIN.Types.Auth.Models; +global using LIN.Types.Identity.Enumerations; +global using LIN.Types.Identity.Models; global using LIN.Types.Enumerations; global using LIN.Types.Responses; global using Microsoft.AspNetCore.Mvc; diff --git a/LIN.Identity/Validations/Account.cs b/LIN.Identity/Validations/Account.cs index a88f8f5..adea5ae 100644 --- a/LIN.Identity/Validations/Account.cs +++ b/LIN.Identity/Validations/Account.cs @@ -16,9 +16,13 @@ public static AccountModel Process(AccountModel modelo) { ID = 0, Nombre = modelo.Nombre, - Genero = modelo.Genero, OrganizationAccess = modelo.OrganizationAccess, - Usuario = modelo.Usuario, + Identity = new() + { + Id = 0, + Type = IdentityTypes.Account, + Unique = modelo.Identity.Unique + }, Visibilidad = modelo.Visibilidad, Contraseña = modelo.Contraseña = EncryptClass.Encrypt(modelo.Contraseña), Creación = modelo.Creación = DateTime.Now, From 4c1b882fdb5728741c140d008dbacedb6c235b8e Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 17:44:01 -0500 Subject: [PATCH 017/178] Integrantes de organizacion en directorio humano --- .../Areas/Organizations/MemberController.cs | 2 +- LIN.Identity/Data/Context.cs | 7 ++++ LIN.Identity/Data/Organizations/Members.cs | 15 ++++++++ LIN.Identity/Queries/Accounts.cs | 38 ++++++++++--------- LIN.Identity/Services/Login/LoginNormal.cs | 21 ++++------ LIN.Identity/Services/Swagger.cs | 14 ------- 6 files changed, 51 insertions(+), 46 deletions(-) delete mode 100644 LIN.Identity/Services/Swagger.cs diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index 5862aad..ccf6a49 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -100,7 +100,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr // ID de la organización - var org = userContext.Model.OrganizationAccess.Organization.ID; + var org = userContext.Model.OrganizationAccess.OrganizationId; // Conexión diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index aa7aaca..adfe392 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -29,6 +29,12 @@ public class Context : DbContext public DbSet Directories { get; set; } + /// + /// Directorios. + /// + public DbSet DirectoryMembers { get; set; } + + /// /// Accesos a organizaciones. /// @@ -170,6 +176,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("APPLICATIONS"); modelBuilder.Entity().ToTable("EMAILS"); modelBuilder.Entity().ToTable("DIRECTORIES"); + modelBuilder.Entity().ToTable("DIRECTORY_MEMBERS"); } diff --git a/LIN.Identity/Data/Organizations/Members.cs b/LIN.Identity/Data/Organizations/Members.cs index af2acaf..d2fd847 100644 --- a/LIN.Identity/Data/Organizations/Members.cs +++ b/LIN.Identity/Data/Organizations/Members.cs @@ -86,6 +86,21 @@ public static async Task> Create(AccountModel data var res = await context.DataBase.Accounts.AddAsync(data); context.DataBase.SaveChanges(); + var memberOnDirectory = new DirectoryMember + { + Account = data, + Directory = new() + { + ID = org.DirectoryId + } + }; + context.DataBase.Attach(memberOnDirectory.Directory); + + context.DataBase.DirectoryMembers.Add(memberOnDirectory); + + context.DataBase.SaveChanges(); + + transaction.Commit(); return new(Responses.Success, data); diff --git a/LIN.Identity/Queries/Accounts.cs b/LIN.Identity/Queries/Accounts.cs index e6955c0..3ec9e8e 100644 --- a/LIN.Identity/Queries/Accounts.cs +++ b/LIN.Identity/Queries/Accounts.cs @@ -141,7 +141,7 @@ public static IQueryable GetDirectory(int id, Conexión context) where directory.ID == id select directory; - + // Armar el modelo accounts = BuildModel(directories); @@ -182,20 +182,22 @@ private static IQueryable BuildModel(IQueryable quer ? account.Nombre : "Usuario privado", Rol = account.Rol, + DirectoryMembers = new(), + IdentityId = account.Identity.Id, Insignia = account.Insignia, Estado = account.Estado, - Identity = new() - { - Id = account.Identity.Id, - Unique = account.Identity.Unique, - Type = account.Identity.Type, - }, + Identity = new() + { + Id = account.Identity.Id, + Unique = account.Identity.Unique, + Type = account.Identity.Type, + }, Contraseña = filters.SensibleInfo ? account.Contraseña : "", Visibilidad = account.Visibilidad, Birthday = account.Visibilidad == AccountVisibility.Visible || account.OrganizationAccess != null && account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser || filters.IsAdmin ? account.Birthday : default, - + Creación = account.Visibilidad == AccountVisibility.Visible || account.OrganizationAccess != null && account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser || filters.IsAdmin ? account.Creación : default, @@ -245,16 +247,16 @@ private static IQueryable BuildModel(IQueryable return from d in query select new DirectoryModel { - Creación = d.Creación, - ID = d.ID, - Identity = new() - { - Id = d.Identity.Id, - Type = d.Identity.Type, - Unique = d.Identity.Unique - }, - IdentityId = d.Identity.Id, - Nombre = d.Nombre + Creación = d.Creación, + ID = d.ID, + Identity = new() + { + Id = d.Identity.Id, + Type = d.Identity.Type, + Unique = d.Identity.Unique + }, + IdentityId = d.Identity.Id, + Nombre = d.Nombre }; } diff --git a/LIN.Identity/Services/Login/LoginNormal.cs b/LIN.Identity/Services/Login/LoginNormal.cs index aefd96c..29c03fc 100644 --- a/LIN.Identity/Services/Login/LoginNormal.cs +++ b/LIN.Identity/Services/Login/LoginNormal.cs @@ -1,22 +1,17 @@ namespace LIN.Identity.Services.Login; -public class LoginNormal : LoginService +/// +/// Nuevo login +/// +/// Datos de la cuenta +/// Llave +/// Contraseña +/// Tipo de inicio +public class LoginNormal(AccountModel? account, string? application, string password, LoginTypes loginType = LoginTypes.Credentials) : LoginService(account, application, password, loginType) { - /// - /// Nuevo login - /// - /// Datos de la cuenta - /// Llave - /// Contraseña - /// Tipo de inicio - public LoginNormal(AccountModel? account, string? application, string password, LoginTypes loginType = LoginTypes.Credentials) : base(account, application, password, loginType) - { - } - - /// /// Iniciar sesión diff --git a/LIN.Identity/Services/Swagger.cs b/LIN.Identity/Services/Swagger.cs deleted file mode 100644 index a2a6e82..0000000 --- a/LIN.Identity/Services/Swagger.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Mvc.ApplicationModels; - -namespace LIN.Identity.Services; - -public class GroupingByNamespaceConvention : IControllerModelConvention -{ - public void Apply(ControllerModel controller) - { - var controllerNamespace = controller.ControllerType.Namespace; - var apiVersion = controllerNamespace.Split(".").Last().ToLower(); - if (!apiVersion.StartsWith("v")) { apiVersion = "v1"; } - controller.ApiExplorer.GroupName = apiVersion; - } -} From 01b35ca5cbaa725e8f3fecbb8ccdaa0cb1cad176 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 17:45:37 -0500 Subject: [PATCH 018/178] Mejoras en Validation.Account --- LIN.Identity/Validations/Account.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LIN.Identity/Validations/Account.cs b/LIN.Identity/Validations/Account.cs index adea5ae..38e4c1d 100644 --- a/LIN.Identity/Validations/Account.cs +++ b/LIN.Identity/Validations/Account.cs @@ -23,6 +23,8 @@ public static AccountModel Process(AccountModel modelo) Type = IdentityTypes.Account, Unique = modelo.Identity.Unique }, + DirectoryMembers = [], + IdentityId = 0, Visibilidad = modelo.Visibilidad, Contraseña = modelo.Contraseña = EncryptClass.Encrypt(modelo.Contraseña), Creación = modelo.Creación = DateTime.Now, From 6cb01e89d0b8bbd39ddaf6b589cfe95d5c1d658c Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 17:56:14 -0500 Subject: [PATCH 019/178] Mejoras en Data.Mails --- LIN.Identity/Data/Mails.cs | 42 ++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/LIN.Identity/Data/Mails.cs b/LIN.Identity/Data/Mails.cs index 0ef3902..1308070 100644 --- a/LIN.Identity/Data/Mails.cs +++ b/LIN.Identity/Data/Mails.cs @@ -101,7 +101,7 @@ public static async Task UpdateState(int id, EmailStatus state) /// - /// Crea un nuevo email + /// Crea un nuevo mail. /// /// Modelo /// Contexto de conexión @@ -152,9 +152,8 @@ public static async Task> ReadAll(int id, Conexión return new(Responses.Success, emails); } - catch (Exception ex) + catch (Exception) { - // Notificar a LIN Error Logger. } return new(); @@ -181,15 +180,12 @@ public static async Task> Read(int id, Conexión con // Email no existe if (email == null) - { return new(Responses.NotRows); - } return new(Responses.Success, email); } - catch (Exception ex) + catch (Exception) { - _ = Logger.Log(ex, 1); } return new(); @@ -240,33 +236,36 @@ public static async Task SetDefaultEmail(int id, int emailID, Cone { try { - // Query - var emails = await (from E in context.DataBase.Emails - where E.UserID == id && E.Status == EmailStatus.Verified - select E).ToListAsync(); + // Consulta. + var mails = await (from E in context.DataBase.Emails + where E.UserID == id && E.Status == EmailStatus.Verified + where E.ID == id || E.IsDefault + select E).ToListAsync(); - var actualDefault = emails.Where(T => T.IsDefault).FirstOrDefault(); - if (actualDefault != null) - { - actualDefault.IsDefault = false; - } + // Obtiene los emails default y los establece normal. + foreach (var item in mails.Where(t => t.IsDefault)) + item.IsDefault = false; - var setted = emails.Where(T => T.ID == emailID && T.Status == EmailStatus.Verified).FirstOrDefault(); + // Elemento actual. + var element = mails.Where(T => T.ID == emailID && T.Status == EmailStatus.Verified).FirstOrDefault(); - if (setted == null) + // No existe. + if (element == null) { transaction.Rollback(); return new(Responses.NotRows); } - setted.IsDefault = true; + // Establece como default. + element.IsDefault = true; + context.DataBase.SaveChanges(); transaction.Commit(); return new(Responses.Success); } - catch + catch (Exception) { transaction.Rollback(); } @@ -306,9 +305,8 @@ public static async Task UpdateState(int id, EmailStatus state, Co return new(Responses.Success); } - catch (Exception ex) + catch (Exception) { - _ = Logger.Log(ex, 1); } return new(); From 41b50a0f38cc6429caa6efdd92954307d84d7395 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 17:57:36 -0500 Subject: [PATCH 020/178] Mejoras en Data.Logins --- LIN.Identity/Data/Logins.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/LIN.Identity/Data/Logins.cs b/LIN.Identity/Data/Logins.cs index c5b4248..3866f68 100644 --- a/LIN.Identity/Data/Logins.cs +++ b/LIN.Identity/Data/Logins.cs @@ -60,14 +60,14 @@ public static async Task Create(LoginLogModel data, Conexión co try { - // + // Ya existe la app. context.DataBase.Attach(data.Application); var res = context.DataBase.LoginLogs.Add(data); await context.DataBase.SaveChangesAsync(); return new(Responses.Success, data.ID); } - catch + catch (Exception) { } @@ -88,6 +88,7 @@ public static async Task> ReadAll(int id, Conexi try { + // Consulta. var logins = from L in context.DataBase.LoginLogs where L.AccountID == id orderby L.Date descending @@ -98,17 +99,18 @@ orderby L.Date descending Date = L.Date, Application = new() { + ID = L.Application.ID, Name = L.Application.Name, Badge = L.Application.Badge } }; - + // Resultado var result = await logins.Take(50).ToListAsync(); return new(Responses.Success, result); } - catch + catch (Exception) { } return new(); From b1570abca9a6f501f73d91ea30773d29222442e5 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 18:02:43 -0500 Subject: [PATCH 021/178] Mejoras en Applications Data --- LIN.Identity/Data/Applications.cs | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/LIN.Identity/Data/Applications.cs b/LIN.Identity/Data/Applications.cs index 7c2dfda..4e93904 100644 --- a/LIN.Identity/Data/Applications.cs +++ b/LIN.Identity/Data/Applications.cs @@ -86,15 +86,17 @@ public static async Task> Read(string key) public static async Task Create(ApplicationModel data, Conexión context) { + // ID. data.ID = 0; // Ejecución try { - + // Guardar la información. + await context.DataBase.Applications.AddAsync(data); - var res = await context.DataBase.Applications.AddAsync(data); + // Llevar info a la BD. context.DataBase.SaveChanges(); return new(Responses.Success, data.ID); @@ -159,13 +161,12 @@ public static async Task> Read(int id, Conexi // Email no existe if (email == null) - { return new(Responses.NotRows); - } - + + // Correcto. return new(Responses.Success, email); } - catch + catch (Exception) { } @@ -193,13 +194,12 @@ public static async Task> Read(string key, Con // Email no existe if (email == null) - { return new(Responses.NotRows); - } + return new(Responses.Success, email); } - catch + catch (Exception) { } @@ -231,7 +231,7 @@ public static async Task> ReadByAppUid(string return new(Responses.Success, app); } - catch + catch (Exception) { } @@ -240,13 +240,4 @@ public static async Task> ReadByAppUid(string - - - - - - - - - } \ No newline at end of file From 3dde94ee657fffaa865248039ce83bf6b771b90d Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 18:06:20 -0500 Subject: [PATCH 022/178] Mejoras en Directory Data --- LIN.Identity/Data/Accounts/AccountsPost.cs | 1 + LIN.Identity/Data/Directories/Directories.cs | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/LIN.Identity/Data/Accounts/AccountsPost.cs b/LIN.Identity/Data/Accounts/AccountsPost.cs index 6374e4d..a52783f 100644 --- a/LIN.Identity/Data/Accounts/AccountsPost.cs +++ b/LIN.Identity/Data/Accounts/AccountsPost.cs @@ -28,6 +28,7 @@ public static async Task> Create(AccountModel data #endregion + /// /// Crea una nueva cuenta /// diff --git a/LIN.Identity/Data/Directories/Directories.cs b/LIN.Identity/Data/Directories/Directories.cs index 711aafb..d3088c9 100644 --- a/LIN.Identity/Data/Directories/Directories.cs +++ b/LIN.Identity/Data/Directories/Directories.cs @@ -5,6 +5,14 @@ public class Directories { + #region Abstracciones + + + + /// + /// Obtener un directorio. + /// + /// Id del directorio. public static async Task> Read(int id) { @@ -19,7 +27,15 @@ public static async Task> Read(int id) + #endregion + + + /// + /// Obtener un directorio. + /// + /// Id del directorio. + /// Contexto de conexión. public static async Task> Read(int id, Conexión context) { @@ -27,6 +43,7 @@ public static async Task> Read(int id, Conexión try { + // Consulta. var query = Queries.Accounts.GetDirectory(id, context); // Obtiene el usuario @@ -38,7 +55,7 @@ public static async Task> Read(int id, Conexión return new(Responses.Success, result); } - catch + catch (Exception) { } From 5fbb0453aece50eca2d77dfebb0477c959a42351 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 18:11:20 -0500 Subject: [PATCH 023/178] Mejoras de comentarios --- LIN.Identity/Data/Organizations/Members.cs | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/LIN.Identity/Data/Organizations/Members.cs b/LIN.Identity/Data/Organizations/Members.cs index d2fd847..5f203b4 100644 --- a/LIN.Identity/Data/Organizations/Members.cs +++ b/LIN.Identity/Data/Organizations/Members.cs @@ -63,44 +63,49 @@ public static async Task> Create(AccountModel data try { - // Obtiene la organización - var org = await (from U in context.DataBase.Organizations - where U.ID == orgID - select U).FirstOrDefaultAsync(); + // Obtiene la organización. + OrganizationModel? organization = await (from org in context.DataBase.Organizations + where org.ID == orgID + select org).FirstOrDefaultAsync(); - // No existe la organización - if (org == null) + // No existe la organización. + if (organization == null) { transaction.Rollback(); return new(Responses.NotRows); } - // Modelo de acceso + // Modelo de acceso. data.OrganizationAccess = new() { Member = data, Rol = rol, - Organization = org + Organization = organization }; - var res = await context.DataBase.Accounts.AddAsync(data); + + // Guardar la cuenta. + await context.DataBase.Accounts.AddAsync(data); context.DataBase.SaveChanges(); + // Miembro del directorio. var memberOnDirectory = new DirectoryMember { Account = data, Directory = new() { - ID = org.DirectoryId + ID = organization.DirectoryId } }; - context.DataBase.Attach(memberOnDirectory.Directory); + // Guardar el miembro en el directorio. + context.DataBase.Attach(memberOnDirectory.Directory); context.DataBase.DirectoryMembers.Add(memberOnDirectory); + // Guardar cambios. context.DataBase.SaveChanges(); - + // Enviar la transacción. transaction.Commit(); return new(Responses.Success, data); @@ -145,7 +150,6 @@ public static async Task> ReadAll(int id, Conexió Type = O.Member.Identity.Type, Unique = O.Member.Identity.Unique }, - OrganizationAccess = new() { ID = O.Member.OrganizationAccess == null ? 0 : O.Member.OrganizationAccess.ID, From 27af83dad830229a28495e9e6ec34e9663de1c31 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 18:12:13 -0500 Subject: [PATCH 024/178] Mejoras de comentarios --- LIN.Identity/Data/Organizations/Organizations.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/LIN.Identity/Data/Organizations/Organizations.cs b/LIN.Identity/Data/Organizations/Organizations.cs index 4e8a6a4..9f7c073 100644 --- a/LIN.Identity/Data/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Organizations/Organizations.cs @@ -83,11 +83,11 @@ public static async Task> Create(Organization }; accounts.Add(accountModel); -context.DataBase.Accounts.Add(accountModel); + context.DataBase.Accounts.Add(accountModel); } - + context.DataBase.SaveChanges(); @@ -116,7 +116,7 @@ public static async Task> Create(Organization org.Members = []; - foreach(var x in accounts) + foreach (var x in accounts) { org.Members.Add(new() { @@ -135,7 +135,7 @@ public static async Task> Create(Organization context.DataBase.SaveChanges(); - foreach (var x in org.Members) + foreach (var x in org.Members) { org.Directory.Members.Add(new() { From 8948e1920f6b87f2ff233d82d897d60b35f3773d Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 18:21:02 -0500 Subject: [PATCH 025/178] Mejoras en la validacion de parametros en Accounts --- .../Areas/Accounts/AccountController.cs | 47 ++++++++++++++----- LIN.Identity/Services/Jwt.cs | 5 ++ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index 9480a19..a7dd88b 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -8,7 +8,7 @@ public class AccountController : ControllerBase /// - /// Crear nueva cuenta (Cuenta de LIN). + /// Crear nueva cuenta. /// /// Modelo de la cuenta [HttpPost("create")] @@ -17,7 +17,10 @@ public async Task Create([FromBody] AccountModel? modelo) // Comprobaciones if (modelo == null || modelo.Identity == null || modelo.Contraseña.Length < 4 || modelo.Nombre.Length <= 0 || modelo.Identity.Unique.Length <= 0) - return new(Responses.InvalidParam); + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; // Organización del modelo modelo = Account.Process(modelo); @@ -27,7 +30,10 @@ public async Task Create([FromBody] AccountModel? modelo) // Evaluación if (response.Response != Responses.Success) - return new(response.Response); + return new(response.Response) + { + Message = "Hubo un error al crear la cuenta." + }; // Obtiene el usuario var token = Jwt.Generate(response.Model, 0); @@ -38,7 +44,7 @@ public async Task Create([FromBody] AccountModel? modelo) LastID = response.Model.Identity.Id, Response = Responses.Success, Token = token, - Message = "Success" + Message = "Cuenta creada satisfactoriamente." }; } @@ -55,8 +61,11 @@ public async Task> Read([FromQuery] int id, [F { // Id es invalido. - if (id <= 0) - return new(Responses.InvalidParam); + if (id <= 0 || string.IsNullOrWhiteSpace(token)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; // Información del token. var (isValid, _, user, orgId, _) = Jwt.Validate(token); @@ -104,8 +113,11 @@ public async Task> Read([FromQuery] string use { // Usuario es invalido. - if (string.IsNullOrWhiteSpace(user)) - return new(Responses.InvalidParam); + if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(token)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; // Información del token. var (isValid, _, userId, orgId, _) = Jwt.Validate(token); @@ -153,8 +165,11 @@ public async Task> Search([FromQuery] string p { // Comprobación - if (pattern.Trim().Length <= 0) - return new(Responses.InvalidParam); + if (pattern.Trim().Length <= 0 || string.IsNullOrWhiteSpace(pattern) || string.IsNullOrWhiteSpace(token)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; // Info del token var (isValid, _, userId, orgId, _) = Jwt.Validate(token); @@ -191,6 +206,14 @@ public async Task> Search([FromQuery] string p public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) { + // Comprobación + if (string.IsNullOrWhiteSpace(token)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; + + // Información del token. var (isValid, _, userId, orgId, _) = Jwt.Validate(token); @@ -247,7 +270,7 @@ public async Task Delete([FromHeader] string token) - + /// /// (ADMIN) encuentra diez (10) usuarios que coincidan con el patron @@ -258,7 +281,7 @@ public async Task Delete([FromHeader] string token) public async Task> FindAll([FromQuery] string pattern, [FromHeader] string token) { - var (isValid, _, id, _, _) = Jwt.Validate(token); + var (isValid, _, _, _, _) = Jwt.Validate(token); if (!isValid) diff --git a/LIN.Identity/Services/Jwt.cs b/LIN.Identity/Services/Jwt.cs index 0ea7704..cb897b8 100644 --- a/LIN.Identity/Services/Jwt.cs +++ b/LIN.Identity/Services/Jwt.cs @@ -71,6 +71,11 @@ internal static (bool isValid, string user, int userID, int orgID, int appID) Va try { + + // Comprobación + if (string.IsNullOrWhiteSpace(token)) + return (false, string.Empty, 0, 0, 0); + // Configurar la clave secreta var key = Encoding.ASCII.GetBytes(JwtKey); From d47172eb5ae9ae40e90d1ebc48a7ee7526d7c5eb Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 18:23:00 -0500 Subject: [PATCH 026/178] Mejoras en la validacion de parametros en Authentication --- .../Areas/Authentication/AuthenticationController.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index 9c6ff1b..0c44b4a 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -19,8 +19,12 @@ public async Task> Login([FromQuery] string us { // Validación de parámetros. - if (!user.Any() || !password.Any() || !application.Any()) - return new(Responses.InvalidParam); + if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(application)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son invalido." + }; + // Obtiene el usuario. var response = await Data.Accounts.Read(user, new() From bbdc5a3df4aa67fcda41cf656541c195894b06a3 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 7 Dec 2023 20:04:30 -0500 Subject: [PATCH 027/178] Mejoras --- LIN.Identity/Areas/Accounts/AccountController.cs | 1 - LIN.Identity/Areas/Directories/DirectoryController.cs | 6 +++--- LIN.Identity/Areas/Organizations/MemberController.cs | 3 --- LIN.Identity/Areas/Security/Security.cs | 7 ------- LIN.Identity/Usings.cs | 3 ++- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index a7dd88b..ed9ee7c 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -1,4 +1,3 @@ -using LIN.Identity.Validations; namespace LIN.Identity.Areas.Accounts; diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index 61e43c9..3e92ca6 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -6,10 +6,10 @@ public class DirectoryController : ControllerBase { /// - /// Obtiene la información de usuario. + /// Obtener un directorio. /// - /// ID del usuario - /// Token de acceso + /// ID del directorio. + /// Token de acceso. [HttpGet("read/id")] public async Task> Read([FromQuery] int id, [FromHeader] string token) { diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index ccf6a49..0df5897 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -1,5 +1,3 @@ -using LIN.Identity.Validations; - namespace LIN.Identity.Areas.Organizations; @@ -33,7 +31,6 @@ public async Task Create([FromBody] AccountModel modelo, [Fr // Organización del modelo modelo = Account.Process(modelo); - // Establece la contraseña default var password = $"ChangePwd@{modelo.Creación:dd.MM.yyyy}"; diff --git a/LIN.Identity/Areas/Security/Security.cs b/LIN.Identity/Areas/Security/Security.cs index bfa4e4c..2fc971e 100644 --- a/LIN.Identity/Areas/Security/Security.cs +++ b/LIN.Identity/Areas/Security/Security.cs @@ -4,11 +4,4 @@ [Route("security")] public class Security : ControllerBase { - - - - - - - } \ No newline at end of file diff --git a/LIN.Identity/Usings.cs b/LIN.Identity/Usings.cs index a124d41..ad4bafc 100644 --- a/LIN.Identity/Usings.cs +++ b/LIN.Identity/Usings.cs @@ -12,4 +12,5 @@ global using Microsoft.EntityFrameworkCore; global using Microsoft.IdentityModel.Tokens; global using System.Text; -global using LIN.Access.Logger; \ No newline at end of file +global using LIN.Access.Logger; +global using LIN.Identity.Validations; \ No newline at end of file From c26b6c06c39754f1400459c7a10bd1f6e00ae1d5 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 10:52:53 -0500 Subject: [PATCH 028/178] Data Directory Mejoradp --- .../Areas/Accounts/AccountController.cs | 21 ++- LIN.Identity/Data/Accounts/AccountsGet.cs | 3 +- LIN.Identity/Data/Context.cs | 51 +++---- LIN.Identity/Data/Directories/Directories.cs | 56 +++++++ LIN.Identity/Data/Organizations/Members.cs | 1 - .../Data/Organizations/Organizations.cs | 1 - LIN.Identity/FilterModels/Account.cs | 2 + LIN.Identity/Hubs/PasskeyHub.cs | 12 +- LIN.Identity/Program.cs | 15 +- LIN.Identity/Queries/Accounts.cs | 143 ++++++++++-------- LIN.Identity/Services/Login/LoginOnOrg.cs | 1 - 11 files changed, 186 insertions(+), 120 deletions(-) diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index ed9ee7c..1accb27 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -7,9 +7,9 @@ public class AccountController : ControllerBase /// - /// Crear nueva cuenta. + /// Crear una cuenta. /// - /// Modelo de la cuenta + /// Modelo de la cuenta. [HttpPost("create")] public async Task Create([FromBody] AccountModel? modelo) { @@ -50,11 +50,11 @@ public async Task Create([FromBody] AccountModel? modelo) - /// - /// Obtiene la información de usuario. - /// - /// ID del usuario - /// Token de acceso + /// + /// Obtener una cuenta. + /// + /// Id de la cuenta. + /// Token de acceso. [HttpGet("read/id")] public async Task> Read([FromQuery] int id, [FromHeader] string token) { @@ -103,10 +103,10 @@ public async Task> Read([FromQuery] int id, [F /// - /// Obtiene la información de usuario. + /// Obtener una cuenta. /// - /// Usuario único - /// Token de acceso + /// Identidad. + /// Token de acceso. [HttpGet("read/user")] public async Task> Read([FromQuery] string user, [FromHeader] string token) { @@ -270,7 +270,6 @@ public async Task Delete([FromHeader] string token) - /// /// (ADMIN) encuentra diez (10) usuarios que coincidan con el patron /// diff --git a/LIN.Identity/Data/Accounts/AccountsGet.cs b/LIN.Identity/Data/Accounts/AccountsGet.cs index 0397b19..7b47066 100644 --- a/LIN.Identity/Data/Accounts/AccountsGet.cs +++ b/LIN.Identity/Data/Accounts/AccountsGet.cs @@ -259,8 +259,6 @@ public static async Task> FindAll(List ids, F - - /// /// Obtiene la información básica de un usuario /// @@ -353,4 +351,5 @@ public static async Task> ReadBasic(string user, C return new(); } + } \ No newline at end of file diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index adfe392..f889b96 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -30,7 +30,7 @@ public class Context : DbContext /// - /// Directorios. + /// Integrantes de Directorios. /// public DbSet DirectoryMembers { get; set; } @@ -81,7 +81,6 @@ public Context(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { - // Indices y identidad. modelBuilder.Entity() .HasIndex(e => e.Unique) @@ -105,15 +104,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // Indices y identidad. modelBuilder.Entity() .HasIndex(e => e.Email) - .IsUnique(); - - - - - - - - + .IsUnique(); modelBuilder.Entity() @@ -122,22 +113,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(p => p.DirectoryId); - - - - modelBuilder.Entity() - .HasOne(oa => oa.Organization) - .WithMany(o => o.Members) - .HasForeignKey(oa => oa.OrganizationId) - .OnDelete(DeleteBehavior.Restrict); + .HasOne(oa => oa.Organization) + .WithMany(o => o.Members) + .HasForeignKey(oa => oa.OrganizationId) + .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity() - .HasOne(dm => dm.Directory) - .WithMany(d => d.Members) - .HasForeignKey(dm => dm.DirectoryId) - .OnDelete(DeleteBehavior.Restrict); // You can a + .HasOne(dm => dm.Directory) + .WithMany(d => d.Members) + .HasForeignKey(dm => dm.DirectoryId) + .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity() @@ -147,20 +134,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Restrict); ; - - // Configure OrganizationAccessModel modelBuilder.Entity() .HasOne(o => o.Member) - .WithOne(a => a.OrganizationAccess) // Assuming OrganizationAccess is the navigation property in AccountModel + .WithOne(a => a.OrganizationAccess) .HasForeignKey(o => o.MemberId) .IsRequired(); - - - - - modelBuilder.Entity() .HasKey(t => new { @@ -168,6 +148,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) t.DirectoryId }); + modelBuilder.Entity() + .HasKey(t => new + { + t.MemberId, + t.OrganizationId + }); + // Nombre de la identidades. modelBuilder.Entity().ToTable("IDENTITIES"); @@ -177,6 +164,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("EMAILS"); modelBuilder.Entity().ToTable("DIRECTORIES"); modelBuilder.Entity().ToTable("DIRECTORY_MEMBERS"); + modelBuilder.Entity().ToTable("ORGANIZATIONS_MEMBERS"); + modelBuilder.Entity().ToTable("LOGS"); } diff --git a/LIN.Identity/Data/Directories/Directories.cs b/LIN.Identity/Data/Directories/Directories.cs index d3088c9..262f483 100644 --- a/LIN.Identity/Data/Directories/Directories.cs +++ b/LIN.Identity/Data/Directories/Directories.cs @@ -8,6 +8,23 @@ public class Directories #region Abstracciones + /// + /// Nuevo directorio vacío. + /// + /// Modelo. + public static async Task Create(DirectoryModel directory) + { + + // Obtiene la conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + var res = await Create(directory, context); + context.CloseActions(connectionKey); + return res; + + } + + /// /// Obtener un directorio. @@ -31,6 +48,45 @@ public static async Task> Read(int id) + /// + /// Crear un directorio vacío. + /// + /// Modelo del directorio. + /// Contexto de conexión. + public static async Task Create(DirectoryModel model, Conexión context) + { + + // Modelo. + model.ID = 0; + model.Members = []; + + // Ejecución + try + { + + // Agregar el elemento. + await context.DataBase.Directories.AddAsync(model); + + // Guardar los cambios. + context.DataBase.SaveChanges(); + + // Respuesta. + return new() + { + Response = Responses.Success, + LastID = model.ID + }; + + } + catch (Exception) + { + } + + return new(); + } + + + /// /// Obtener un directorio. /// diff --git a/LIN.Identity/Data/Organizations/Members.cs b/LIN.Identity/Data/Organizations/Members.cs index 5f203b4..58b529e 100644 --- a/LIN.Identity/Data/Organizations/Members.cs +++ b/LIN.Identity/Data/Organizations/Members.cs @@ -152,7 +152,6 @@ public static async Task> ReadAll(int id, Conexió }, OrganizationAccess = new() { - ID = O.Member.OrganizationAccess == null ? 0 : O.Member.OrganizationAccess.ID, Rol = O.Member.OrganizationAccess == null ? OrgRoles.Undefine : O.Member.OrganizationAccess.Rol } }; diff --git a/LIN.Identity/Data/Organizations/Organizations.cs b/LIN.Identity/Data/Organizations/Organizations.cs index 9f7c073..037314a 100644 --- a/LIN.Identity/Data/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Organizations/Organizations.cs @@ -120,7 +120,6 @@ public static async Task> Create(Organization { org.Members.Add(new() { - ID = 0, Member = x, Organization = org, Rol = OrgRoles.SuperManager diff --git a/LIN.Identity/FilterModels/Account.cs b/LIN.Identity/FilterModels/Account.cs index df16daf..cb16299 100644 --- a/LIN.Identity/FilterModels/Account.cs +++ b/LIN.Identity/FilterModels/Account.cs @@ -12,6 +12,8 @@ public class Account public FindOn FindOn { get; set; } = FindOn.AllAccount; public IncludeOrgLevel OrgLevel { get; set; } = IncludeOrgLevel.Basic; + + } public enum IncludeOrg diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs index cd63991..a716f3c 100644 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ b/LIN.Identity/Hubs/PasskeyHub.cs @@ -6,14 +6,14 @@ public class PassKeyHub : Hub /// - /// Lista de intentos Passkey + /// Lista de intentos Passkey. /// public readonly static Dictionary> Attempts = new(); /// - /// Nuevo intento passkey + /// Nuevo intento passkey. /// /// Intento passkey public async Task JoinIntent(PassKeyModel attempt) @@ -44,10 +44,8 @@ public async Task JoinIntent(PassKeyModel attempt) // Agrega el modelo if (!Attempts.ContainsKey(attempt.User.ToLower())) - Attempts.Add(attempt.User.ToLower(), new() - { - attempt - }); + Attempts.Add(attempt.User.ToLower(), [attempt]); + else Attempts[attempt.User.ToLower()].Add(attempt); @@ -101,7 +99,7 @@ public override Task OnDisconnectedAsync(Exception? exception) /// - /// Un dispositivo envia el PassKey intent + /// Un dispositivo envía el PassKey intent /// public async Task JoinAdmin(string usuario) { diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index a09ce45..164d905 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -1,12 +1,11 @@ using LIN.Identity.Data; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.OpenApi.Models; -using System.ComponentModel; +try { - LIN.Access.Logger.Logger.AppName = "LIN.IDENTITY.V3"; + // Nombre de la app en Error Logger. + Logger.AppName = "LIN.IDENTITY.V3"; var builder = WebApplication.CreateBuilder(args); @@ -73,9 +72,17 @@ EmailWorker.StarService(); + + app.MapGet("/", () => "Hello World!"); + app.UseAuthorization(); app.MapControllers(); app.Run(); +} +catch (Exception ex) +{ + Logger.AppName = "LIN.Identity.V3"; + await Logger.Log(ex, 3); } \ No newline at end of file diff --git a/LIN.Identity/Queries/Accounts.cs b/LIN.Identity/Queries/Accounts.cs index 3ec9e8e..c711f5b 100644 --- a/LIN.Identity/Queries/Accounts.cs +++ b/LIN.Identity/Queries/Accounts.cs @@ -174,55 +174,87 @@ private static IQueryable BuildModel(IQueryable quer } catch { } - return from account in query - select new AccountModel - { - ID = account.ID, - Nombre = account.Visibilidad == AccountVisibility.Visible || account.OrganizationAccess != null && account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser || filters.IsAdmin - ? account.Nombre - : "Usuario privado", - Rol = account.Rol, - DirectoryMembers = new(), - IdentityId = account.Identity.Id, - Insignia = account.Insignia, - Estado = account.Estado, - Identity = new() - { - Id = account.Identity.Id, - Unique = account.Identity.Unique, - Type = account.Identity.Type, - }, - Contraseña = filters.SensibleInfo ? account.Contraseña : "", - Visibilidad = account.Visibilidad, - Birthday = account.Visibilidad == AccountVisibility.Visible || account.OrganizationAccess != null && account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser || filters.IsAdmin - ? account.Birthday - : default, - - Creación = account.Visibilidad == AccountVisibility.Visible || account.OrganizationAccess != null && account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser || filters.IsAdmin - ? account.Creación - : default, - Perfil = account.Visibilidad == AccountVisibility.Visible || account.OrganizationAccess != null && account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser || filters.IsAdmin - ? account.Perfil - : profile, - OrganizationAccess = account.OrganizationAccess != null && filters.IncludeOrg != FilterModels.IncludeOrg.None && (account.Visibilidad == AccountVisibility.Visible && filters.IncludeOrg == FilterModels.IncludeOrg.Include - || account.OrganizationAccess.Organization.ID == filters.ContextOrg || account.ID == filters.ContextUser - || filters.IsAdmin) - ? new OrganizationAccessModel() - { - ID = account.ID, - Rol = account.OrganizationAccess.Rol, - Organization = filters.OrgLevel == FilterModels.IncludeOrgLevel.Advance ? new() - { - ID = account.OrganizationAccess.Organization.ID, - Domain = !account.OrganizationAccess.Organization.IsPublic && !filters.IsAdmin && filters.IncludeOrg == FilterModels.IncludeOrg.IncludeIf && filters.ContextOrg != account.OrganizationAccess.Organization.ID ? "" - : account.OrganizationAccess.Organization.Domain, - Name = !account.OrganizationAccess.Organization.IsPublic && !filters.IsAdmin && filters.IncludeOrg == FilterModels.IncludeOrg.IncludeIf && filters.ContextOrg != account.OrganizationAccess.Organization.ID - ? "Organización privada" : account.OrganizationAccess.Organization.Name - } : new(), - Member = null!, - } - : null - }; + var queryFinal = from account in query + select new AccountModel + { + ID = account.ID, + Rol = account.Rol, + DirectoryMembers = null!, + IdentityId = account.Identity.Id, + Insignia = account.Insignia, + Estado = account.Estado, + Contraseña = filters.SensibleInfo ? account.Contraseña : "", + Visibilidad = account.Visibilidad, + Identity = new() + { + Id = account.Identity.Id, + Unique = account.Identity.Unique, + Type = account.Identity.Type, + }, + + // Nombre. + Nombre = account.Visibilidad == AccountVisibility.Visible + || (account.OrganizationAccess != null + && account.OrganizationAccess.Organization.ID == filters.ContextOrg) + || account.ID == filters.ContextUser + || filters.IsAdmin + ? account.Nombre + : "Usuario privado", + + // Cumpleaños. + Birthday = account.Visibilidad == AccountVisibility.Visible + || (account.OrganizationAccess != null + && account.OrganizationAccess.Organization.ID == filters.ContextOrg) + || account.ID == filters.ContextUser + || filters.IsAdmin + ? account.Birthday + : default, + + // Creación. + Creación = account.Visibilidad == AccountVisibility.Visible + || (account.OrganizationAccess != null + && account.OrganizationAccess.Organization.ID == filters.ContextOrg) + || account.ID == filters.ContextUser + || filters.IsAdmin + ? account.Creación + : default, + + // Perfil. + Perfil = account.Visibilidad == AccountVisibility.Visible + || (account.OrganizationAccess != null + && account.OrganizationAccess.Organization.ID == filters.ContextOrg) + || account.ID == filters.ContextUser + || filters.IsAdmin + ? account.Perfil + : profile, + + // Organización. + OrganizationAccess = account.OrganizationAccess != null + && filters.IncludeOrg != FilterModels.IncludeOrg.None + && (account.Visibilidad == AccountVisibility.Visible + && filters.IncludeOrg == FilterModels.IncludeOrg.Include + || account.OrganizationAccess.Organization.ID == filters.ContextOrg + || account.ID == filters.ContextUser + || filters.IsAdmin) + ? + new OrganizationAccessModel() + { + Rol = account.OrganizationAccess.Rol, + Organization = filters.OrgLevel == FilterModels.IncludeOrgLevel.Advance ? new() + { + ID = account.OrganizationAccess.Organization.ID, + Domain = !account.OrganizationAccess.Organization.IsPublic && !filters.IsAdmin && filters.IncludeOrg == FilterModels.IncludeOrg.IncludeIf && filters.ContextOrg != account.OrganizationAccess.Organization.ID ? "" + : account.OrganizationAccess.Organization.Domain, + Name = !account.OrganizationAccess.Organization.IsPublic && !filters.IsAdmin && filters.IncludeOrg == FilterModels.IncludeOrg.IncludeIf && filters.ContextOrg != account.OrganizationAccess.Organization.ID + ? "Organización privada" : account.OrganizationAccess.Organization.Name + } : new(), + Member = null!, + } + : null + }; + + return queryFinal; + } @@ -235,15 +267,6 @@ private static IQueryable BuildModel(IQueryable quer private static IQueryable BuildModel(IQueryable query) { - byte[] profile = - { - }; - try - { - profile = File.ReadAllBytes("wwwroot/user.png"); - } - catch { } - return from d in query select new DirectoryModel { @@ -261,8 +284,4 @@ private static IQueryable BuildModel(IQueryable } - - - - } \ No newline at end of file diff --git a/LIN.Identity/Services/Login/LoginOnOrg.cs b/LIN.Identity/Services/Login/LoginOnOrg.cs index 02e71cd..c2363ae 100644 --- a/LIN.Identity/Services/Login/LoginOnOrg.cs +++ b/LIN.Identity/Services/Login/LoginOnOrg.cs @@ -53,7 +53,6 @@ private async Task LoadOrganization() } OrganizationAccess.Rol = Account.OrganizationAccess!.Rol; - OrganizationAccess.ID = Account.OrganizationAccess.ID; OrganizationAccess.Organization = orgResponse.Model; return true; From 42c6ac0047cbb92c43e0ce8f9bded63089c58a06 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 11:47:21 -0500 Subject: [PATCH 029/178] IAM Applications -> IAM V2 --- LIN.Identity/Data/Context.cs | 22 ++++++ .../Data/Directories/DirectoryMembers.cs | 70 +++++++++++++++++++ .../Data/Organizations/Organizations.cs | 51 ++++++-------- LIN.Identity/Services/Iam/Applications.cs | 57 +++++++++------ 4 files changed, 148 insertions(+), 52 deletions(-) create mode 100644 LIN.Identity/Data/Directories/DirectoryMembers.cs diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index f889b96..063a298 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -113,6 +113,28 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(p => p.DirectoryId); + modelBuilder.Entity() + .HasOne(p => p.Directory) + .WithMany() + .HasForeignKey(p => p.DirectoryId); + + + modelBuilder.Entity() + .HasOne(p => p.Application) + .WithMany() + .HasForeignKey(p => p.ApplicationID) + .OnDelete(DeleteBehavior.NoAction); + ; + + + modelBuilder.Entity() + .HasOne(p => p.Account) + .WithMany() + .HasForeignKey(p => p.AccountID); + + + + modelBuilder.Entity() .HasOne(oa => oa.Organization) .WithMany(o => o.Members) diff --git a/LIN.Identity/Data/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Directories/DirectoryMembers.cs new file mode 100644 index 0000000..31ce468 --- /dev/null +++ b/LIN.Identity/Data/Directories/DirectoryMembers.cs @@ -0,0 +1,70 @@ +namespace LIN.Identity.Data; + + +public class DirectoryMembers +{ + + + #region Abstracciones + + + /// + /// Agregar un miembro a un directorio. + /// + /// Modelo. + public static async Task Create(DirectoryMember directory) + { + + // Obtiene la conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + var res = await Create(directory, context); + context.CloseActions(connectionKey); + return res; + + } + + + + #endregion + + + + /// + /// Agregar un miembro a un directorio. + /// + /// Modelo. + /// Contexto de conexión. + public static async Task Create(DirectoryMember model, Conexión context) + { + + // Ejecución + try + { + + // Ya existen los registros. + context.DataBase.Attach(model.Account); + context.DataBase.Attach(model.Directory); + + // Agregar el elemento. + await context.DataBase.DirectoryMembers.AddAsync(model); + + // Guardar los cambios. + context.DataBase.SaveChanges(); + + // Respuesta. + return new() + { + Response = Responses.Success + }; + + } + catch (Exception) + { + } + + return new(); + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Data/Organizations/Organizations.cs b/LIN.Identity/Data/Organizations/Organizations.cs index 037314a..e5c0d7d 100644 --- a/LIN.Identity/Data/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Organizations/Organizations.cs @@ -51,6 +51,7 @@ public static async Task> Read(int id) public static async Task> Create(OrganizationModel data, Conexión context) { + // Modelo. data.ID = 0; // Ejecución @@ -59,9 +60,10 @@ public static async Task> Create(Organization try { + // Lista de cuentas. List accounts = []; - // Crear cuentas + // Enumerar las cuentas actuales. foreach (var account in data.Members.Select(t => t.Member)) { AccountModel accountModel = new() @@ -86,18 +88,17 @@ public static async Task> Create(Organization context.DataBase.Accounts.Add(accountModel); } - - + // Guardar cambios. context.DataBase.SaveChanges(); - - + // Modelo la organización. OrganizationModel org = new() { + ID = 0, Domain = data.Domain, Name = data.Name, IsPublic = data.IsPublic, - ID = 0, + Members = [], Directory = new() { Creación = DateTime.Now, @@ -112,40 +113,34 @@ public static async Task> Create(Organization } }; - - - org.Members = []; - - foreach (var x in accounts) + // Agregar miembros. + foreach (var account in accounts) { + // Miembros de la organización. org.Members.Add(new() { - Member = x, + Member = account, Organization = org, Rol = OrgRoles.SuperManager }); - } - - - var res = await context.DataBase.Organizations.AddAsync(org); - - - context.DataBase.SaveChanges(); - - - foreach (var x in org.Members) - { + // Miembros del directorio. org.Directory.Members.Add(new() { - Account = x.Member, - Directory = org.Directory + Account = account, + Directory = org.Directory, + Rol = DirectoryRoles.Administrator }); } - context.DataBase.SaveChanges(); + // Agregar el modelo. + await context.DataBase.Organizations.AddAsync(org); + + // Guardar en la BD. + context.DataBase.SaveChanges(); + // Commit cambios. transaction.Commit(); return new(Responses.Success, data); @@ -193,10 +188,8 @@ public static async Task> Read(int id, Conexi // Email no existe if (org == null) - { return new(Responses.NotRows); - } - + return new(Responses.Success, org); } catch diff --git a/LIN.Identity/Services/Iam/Applications.cs b/LIN.Identity/Services/Iam/Applications.cs index 83e428e..0d06a50 100644 --- a/LIN.Identity/Services/Iam/Applications.cs +++ b/LIN.Identity/Services/Iam/Applications.cs @@ -25,7 +25,7 @@ public static async Task> ValidateAccess(int account, Model = IamLevels.NotAccess }; - // Si es admin del recurso / creador + // Si es admin del recurso / creador. if (resource.Model.AccountID == account) return new() { @@ -33,33 +33,44 @@ public static async Task> ValidateAccess(int account, Model = IamLevels.Privileged }; - // Si es un recurso publico. - //if (resource.Model.AllowAnyAccount) - // return new() - // { - // Response = Responses.Success, - // Model = IamLevels.Visualizer - // }; - - //// Validar acceso al recurso privado. - //var isAllowed = await Data.Applications.IsAllow(app, account); - - //// No es permitido. - //if (!isAllowed.Model) - // return new() - // { - // Response = Responses.Success, - // Model = IamLevels.NotAccess - // }; - - // Acceso visualizador. + + // App publica. + if (resource.Model.DirectoryId <= 0) + { + return new() + { + Response = Responses.Success, + Model = IamLevels.Visualizer + }; + } + + + var (context, contextKey) = Conexión.GetOneConnection(); + + + var directory = (from m in context.DataBase.DirectoryMembers + where m.AccountId == account + && m.DirectoryId == resource.Model.DirectoryId + select m).FirstOrDefault(); + + + if (directory == null) + { + return new() + { + Response = Responses.NotRows, + Model = IamLevels.NotAccess, + Message = "No tienes acceso a este recurso/app." + }; + } + + return new() { Response = Responses.Success, - Model = IamLevels.Visualizer + Model = (directory.Rol == DirectoryRoles.Administrator) ? IamLevels.Privileged : IamLevels.Visualizer }; - } From 3422a88b41d1fdbf0aad3f5c557721aec10e952b Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 12:03:35 -0500 Subject: [PATCH 030/178] Allow To --- .../Applications/ApplicationController.cs | 39 ++++++++++ LIN.Identity/Data/Applications.cs | 74 ++++++++++++++++++- LIN.Identity/Services/Iam/Applications.cs | 3 + 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs index 77e3584..00f2941 100644 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -74,5 +74,44 @@ public async Task> GetAll([FromHeader] str + /// + /// Crear acceso permitido a una app. + /// + /// Token de acceso. + /// ID de la aplicación. + /// ID del integrante. + [HttpPut] + public async Task> InsertAllow([FromHeader] string token, [FromHeader] int appId, [FromHeader] int accountId) + { + + // Información del token. + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + // Si el token es invalido. + if (!isValid) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "El token es invalido." + }; + + // Respuesta de Iam. + var iam = await Services.Iam.Applications.ValidateAccess(userId, appId); + + // Validación de Iam + if (iam.Model != IamLevels.Privileged) + return new ReadOneResponse() + { + Response = Responses.Unauthorized, + Message = "No tienes autorización para modificar este recurso." + }; + + + // Enviar la actualización + var data = await Data.Applications.AllowTo(appId, accountId); + + return data; + + } } \ No newline at end of file diff --git a/LIN.Identity/Data/Applications.cs b/LIN.Identity/Data/Applications.cs index 4e93904..079ed32 100644 --- a/LIN.Identity/Data/Applications.cs +++ b/LIN.Identity/Data/Applications.cs @@ -39,7 +39,14 @@ public static async Task> ReadAll(int id) + public static async Task> AllowTo(int appId, int accountId) + { + var (context, contextKey) = Conexión.GetOneConnection(); + var res = await AllowTo(appId, accountId, context); + context.CloseActions(contextKey); + return res; + } @@ -162,7 +169,7 @@ public static async Task> Read(int id, Conexi // Email no existe if (email == null) return new(Responses.NotRows); - + // Correcto. return new(Responses.Success, email); } @@ -195,7 +202,7 @@ public static async Task> Read(string key, Con // Email no existe if (email == null) return new(Responses.NotRows); - + return new(Responses.Success, email); } @@ -231,7 +238,7 @@ public static async Task> ReadByAppUid(string return new(Responses.Success, app); } - catch (Exception) + catch (Exception) { } @@ -240,4 +247,65 @@ public static async Task> ReadByAppUid(string + + public static async Task> AllowTo(int appId, int accountId, Conexión context) + { + + // Ejecución + try + { + + + var application = await (from app in context.DataBase.Applications + where app.ID == appId + select new ApplicationModel() + { + ID = app.ID, + DirectoryId = app.Directory.ID + }).FirstOrDefaultAsync(); + + if (application == null) + { + return new() + { + Response = Responses.NotRows + }; + } + + + if (application.DirectoryId <= 0) + { + return new() + { + Response = Responses.NotFoundDirectory + }; + } + + DirectoryMember member = new() + { + Account = new() + { + ID = accountId, + }, + Directory = new() + { + ID = application.DirectoryId, + } + }; + context.DataBase.Attach(member.Account); + context.DataBase.Attach(member.Directory); + + context.DataBase.DirectoryMembers.Add(member); + + context.DataBase.SaveChanges(); + + return new(Responses.Success, true); + } + catch + { + } + + return new(); + } + } \ No newline at end of file diff --git a/LIN.Identity/Services/Iam/Applications.cs b/LIN.Identity/Services/Iam/Applications.cs index 0d06a50..72c2714 100644 --- a/LIN.Identity/Services/Iam/Applications.cs +++ b/LIN.Identity/Services/Iam/Applications.cs @@ -54,6 +54,9 @@ public static async Task> ValidateAccess(int account, select m).FirstOrDefault(); + context.CloseActions(contextKey); + + if (directory == null) { return new() From 2ca8df523f87e6a1cce0c3886743bcfa34a4cabf Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 12:06:25 -0500 Subject: [PATCH 031/178] mejoras --- LIN.Identity/Areas/Applications/ApplicationController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs index 00f2941..57d08a9 100644 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -114,4 +114,6 @@ public async Task> InsertAllow([FromHeader] string tok } + + } \ No newline at end of file From c1aada641abe58840159fb00e4399ce0d20f6587 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 16:27:59 -0500 Subject: [PATCH 032/178] =?UTF-8?q?Actualizar=20la=20contrase=C3=B1a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Areas/Accounts/AccountController.cs | 29 ------ .../Accounts/AccountSecurityController.cs | 99 +++++++++++++++++++ LIN.Identity/Areas/Security/Security.cs | 1 + LIN.Identity/Data/Accounts/AccountsUpdate.cs | 47 +++++++++ 4 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 LIN.Identity/Areas/Accounts/AccountSecurityController.cs diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index 1accb27..61a3c8a 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -241,35 +241,6 @@ public async Task> ReadAll([FromBody] List - /// Elimina una cuenta - /// - /// Token de acceso - [HttpDelete("delete")] - public async Task Delete([FromHeader] string token) - { - - // Información del token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); - - // Si es invalido. - if (!isValid) - return new ResponseBase - { - Response = Responses.Unauthorized, - Message = "Token invalido" - }; - - if (userId <= 0) - return new(Responses.InvalidParam); - - var response = await Data.Accounts.Delete(userId); - return response; - } - - - - /// /// (ADMIN) encuentra diez (10) usuarios que coincidan con el patron /// diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs new file mode 100644 index 0000000..cc824af --- /dev/null +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -0,0 +1,99 @@ +namespace LIN.Identity.Areas.Accounts; + + +[Route("account/security")] +public class AccountSecurityController : ControllerBase +{ + + + + /// + /// Elimina una cuenta + /// + /// Token de acceso + [HttpDelete("delete")] + public async Task Delete([FromHeader] string token) + { + + // Información del token. + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + // Si es invalido. + if (!isValid) + return new ResponseBase + { + Response = Responses.Unauthorized, + Message = "Token invalido" + }; + + if (userId <= 0) + return new(Responses.InvalidParam); + + var response = await Data.Accounts.Delete(userId); + return response; + } + + + + /// + /// Actualizar la contraseña. + /// + /// Id de la cuenta actual. + /// Contraseña actual. + /// Nueva contraseña. + [HttpPatch("update/password")] + public async Task UpdatePassword([FromHeader] int account, [FromQuery] string actualPassword, [FromHeader] string newPassword) + { + + // Validación de parámetros. + if (account <= 0 || string.IsNullOrWhiteSpace(actualPassword) || string.IsNullOrWhiteSpace(newPassword)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; + + // Tamaño invalido. + if (newPassword.Length < 4) + return new(Responses.InvalidParam) + { + Message = "La nueva contraseña debe de tener mas de 4 dígitos." + }; + + + // Data actual. + var actualData = await Data.Accounts.Read(account, new() + { + ContextOrg = 0, + SensibleInfo = true, + ContextUser = account, + FindOn = FilterModels.FindOn.StableAccounts, + IncludeOrg = FilterModels.IncludeOrg.None, + IsAdmin = true, + OrgLevel = FilterModels.IncludeOrgLevel.Basic + }); + + // Si no existe la cuenta. + if (actualData.Response != Responses.Success) + return new(Responses.NotExistAccount) + { + Message = "No se encontró la cuenta." + }; + + // Encriptar la contraseña actual. + if (EncryptClass.Encrypt(actualPassword) != actualData.Model.Contraseña) + return new(Responses.Unauthorized) + { + Message = "La contraseña actual es diferente a la proporcionada." + }; + + // Encriptar la nueva contraseña. + var response = await Data.Accounts.Update(account, EncryptClass.Encrypt(newPassword)); + + return response; + } + + + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Security/Security.cs b/LIN.Identity/Areas/Security/Security.cs index 2fc971e..075b0a9 100644 --- a/LIN.Identity/Areas/Security/Security.cs +++ b/LIN.Identity/Areas/Security/Security.cs @@ -4,4 +4,5 @@ [Route("security")] public class Security : ControllerBase { + } \ No newline at end of file diff --git a/LIN.Identity/Data/Accounts/AccountsUpdate.cs b/LIN.Identity/Data/Accounts/AccountsUpdate.cs index 44d911f..bfa9817 100644 --- a/LIN.Identity/Data/Accounts/AccountsUpdate.cs +++ b/LIN.Identity/Data/Accounts/AccountsUpdate.cs @@ -95,6 +95,19 @@ public static async Task Update(int id, AccountVisibility visibili } + + public static async Task Update(int id, string password) + { + + var (context, key) = Conexión.GetOneConnection(); + + var res = await Update(id, password, context); + context.CloseActions(key); + return res; + + } + + #endregion @@ -296,4 +309,38 @@ public static async Task Update(int user, AccountVisibility visibi } + + + /// + /// Actualiza la contraseña + /// + /// ID + /// Nueva contraseña + /// Contexto de conexión con la BD + public static async Task Update(int user, string password, Conexión context) + { + + // Encontrar el usuario + var usuario = await (from U in context.DataBase.Accounts + where U.ID == user + select U).FirstOrDefaultAsync(); + + // Si el usuario no existe + if (usuario == null) + { + return new(Responses.NotExistAccount); + } + + // Cambiar visibilidad + usuario.Contraseña = password; + + context.DataBase.SaveChanges(); + return new(Responses.Success); + + } + + + + + } \ No newline at end of file From 48c053b4134e7b0b20a9e1a02a24b3d008b17361 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 18:01:00 -0500 Subject: [PATCH 033/178] Eliminacion de elementos no usados --- LIN.Identity/Areas/Security/Security.cs | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 LIN.Identity/Areas/Security/Security.cs diff --git a/LIN.Identity/Areas/Security/Security.cs b/LIN.Identity/Areas/Security/Security.cs deleted file mode 100644 index 075b0a9..0000000 --- a/LIN.Identity/Areas/Security/Security.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace LIN.Identity.Areas.Security; - - -[Route("security")] -public class Security : ControllerBase -{ - -} \ No newline at end of file From 81a7c251834745bbcfccada3b704e6bab619fbd9 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 18:09:58 -0500 Subject: [PATCH 034/178] Mejoras --- LIN.Identity/Areas/Accounts/AccountSecurityController.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs index cc824af..2bfe803 100644 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -93,7 +93,4 @@ public async Task UpdatePassword([FromHeader] int account, [Fr } - - - } \ No newline at end of file From b637f634af5558ad95120a4ac51cb58a41610476 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 21:46:57 -0500 Subject: [PATCH 035/178] Loginlog controller --- .../Areas/Accounts/AccountController.cs | 2 +- .../Areas/Accounts/AccountLogsController.cs | 36 +++++++++++++++++++ LIN.Identity/Data/Applications.cs | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 LIN.Identity/Areas/Accounts/AccountLogsController.cs diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index 61a3c8a..a631ff4 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -304,7 +304,7 @@ public async Task Update([FromBody] AccountModel modelo, [From // Organizar el modelo. modelo.Identity.Id = userId; - modelo.Perfil = Image.Zip(modelo.Perfil); + modelo.Perfil = Image.Zip(modelo.Perfil ?? []); if (modelo.Identity.Id <= 0 || modelo.Nombre.Any()) return new(Responses.InvalidParam); diff --git a/LIN.Identity/Areas/Accounts/AccountLogsController.cs b/LIN.Identity/Areas/Accounts/AccountLogsController.cs new file mode 100644 index 0000000..63fe3c7 --- /dev/null +++ b/LIN.Identity/Areas/Accounts/AccountLogsController.cs @@ -0,0 +1,36 @@ +namespace LIN.Identity.Areas.Accounts; + + +[Route("account/logs")] +public class AccountLogsController : ControllerBase +{ + + + /// + /// Obtienes la lista de accesos asociados a una cuenta + /// + /// Token de acceso + [HttpGet] + public async Task> GetAll([FromHeader] string token) + { + + // JWT. + var (isValid, _, userId, _, _) = Jwt.Validate(token); + + // Validación. + if (!isValid) + return new(Responses.Unauthorized) + { + Message = "Token invalido." + }; + + // Obtiene el usuario. + var result = await Data.Logins.ReadAll(userId); + + // Retorna el resultado. + return result ?? new(); + + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Data/Applications.cs b/LIN.Identity/Data/Applications.cs index 079ed32..31a8a1d 100644 --- a/LIN.Identity/Data/Applications.cs +++ b/LIN.Identity/Data/Applications.cs @@ -261,7 +261,7 @@ public static async Task> AllowTo(int appId, int accountId select new ApplicationModel() { ID = app.ID, - DirectoryId = app.Directory.ID + DirectoryId = app.DirectoryId }).FirstOrDefaultAsync(); if (application == null) From ed3d3ad30939d5dde48898f2b02a6dee48d9e1b9 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 21:53:05 -0500 Subject: [PATCH 036/178] Mejoras --- LIN.Identity/Areas/Directories/DirectoryController.cs | 1 + LIN.Identity/Areas/Organizations/OrganizationController.cs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index 3e92ca6..cbe4118 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -44,4 +44,5 @@ public async Task> Read([FromQuery] int id, } + } diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index 342808f..cbae424 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -1,5 +1,3 @@ -using LIN.Identity.Validations; - namespace LIN.Identity.Areas.Organizations; From 39bfcc003cc81819193bd7ff3d4b58531dd5c39c Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Dec 2023 22:09:09 -0500 Subject: [PATCH 037/178] Mejoras con Directory --- .../Areas/Directories/DirectoryController.cs | 39 ++++++++++- .../Data/Directories/DirectoryMembers.cs | 67 ++++++++++++++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index cbe4118..37dcaa0 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -45,4 +45,41 @@ public async Task> Read([FromQuery] int id, } -} + + /// + /// Obtener los directorios asociados. + /// + /// Token de acceso. + [HttpGet("read/all")] + public async Task> ReadAll([FromHeader] string token) + { + + // Información del token. + var (isValid, _, user, _, _) = Jwt.Validate(token); + + // Token es invalido. + if (!isValid) + return new ReadAllResponse() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + // Obtiene el usuario. + var response = await Data.DirectoryMembers.ReadAll(user); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Data/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Directories/DirectoryMembers.cs index 31ce468..c0cfde8 100644 --- a/LIN.Identity/Data/Directories/DirectoryMembers.cs +++ b/LIN.Identity/Data/Directories/DirectoryMembers.cs @@ -25,7 +25,25 @@ public static async Task Create(DirectoryMember directory) } - + + /// + /// Obtiene los directorios donde un usuario es integrante. + /// + /// Id de la cuenta. + public static async Task> ReadAll(int accountId) + { + + // Obtiene la conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + var res = await ReadAll(accountId, context); + context.CloseActions(connectionKey); + return res; + + } + + + #endregion @@ -67,4 +85,51 @@ public static async Task Create(DirectoryMember model, Conexión } + + /// + /// Obtiene los directorios donde un usuario es integrante. + /// + /// Id de la cuenta. + /// Contexto de conexión. + public static async Task> ReadAll(int accountId, Conexión context) + { + + // Ejecución + try + { + + // Directorios. + var directories = await (from directory in context.DataBase.DirectoryMembers + where directory.AccountId == accountId + select new DirectoryMember + { + Rol = directory.Rol, + DirectoryId = directory.AccountId, + Directory = new() + { + ID = directory.Directory.ID, + Creación = directory.Directory.Creación, + Nombre = directory.Directory.Nombre, + IdentityId = directory.Directory.IdentityId, + Identity = new() + { + Id = directory.Directory.Identity.Id, + Unique = directory.Directory.Identity.Unique, + Type = directory.Directory.Identity.Type + } + }, + AccountId = accountId + }).ToListAsync(); + + return new(Responses.Success, directories); + + } + catch (Exception) + { + } + + return new(); + } + + } \ No newline at end of file From 206a8b534d5d7bbe9839a1c866eba9686fb068b6 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 9 Dec 2023 10:14:15 -0500 Subject: [PATCH 038/178] Tabla de politicas --- LIN.Identity/Data/Context.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index 063a298..234b6c5 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -61,6 +61,12 @@ public class Context : DbContext public DbSet Emails { get; set; } + /// + /// Políticas. + /// + public DbSet Policies { get; set; } + + @@ -156,6 +162,19 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Restrict); ; + + modelBuilder.Entity() + .HasOne(p => p.Directory) + .WithMany(d => d.Policies) + .HasForeignKey(p => p.DirectoryId) + .OnDelete(DeleteBehavior.Restrict); + + + + + + + // Configure OrganizationAccessModel modelBuilder.Entity() .HasOne(o => o.Member) @@ -188,6 +207,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("DIRECTORY_MEMBERS"); modelBuilder.Entity().ToTable("ORGANIZATIONS_MEMBERS"); modelBuilder.Entity().ToTable("LOGS"); + modelBuilder.Entity().ToTable("POLICIES"); } From 07d89f3820b0dd0c207014a695a3209f435d6b0a Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 9 Dec 2023 10:26:31 -0500 Subject: [PATCH 039/178] Politicas DATA --- LIN.Identity/Data/Policies.cs | 114 ++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 LIN.Identity/Data/Policies.cs diff --git a/LIN.Identity/Data/Policies.cs b/LIN.Identity/Data/Policies.cs new file mode 100644 index 0000000..2ad72f1 --- /dev/null +++ b/LIN.Identity/Data/Policies.cs @@ -0,0 +1,114 @@ +namespace LIN.Identity.Data; + + +public class Policies +{ + + + #region Abstracciones + + + + /// + /// Crear nueva política. + /// + /// Modelo. + public static async Task Create(PolicyModel data) + { + var (context, contextKey) = Conexión.GetOneConnection(); + var response = await Create(data, context); + context.CloseActions(contextKey); + return response; + } + + + + /// + /// Obtener las políticas. + /// + /// Id de la cuenta. + public static async Task> ReadAll(int id) + { + var (context, contextKey) = Conexión.GetOneConnection(); + + var res = await ReadAll(id, context); + context.CloseActions(contextKey); + return res; + } + + + + #endregion + + + + /// + /// Crear política. + /// + /// Modelo + /// Contexto de conexión. + public static async Task Create(PolicyModel data, Conexión context) + { + + // ID. + data.Id = 0; + + // Ejecución + try + { + + // Existe el directorio. + context.DataBase.Attach(data.Directory); + + // Guardar la información. + await context.DataBase.Policies.AddAsync(data); + + // Llevar info a la BD. + context.DataBase.SaveChanges(); + + return new(Responses.Success, data.Id); + } + catch (Exception ex) + { + + if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) + return new(Responses.Undefined); + + } + + return new(); + } + + + + /// + /// Obtiene las políticas asociadas a un usuario. + /// + /// ID de la cuenta + /// Contexto de conexión + public static async Task> ReadAll(int id, Conexión context) + { + + // Ejecución + try + { + + var policies = await (from directory in context.DataBase.DirectoryMembers + where directory.AccountId == id + join policie in context.DataBase.Policies + on directory.DirectoryId equals policie.DirectoryId + select policie).ToListAsync(); + + + return new(Responses.Success, policies); + + } + catch + { + } + + return new(); + } + + +} \ No newline at end of file From 315f651fdba9c37dfcbb062d5aa94da112b72081 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 9 Dec 2023 11:47:18 -0500 Subject: [PATCH 040/178] =?UTF-8?q?Comprobar=20politicas=20de=20contrase?= =?UTF-8?q?=C3=B1a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Accounts/AccountSecurityController.cs | 10 +++ LIN.Identity/Validations/AccountPassword.cs | 63 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 LIN.Identity/Validations/AccountPassword.cs diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs index 2bfe803..4a55f3c 100644 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -86,6 +86,16 @@ public async Task UpdatePassword([FromHeader] int account, [Fr Message = "La contraseña actual es diferente a la proporcionada." }; + // Validar políticas de contraseña. + var result = await AccountPassword.ValidatePassword(account, newPassword); + + // Invalido por políticas + if (!result) + return new(Responses.PoliciesNotComplied) + { + Message = "Invalidado por no cumplir las políticas del directorio." + }; + // Encriptar la nueva contraseña. var response = await Data.Accounts.Update(account, EncryptClass.Encrypt(newPassword)); diff --git a/LIN.Identity/Validations/AccountPassword.cs b/LIN.Identity/Validations/AccountPassword.cs new file mode 100644 index 0000000..e97d0d0 --- /dev/null +++ b/LIN.Identity/Validations/AccountPassword.cs @@ -0,0 +1,63 @@ +namespace LIN.Identity.Validations; + + +public class AccountPassword +{ + + + /// + /// Validar la contraseña con las políticas de directorio. + /// + /// Id de la cuenta. + /// Contraseña a validar. + public static async Task ValidatePassword(int account, string password) + { + + // Contexto. + var (context, contextKey) = Conexión.GetOneConnection(); + + // Política. + var policy = await (from directory in context.DataBase.DirectoryMembers + where directory.AccountId == account + join policie in context.DataBase.Policies + on directory.DirectoryId equals policie.DirectoryId + where policie.Type == PolicyTypes.PasswordLength + orderby policie.Creation + select new PolicyModel + { + Id = policie.Id, + ValueJson = policie.ValueJson, + Type = policie.Type + }).LastOrDefaultAsync(); + + // Política no existe. + if (policy == null) + return true; + + try + { + + // Valor. + dynamic? policyValue = Newtonsoft.Json.JsonConvert.DeserializeObject(policy.ValueJson); + + // Convertir el valor. + var can = int.TryParse(((policyValue?.length) as object)?.ToString(), out int length); + + // No se pudo pasear. + if (!can) + _ = Logger.Log($"Hubo un error al obtener el valor de Policy con id {policy.Id}", policy.ValueJson ?? "NULL", 3); + + // Validar. + if (password.Length < length) + return false; + + } + catch (Exception) + { + } + + return true; + + } + +} \ No newline at end of file From 698bfdfe73813b611dab944d69606cae0d10e890 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 9 Dec 2023 12:07:27 -0500 Subject: [PATCH 041/178] Mejoras --- .../Areas/Accounts/AccountSecurityController.cs | 2 +- LIN.Identity/Data/Organizations/Organizations.cs | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs index 4a55f3c..73ce71b 100644 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -56,7 +56,7 @@ public async Task UpdatePassword([FromHeader] int account, [Fr if (newPassword.Length < 4) return new(Responses.InvalidParam) { - Message = "La nueva contraseña debe de tener mas de 4 dígitos." + Message = "La nueva contraseña debe de tener mas de 4 dígitos y cumplir con las políticas asociadas." }; diff --git a/LIN.Identity/Data/Organizations/Organizations.cs b/LIN.Identity/Data/Organizations/Organizations.cs index e5c0d7d..028135d 100644 --- a/LIN.Identity/Data/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Organizations/Organizations.cs @@ -110,9 +110,20 @@ public static async Task> Create(Organization }, Nombre = data.Directory.Nombre, IdentityId = 0, + Policies = + [ + new PolicyModel + { + Creation = DateTime.Now, + Id =0, + Type = PolicyTypes.PasswordLength, + ValueJson = """ {"length": 8} """ + } + ] } }; + // Agregar miembros. foreach (var account in accounts) { @@ -189,7 +200,7 @@ public static async Task> Read(int id, Conexi // Email no existe if (org == null) return new(Responses.NotRows); - + return new(Responses.Success, org); } catch From 2670869feceb440863ae30ac7c8d40060d1bbbfb Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 10 Dec 2023 17:13:47 -0500 Subject: [PATCH 042/178] Soluciones --- LIN.Identity/Data/Context.cs | 4 ---- LIN.Identity/LIN.Identity.csproj | 1 + LIN.Identity/Program.cs | 7 ++++++- LIN.Identity/Services/EmailWorker.cs | 4 +--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index 234b6c5..3df3445 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -69,10 +69,6 @@ public class Context : DbContext - - - - /// /// Nuevo contexto a la base de datos /// diff --git a/LIN.Identity/LIN.Identity.csproj b/LIN.Identity/LIN.Identity.csproj index 7977807..21d27ac 100644 --- a/LIN.Identity/LIN.Identity.csproj +++ b/LIN.Identity/LIN.Identity.csproj @@ -15,6 +15,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index 164d905..e94a5c7 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -25,8 +25,13 @@ }); - +#if DEBUG + var sqlConnection = builder.Configuration["ConnectionStrings:local"] ?? string.Empty; +#elif RELEASE var sqlConnection = builder.Configuration["ConnectionStrings:somee"] ?? string.Empty; +#endif + + // Servicio de BD builder.Services.AddDbContext(options => diff --git a/LIN.Identity/Services/EmailWorker.cs b/LIN.Identity/Services/EmailWorker.cs index fcbe704..5ae2b4e 100644 --- a/LIN.Identity/Services/EmailWorker.cs +++ b/LIN.Identity/Services/EmailWorker.cs @@ -40,7 +40,6 @@ public static async Task SendVerification(string to, string url, string ma body = body.Replace("@@Mensaje", "Hemos recibido tu solicitud para agregar una dirección de correo electrónico adicional a tu cuenta. Para completar este proceso, da click en el siguiente botón"); body = body.Replace("@@ButtonMessage", "Verificar"); - // Envía el email return await SendMail(to, "Verifica el email", body); @@ -101,8 +100,7 @@ public static async Task SendMail(string to, string asunto, string body) }; - - var json = Newtonsoft.Json.JsonConvert.SerializeObject(requestData); + var json = System.Text.Json.JsonSerializer.Serialize(requestData); using var content = new StringContent(json, Encoding.UTF8, "application/json"); client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken); From d7a5275e2cfc071cffed8740c5083a88852dfd27 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 11 Dec 2023 13:28:13 -0500 Subject: [PATCH 043/178] Tests --- .../AuthenticationController.cs | 7 + .../Organizations/OrganizationController.cs | 6 +- LIN.Identity/Data/Applications.cs | 22 ++- LIN.Identity/Data/Context.cs | 8 +- .../Data/Directories/DirectoryMembers.cs | 10 +- LIN.Identity/Data/Organizations/Members.cs | 2 +- .../Data/Organizations/Organizations.cs | 3 +- LIN.Identity/Data/Policies.cs | 2 +- LIN.Identity/Queries/Accounts.cs | 1 - LIN.Identity/Services/Iam/Applications.cs | 130 ++++++++++++++---- LIN.Identity/Services/Jwt.cs | 4 +- LIN.Identity/Validations/Account.cs | 2 +- LIN.Identity/Validations/AccountPassword.cs | 2 +- 13 files changed, 146 insertions(+), 53 deletions(-) diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index 0c44b4a..88dde2c 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -124,4 +124,11 @@ public async Task> LoginWithToken([FromHeader] + + [HttpGet("Hi")] + public async Task Get([FromHeader]int account) + { + var iam = await Services.Iam.Applications.ValidateAccess(account, 0); + return iam; + } } \ No newline at end of file diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index cbae424..2b9a8c0 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -19,7 +19,7 @@ public async Task Create([FromBody] OrganizationModel modelo return new(Responses.InvalidParam); - // Conexi�n + // BD. var (context, connectionKey) = Conexión.GetOneConnection(); @@ -47,8 +47,8 @@ public async Task Create([FromBody] OrganizationModel modelo { modelo.Directory.Members.Add(new() { - Account = member.Member, - AccountId = 0, + Identity = member.Member.Identity, + IdentityId =0, Directory = modelo.Directory, DirectoryId = 0 }); diff --git a/LIN.Identity/Data/Applications.cs b/LIN.Identity/Data/Applications.cs index 31a8a1d..1c227ec 100644 --- a/LIN.Identity/Data/Applications.cs +++ b/LIN.Identity/Data/Applications.cs @@ -38,7 +38,11 @@ public static async Task> ReadAll(int id) - + /// + /// Agregar una identidad al directorio de una app. + /// + /// Id de la app. + /// Id de la identidad. public static async Task> AllowTo(int appId, int accountId) { var (context, contextKey) = Conexión.GetOneConnection(); @@ -247,8 +251,13 @@ public static async Task> ReadByAppUid(string - - public static async Task> AllowTo(int appId, int accountId, Conexión context) + /// + /// Permitir acceso a una identidad al directorio de una app. + /// + /// Id de la app. + /// Id de la identidad. + /// Cuenta de conexión. + public static async Task> AllowTo(int appId, int identityId, Conexión context) { // Ejecución @@ -283,16 +292,16 @@ public static async Task> AllowTo(int appId, int accountId DirectoryMember member = new() { - Account = new() + Identity = new IdentityModel() { - ID = accountId, + Id = identityId }, Directory = new() { ID = application.DirectoryId, } }; - context.DataBase.Attach(member.Account); + context.DataBase.Attach(member.Identity); context.DataBase.Attach(member.Directory); context.DataBase.DirectoryMembers.Add(member); @@ -308,4 +317,5 @@ public static async Task> AllowTo(int appId, int accountId return new(); } + } \ No newline at end of file diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index 3df3445..a692d05 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -152,10 +152,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() - .HasOne(p => p.Account) + .HasOne(p => p.Identity) .WithMany(d => d.DirectoryMembers) - .HasForeignKey(p => p.AccountId) - .OnDelete(DeleteBehavior.Restrict); ; + .HasForeignKey(p => p.IdentityId) + .OnDelete(DeleteBehavior.Restrict); @@ -181,7 +181,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasKey(t => new { - t.AccountId, + t.IdentityId, t.DirectoryId }); diff --git a/LIN.Identity/Data/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Directories/DirectoryMembers.cs index c0cfde8..f3bbe60 100644 --- a/LIN.Identity/Data/Directories/DirectoryMembers.cs +++ b/LIN.Identity/Data/Directories/DirectoryMembers.cs @@ -61,7 +61,7 @@ public static async Task Create(DirectoryMember model, Conexión { // Ya existen los registros. - context.DataBase.Attach(model.Account); + context.DataBase.Attach(model.Identity); context.DataBase.Attach(model.Directory); // Agregar el elemento. @@ -91,7 +91,7 @@ public static async Task Create(DirectoryMember model, Conexión /// /// Id de la cuenta. /// Contexto de conexión. - public static async Task> ReadAll(int accountId, Conexión context) + public static async Task> ReadAll(int identityId, Conexión context) { // Ejecución @@ -100,11 +100,11 @@ public static async Task> ReadAll(int accountId // Directorios. var directories = await (from directory in context.DataBase.DirectoryMembers - where directory.AccountId == accountId + where directory.IdentityId == identityId select new DirectoryMember { Rol = directory.Rol, - DirectoryId = directory.AccountId, + DirectoryId = directory.DirectoryId, Directory = new() { ID = directory.Directory.ID, @@ -118,7 +118,7 @@ public static async Task> ReadAll(int accountId Type = directory.Directory.Identity.Type } }, - AccountId = accountId + IdentityId = directory.IdentityId }).ToListAsync(); return new(Responses.Success, directories); diff --git a/LIN.Identity/Data/Organizations/Members.cs b/LIN.Identity/Data/Organizations/Members.cs index 58b529e..a0a0da6 100644 --- a/LIN.Identity/Data/Organizations/Members.cs +++ b/LIN.Identity/Data/Organizations/Members.cs @@ -91,7 +91,7 @@ public static async Task> Create(AccountModel data // Miembro del directorio. var memberOnDirectory = new DirectoryMember { - Account = data, + Identity= data.Identity, Directory = new() { ID = organization.DirectoryId diff --git a/LIN.Identity/Data/Organizations/Organizations.cs b/LIN.Identity/Data/Organizations/Organizations.cs index 028135d..b5fb21f 100644 --- a/LIN.Identity/Data/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Organizations/Organizations.cs @@ -80,7 +80,6 @@ public static async Task> Create(Organization Rol = account.Rol, Perfil = account.Perfil, OrganizationAccess = null, - DirectoryMembers = [], IdentityId = 0, }; @@ -138,7 +137,7 @@ public static async Task> Create(Organization // Miembros del directorio. org.Directory.Members.Add(new() { - Account = account, + Identity = account.Identity, Directory = org.Directory, Rol = DirectoryRoles.Administrator }); diff --git a/LIN.Identity/Data/Policies.cs b/LIN.Identity/Data/Policies.cs index 2ad72f1..9101bb2 100644 --- a/LIN.Identity/Data/Policies.cs +++ b/LIN.Identity/Data/Policies.cs @@ -94,7 +94,7 @@ public static async Task> ReadAll(int id, Conexión { var policies = await (from directory in context.DataBase.DirectoryMembers - where directory.AccountId == id + where directory.IdentityId == id join policie in context.DataBase.Policies on directory.DirectoryId equals policie.DirectoryId select policie).ToListAsync(); diff --git a/LIN.Identity/Queries/Accounts.cs b/LIN.Identity/Queries/Accounts.cs index c711f5b..06a88ed 100644 --- a/LIN.Identity/Queries/Accounts.cs +++ b/LIN.Identity/Queries/Accounts.cs @@ -179,7 +179,6 @@ private static IQueryable BuildModel(IQueryable quer { ID = account.ID, Rol = account.Rol, - DirectoryMembers = null!, IdentityId = account.Identity.Id, Insignia = account.Insignia, Estado = account.Estado, diff --git a/LIN.Identity/Services/Iam/Applications.cs b/LIN.Identity/Services/Iam/Applications.cs index 72c2714..a1950b5 100644 --- a/LIN.Identity/Services/Iam/Applications.cs +++ b/LIN.Identity/Services/Iam/Applications.cs @@ -13,6 +13,40 @@ public static class Applications public static async Task> ValidateAccess(int account, int app) { + + // Conexión. + var (context, contextKey) = Conexión.GetOneConnection(); + + + var identity = (from a in context.DataBase.Accounts + where a.ID == account + select a.IdentityId).FirstOrDefault(); + + + + + + List final = new List(); + + + + await A(identity, final); + + + + var mapp = final.Select(t => t.DirectoryId).ToList(); + + + var policies = (from p in context.DataBase.Policies + where mapp.Contains(p.DirectoryId) + select p).Distinct(); + + var q = policies.ToQueryString(); + + + var ssss = policies.ToList(); + + // Obtiene el recurso. var resource = await Data.Applications.Read(app); @@ -25,14 +59,6 @@ public static async Task> ValidateAccess(int account, Model = IamLevels.NotAccess }; - // Si es admin del recurso / creador. - if (resource.Model.AccountID == account) - return new() - { - Response = Responses.Success, - Model = IamLevels.Privileged - }; - // App publica. if (resource.Model.DirectoryId <= 0) @@ -45,36 +71,88 @@ public static async Task> ValidateAccess(int account, } - var (context, contextKey) = Conexión.GetOneConnection(); - var directory = (from m in context.DataBase.DirectoryMembers - where m.AccountId == account - && m.DirectoryId == resource.Model.DirectoryId - select m).FirstOrDefault(); - context.CloseActions(contextKey); - if (directory == null) - { - return new() - { - Response = Responses.NotRows, - Model = IamLevels.NotAccess, - Message = "No tienes acceso a este recurso/app." - }; - } + + + + + + + return new() { - Response = Responses.Success, - Model = (directory.Rol == DirectoryRoles.Administrator) ? IamLevels.Privileged : IamLevels.Visualizer + Response = Responses.NotRows, + Model = IamLevels.NotAccess, + Message = "TESTING." }; + + + + context.CloseActions(contextKey); + + + //if (directory == null) + //{ + // return new() + // { + // Response = Responses.NotRows, + // Model = IamLevels.NotAccess, + // Message = "No tienes acceso a este recurso/app." + // }; + //} + + + //return new() + //{ + // Response = Responses.Success, + // Model = (directory.Rol == DirectoryRoles.Administrator) ? IamLevels.Privileged : IamLevels.Visualizer + //}; + + } + + + + static async Task A(int identidad, List final, Conexión context) + { + + + var x = from DM in context.DataBase.DirectoryMembers + where DM.IdentityId == identidad + select new DirectoryMember + { + DirectoryId = DM.DirectoryId, + Directory = new() + { + ID = DM.Directory.ID, + Identity = new() + { + Id = DM.Directory.Identity.Id + } + } + }; + + + if (x.Any()) + { + var ss = x.ToList(); + final.AddRange(ss); + + foreach (var member in ss) + { + await A(member.Directory.Identity.Id, final, context); + } + + } + } -} \ No newline at end of file +} diff --git a/LIN.Identity/Services/Jwt.cs b/LIN.Identity/Services/Jwt.cs index cb897b8..9ef4fd4 100644 --- a/LIN.Identity/Services/Jwt.cs +++ b/LIN.Identity/Services/Jwt.cs @@ -47,6 +47,7 @@ internal static string Generate(AccountModel user, int appID) new Claim(ClaimTypes.NameIdentifier, user.Identity.Unique), new Claim(ClaimTypes.Role, ((int)user.Rol).ToString()), new Claim(ClaimTypes.UserData, (user.OrganizationAccess?.Organization.ID).ToString() ?? ""), + new Claim(ClaimTypes.GroupSid, (user.Identity.Id).ToString() ?? ""), new Claim(ClaimTypes.Authentication, appID.ToString()) }; @@ -103,10 +104,9 @@ internal static (bool isValid, string user, int userID, int orgID, int appID) Va // _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.PrimarySid)?.Value, out var id); - _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.UserData)?.Value, out var orgID); - _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Authentication)?.Value, out var appID); + _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.GroupSid)?.Value, out var identityId); // Devuelve una respuesta exitosa diff --git a/LIN.Identity/Validations/Account.cs b/LIN.Identity/Validations/Account.cs index 38e4c1d..d27e572 100644 --- a/LIN.Identity/Validations/Account.cs +++ b/LIN.Identity/Validations/Account.cs @@ -23,7 +23,7 @@ public static AccountModel Process(AccountModel modelo) Type = IdentityTypes.Account, Unique = modelo.Identity.Unique }, - DirectoryMembers = [], + IdentityId = 0, Visibilidad = modelo.Visibilidad, Contraseña = modelo.Contraseña = EncryptClass.Encrypt(modelo.Contraseña), diff --git a/LIN.Identity/Validations/AccountPassword.cs b/LIN.Identity/Validations/AccountPassword.cs index e97d0d0..d63fe5c 100644 --- a/LIN.Identity/Validations/AccountPassword.cs +++ b/LIN.Identity/Validations/AccountPassword.cs @@ -18,7 +18,7 @@ public static async Task ValidatePassword(int account, string password) // Política. var policy = await (from directory in context.DataBase.DirectoryMembers - where directory.AccountId == account + where directory.IdentityId == account join policie in context.DataBase.Policies on directory.DirectoryId equals policie.DirectoryId where policie.Type == PolicyTypes.PasswordLength From 383e1b4417c25d5acabe2eec4d680d61f6d3e167 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 11 Dec 2023 14:30:39 -0500 Subject: [PATCH 044/178] Solucion de error al validar politicas externas al directorios de primer nivel --- .../Accounts/AccountSecurityController.cs | 2 +- .../AuthenticationController.cs | 9 -- LIN.Identity/Queries/Directories.cs | 51 ++++++++ LIN.Identity/Services/Iam/Applications.cs | 117 +++--------------- LIN.Identity/Validations/AccountPassword.cs | 22 ++-- 5 files changed, 80 insertions(+), 121 deletions(-) create mode 100644 LIN.Identity/Queries/Directories.cs diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs index 73ce71b..b56bc59 100644 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -87,7 +87,7 @@ public async Task UpdatePassword([FromHeader] int account, [Fr }; // Validar políticas de contraseña. - var result = await AccountPassword.ValidatePassword(account, newPassword); + var result = await AccountPassword.ValidatePassword(actualData.Model.IdentityId, newPassword); // Invalido por políticas if (!result) diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index 88dde2c..c464d95 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -122,13 +122,4 @@ public async Task> LoginWithToken([FromHeader] } - - - - [HttpGet("Hi")] - public async Task Get([FromHeader]int account) - { - var iam = await Services.Iam.Applications.ValidateAccess(account, 0); - return iam; - } } \ No newline at end of file diff --git a/LIN.Identity/Queries/Directories.cs b/LIN.Identity/Queries/Directories.cs new file mode 100644 index 0000000..fd0b7fe --- /dev/null +++ b/LIN.Identity/Queries/Directories.cs @@ -0,0 +1,51 @@ +namespace LIN.Identity.Queries; + +public class Directories +{ + + + + public static async Task> GetDirectories(int identidad) + { + List identities = []; + List directories = []; + + var (context, contextKey) = Conexión.GetOneConnection(); + + await GetDirectories(identidad, identities, context, directories); + + context.CloseActions(contextKey); + return directories; + } + + + + public static async Task GetDirectories(int identidad, List final, Conexión context, List directories) + { + + + // Consulta. + var query = from DM in context.DataBase.DirectoryMembers + where DM.IdentityId == identidad + && !final.Contains(DM.Directory.IdentityId) + select new + { + Identity = DM.Directory.Identity.Id, + Directory = DM.Directory.ID + }; + + // Si hay elementos. + if (query.Any()) + { + var local = query.ToList(); + final.AddRange(local.Select(t => t.Identity)); + directories.AddRange(local.Select(t => t.Directory)); + + foreach (var id in local) + await GetDirectories(id.Identity, final, context, directories); + + } + + } + +} diff --git a/LIN.Identity/Services/Iam/Applications.cs b/LIN.Identity/Services/Iam/Applications.cs index a1950b5..52f9a13 100644 --- a/LIN.Identity/Services/Iam/Applications.cs +++ b/LIN.Identity/Services/Iam/Applications.cs @@ -13,40 +13,6 @@ public static class Applications public static async Task> ValidateAccess(int account, int app) { - - // Conexión. - var (context, contextKey) = Conexión.GetOneConnection(); - - - var identity = (from a in context.DataBase.Accounts - where a.ID == account - select a.IdentityId).FirstOrDefault(); - - - - - - List final = new List(); - - - - await A(identity, final); - - - - var mapp = final.Select(t => t.DirectoryId).ToList(); - - - var policies = (from p in context.DataBase.Policies - where mapp.Contains(p.DirectoryId) - select p).Distinct(); - - var q = policies.ToQueryString(); - - - var ssss = policies.ToList(); - - // Obtiene el recurso. var resource = await Data.Applications.Read(app); @@ -73,86 +39,35 @@ where mapp.Contains(p.DirectoryId) + var (context, contextKey) = Conexión.GetOneConnection(); + var identity = (from a in context.DataBase.Accounts + where a.ID == account + select a.IdentityId).FirstOrDefault(); + var directories = await Queries.Directories.GetDirectories(identity); - - - - - - + // Tiene acceso. + if (directories.Contains(resource.Model.DirectoryId)) + { + return new() + { + Model = IamLevels.Visualizer, + Response = Responses.Success + }; + } return new() { - Response = Responses.NotRows, Model = IamLevels.NotAccess, - Message = "TESTING." + Response = Responses.Success }; - - - - context.CloseActions(contextKey); - - - //if (directory == null) - //{ - // return new() - // { - // Response = Responses.NotRows, - // Model = IamLevels.NotAccess, - // Message = "No tienes acceso a este recurso/app." - // }; - //} - - - //return new() - //{ - // Response = Responses.Success, - // Model = (directory.Rol == DirectoryRoles.Administrator) ? IamLevels.Privileged : IamLevels.Visualizer - //}; - } - static async Task A(int identidad, List final, Conexión context) - { - - - var x = from DM in context.DataBase.DirectoryMembers - where DM.IdentityId == identidad - select new DirectoryMember - { - DirectoryId = DM.DirectoryId, - Directory = new() - { - ID = DM.Directory.ID, - Identity = new() - { - Id = DM.Directory.Identity.Id - } - } - }; - - - if (x.Any()) - { - var ss = x.ToList(); - final.AddRange(ss); - - foreach (var member in ss) - { - await A(member.Directory.Identity.Id, final, context); - } - - } - - } - - -} +} \ No newline at end of file diff --git a/LIN.Identity/Validations/AccountPassword.cs b/LIN.Identity/Validations/AccountPassword.cs index d63fe5c..cf61a70 100644 --- a/LIN.Identity/Validations/AccountPassword.cs +++ b/LIN.Identity/Validations/AccountPassword.cs @@ -10,26 +10,28 @@ public class AccountPassword /// /// Id de la cuenta. /// Contraseña a validar. - public static async Task ValidatePassword(int account, string password) + public static async Task ValidatePassword(int identity, string password) { // Contexto. var (context, contextKey) = Conexión.GetOneConnection(); + // Directorios. + var directories = await Queries.Directories.GetDirectories(identity); + // Política. - var policy = await (from directory in context.DataBase.DirectoryMembers - where directory.IdentityId == account - join policie in context.DataBase.Policies - on directory.DirectoryId equals policie.DirectoryId - where policie.Type == PolicyTypes.PasswordLength - orderby policie.Creation + var policy = await (from p in context.DataBase.Policies + where p.Type == PolicyTypes.PasswordLength + where directories.Contains(p.DirectoryId) + orderby p.Creation select new PolicyModel { - Id = policie.Id, - ValueJson = policie.ValueJson, - Type = policie.Type + Id = p.Id, + ValueJson = p.ValueJson, + Type = p.Type }).LastOrDefaultAsync(); + // Política no existe. if (policy == null) return true; From 9f6edd605693da293c9b282d98085b4bc85cb902 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 13 Dec 2023 15:04:50 -0500 Subject: [PATCH 045/178] Policy Controller --- .../Areas/Accounts/AccountController.cs | 1 - .../Areas/Policies/PolicyController.cs | 33 +++++++++ LIN.Identity/Data/Policies.cs | 71 ++++++++++++++++++- LIN.Identity/Queries/Directories.cs | 32 ++++++--- LIN.Identity/Services/Iam/Applications.cs | 2 +- LIN.Identity/Validations/AccountPassword.cs | 2 +- 6 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 LIN.Identity/Areas/Policies/PolicyController.cs diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index a631ff4..eeb691d 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -5,7 +5,6 @@ namespace LIN.Identity.Areas.Accounts; public class AccountController : ControllerBase { - /// /// Crear una cuenta. /// diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs new file mode 100644 index 0000000..6ab095c --- /dev/null +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -0,0 +1,33 @@ +namespace LIN.Identity.Areas.Organizations; + + +[Route("policies")] +public class PolicyController : ControllerBase +{ + + + /// + /// Valida el acceso a un permiso de una identidad. + /// + /// ID de la identidad + /// ID de la política de permisos + [HttpGet("access")] + public async Task> ReadAll([FromQuery] int identity, [FromQuery] int policy) + { + + // Validar parámetros. + if (identity <= 0 || policy <=0) + return new() + { + Message = "Parámetros inválidos.", + Response = Responses.Unauthorized + }; + + // Respuesta. + return await Data.Policies.ValidatePermission(identity, policy); + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Data/Policies.cs b/LIN.Identity/Data/Policies.cs index 9101bb2..575ea81 100644 --- a/LIN.Identity/Data/Policies.cs +++ b/LIN.Identity/Data/Policies.cs @@ -8,7 +8,7 @@ public class Policies #region Abstracciones - + /// /// Crear nueva política. /// @@ -38,6 +38,21 @@ public static async Task> ReadAll(int id) + /// + /// Valida el acceso a un permiso de una identidad. + /// + /// ID de la identidad + public static async Task> ValidatePermission(int identity, int policy) + { + var (context, contextKey) = Conexión.GetOneConnection(); + + var res = await ValidatePermission(identity, policy, context); + context.CloseActions(contextKey); + return res; + } + + + #endregion @@ -111,4 +126,58 @@ on directory.DirectoryId equals policie.DirectoryId } + + + /// + /// Valida el acceso a un permiso de una identidad. + /// + /// ID de la identidad + /// Contexto de conexión + public static async Task> ValidatePermission(int identity, int policyId, Conexión context) + { + + // Ejecución + try + { + + var (directories, _) = await Queries.Directories.Get(identity); + + + // Consulta. + var policy = await (from p in context.DataBase.Policies + where p.Type == PolicyTypes.Permission + where p.Id == policyId + select p).FirstOrDefaultAsync(); + + // Si no se encontró la política. + if (policy == null) + return new() + { + Response = Responses.NotRows, + Model = false, + Message = "Sin definición." + }; + + + // No tiene acceso. + if (!directories.Contains(policy.DirectoryId)) + return new() + { + Response = Responses.PoliciesNotComplied, + Model = false, + Message = $$"""No tienes permiso para el recurso {{{policyId}}}.""" + }; + + + return new(Responses.Success, true); + + } + catch + { + } + + return new(); + } + + } \ No newline at end of file diff --git a/LIN.Identity/Queries/Directories.cs b/LIN.Identity/Queries/Directories.cs index fd0b7fe..5f1ef98 100644 --- a/LIN.Identity/Queries/Directories.cs +++ b/LIN.Identity/Queries/Directories.cs @@ -5,29 +5,38 @@ public class Directories - public static async Task> GetDirectories(int identidad) + /// + /// Obtiene las identidades y directorios. + /// + /// Identidad base + public static async Task<(List directories, List identities)> Get(int identity) { List identities = []; List directories = []; var (context, contextKey) = Conexión.GetOneConnection(); - await GetDirectories(identidad, identities, context, directories); + await Get(identity, context, identities, directories); context.CloseActions(contextKey); - return directories; + return (directories, identities); } - public static async Task GetDirectories(int identidad, List final, Conexión context, List directories) + /// + /// Obtiene las identidades y directorios. + /// + /// Identidad base + /// Contexto + /// Lista de identidades. + /// Directorios + private static async Task Get(int identityBase, Conexión context, List identities, List directories) { - - // Consulta. var query = from DM in context.DataBase.DirectoryMembers - where DM.IdentityId == identidad - && !final.Contains(DM.Directory.IdentityId) + where DM.IdentityId == identityBase + && !identities.Contains(DM.Directory.IdentityId) select new { Identity = DM.Directory.Identity.Id, @@ -38,14 +47,15 @@ public static async Task GetDirectories(int identidad, List final, Conexió if (query.Any()) { var local = query.ToList(); - final.AddRange(local.Select(t => t.Identity)); + identities.AddRange(local.Select(t => t.Identity)); directories.AddRange(local.Select(t => t.Directory)); foreach (var id in local) - await GetDirectories(id.Identity, final, context, directories); + await Get(id.Identity, context, identities, directories); } } -} + +} \ No newline at end of file diff --git a/LIN.Identity/Services/Iam/Applications.cs b/LIN.Identity/Services/Iam/Applications.cs index 52f9a13..aa5967f 100644 --- a/LIN.Identity/Services/Iam/Applications.cs +++ b/LIN.Identity/Services/Iam/Applications.cs @@ -47,7 +47,7 @@ public static async Task> ValidateAccess(int account, select a.IdentityId).FirstOrDefault(); - var directories = await Queries.Directories.GetDirectories(identity); + var (directories, _) = await Queries.Directories.Get(identity); // Tiene acceso. diff --git a/LIN.Identity/Validations/AccountPassword.cs b/LIN.Identity/Validations/AccountPassword.cs index cf61a70..c27aef6 100644 --- a/LIN.Identity/Validations/AccountPassword.cs +++ b/LIN.Identity/Validations/AccountPassword.cs @@ -17,7 +17,7 @@ public static async Task ValidatePassword(int identity, string password) var (context, contextKey) = Conexión.GetOneConnection(); // Directorios. - var directories = await Queries.Directories.GetDirectories(identity); + var(directories, _) = await Queries.Directories.Get(identity); // Política. var policy = await (from p in context.DataBase.Policies From 4c576a8ec386f13398c9c2a326fabfbc060dad9a Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 13 Dec 2023 15:20:43 -0500 Subject: [PATCH 046/178] Identidad en el token --- .../Areas/Accounts/AccountController.cs | 16 ++++---- .../Areas/Accounts/AccountLogsController.cs | 2 +- .../Accounts/AccountSecurityController.cs | 2 +- .../Applications/ApplicationController.cs | 6 +-- .../AuthenticationController.cs | 2 +- .../Areas/Authentication/IntentsController.cs | 2 +- .../Areas/Directories/DirectoryController.cs | 4 +- .../Areas/Organizations/MemberController.cs | 4 +- .../Organizations/OrganizationController.cs | 2 +- .../Areas/Policies/PolicyController.cs | 27 +++++++++++++ LIN.Identity/Hubs/PasskeyHub.cs | 2 +- LIN.Identity/Services/Iam/Directories.cs | 40 +++++++++++++++++++ LIN.Identity/Services/Jwt.cs | 10 +++-- 13 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 LIN.Identity/Services/Iam/Directories.cs diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index eeb691d..4b6e177 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -66,7 +66,7 @@ public async Task> Read([FromQuery] int id, [F }; // Información del token. - var (isValid, _, user, orgId, _) = Jwt.Validate(token); + var (isValid, _, user, orgId, _, _) = Jwt.Validate(token);; // Token es invalido. if (!isValid) @@ -118,7 +118,7 @@ public async Task> Read([FromQuery] string use }; // Información del token. - var (isValid, _, userId, orgId, _) = Jwt.Validate(token); + var (isValid, _, userId, orgId, _, _) = Jwt.Validate(token);; // Token es invalido. if (!isValid) @@ -170,7 +170,7 @@ public async Task> Search([FromQuery] string p }; // Info del token - var (isValid, _, userId, orgId, _) = Jwt.Validate(token); + var (isValid, _, userId, orgId, _, _) = Jwt.Validate(token);; // Token es invalido if (!isValid) @@ -213,7 +213,7 @@ public async Task> ReadAll([FromBody] List> ReadAll([FromBody] List> FindAll([FromQuery] string pattern, [FromHeader] string token) { - var (isValid, _, _, _, _) = Jwt.Validate(token); + var (isValid, _, _, _, _, _) = Jwt.Validate(token);; if (!isValid) @@ -291,7 +291,7 @@ public async Task Update([FromBody] AccountModel modelo, [From { // Información del token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); + var (isValid, _, userId, _, _, _) = Jwt.Validate(token);; // Es invalido. if (!isValid) @@ -324,7 +324,7 @@ public async Task Update([FromHeader] string token, [FromHeade { // Información del token. - var (isValid, _, id, _, _) = Jwt.Validate(token); + var (isValid, _, id, _, _, _) = Jwt.Validate(token);; // Token es invalido. if (!isValid) @@ -351,7 +351,7 @@ public async Task Update([FromHeader] string token, [FromHeade { // Información del token. - var (isValid, _, id, _, _) = Jwt.Validate(token); + var (isValid, _, id, _, _, _) = Jwt.Validate(token);; // Token es invalido. if (!isValid) diff --git a/LIN.Identity/Areas/Accounts/AccountLogsController.cs b/LIN.Identity/Areas/Accounts/AccountLogsController.cs index 63fe3c7..192d9bb 100644 --- a/LIN.Identity/Areas/Accounts/AccountLogsController.cs +++ b/LIN.Identity/Areas/Accounts/AccountLogsController.cs @@ -15,7 +15,7 @@ public async Task> GetAll([FromHeader] string { // JWT. - var (isValid, _, userId, _, _) = Jwt.Validate(token); + var (isValid, _, userId, _, _, _) = Jwt.Validate(token);; // Validación. if (!isValid) diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs index b56bc59..6a73dfc 100644 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -16,7 +16,7 @@ public async Task Delete([FromHeader] string token) { // Información del token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); + var (isValid, _, userId, _, _, _) = Jwt.Validate(token);; // Si es invalido. if (!isValid) diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs index 57d08a9..d3f21e0 100644 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -16,7 +16,7 @@ public async Task Create([FromBody] ApplicationModel applica { // Información del token. - var (isValid, _, userID, _, _) = Jwt.Validate(token); + var (isValid, _, userID, _, _, _) = Jwt.Validate(token);; // Si el token es invalido. if (!isValid) @@ -55,7 +55,7 @@ public async Task> GetAll([FromHeader] str { // Información del token. - var (isValid, _, userID, _, _) = Jwt.Validate(token); + var (isValid, _, userID, _, _, _) = Jwt.Validate(token);; // Si el token es invalido. if (!isValid) @@ -85,7 +85,7 @@ public async Task> InsertAllow([FromHeader] string tok { // Información del token. - var (isValid, _, userId, _, _) = Jwt.Validate(token); + var (isValid, _, userId, _, _, _) = Jwt.Validate(token);; // Si el token es invalido. if (!isValid) diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index c464d95..de5e7f5 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -92,7 +92,7 @@ public async Task> LoginWithToken([FromHeader] { // Información del token de acceso. - var (isValid, _, user, _, _) = Jwt.Validate(token); + var (isValid, _, user, _, _, _) = Jwt.Validate(token);; // Si el token es invalido. if (!isValid) diff --git a/LIN.Identity/Areas/Authentication/IntentsController.cs b/LIN.Identity/Areas/Authentication/IntentsController.cs index 128abe5..af76a01 100644 --- a/LIN.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Identity/Areas/Authentication/IntentsController.cs @@ -16,7 +16,7 @@ public HttpReadAllResponse GetAll([FromHeader] string token) { // Info del token - var (isValid, user, _, _, _) = Jwt.Validate(token); + var (isValid, user, _, _, _, _) = Jwt.Validate(token);; // Si el token es invalido if (!isValid) diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index 37dcaa0..0980b70 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -19,7 +19,7 @@ public async Task> Read([FromQuery] int id, return new(Responses.InvalidParam); // Información del token. - var (isValid, _, user, orgId, _) = Jwt.Validate(token); + var (isValid, _, user, orgId, _, _) = Jwt.Validate(token);; // Token es invalido. if (!isValid) @@ -55,7 +55,7 @@ public async Task> ReadAll([FromHeader] str { // Información del token. - var (isValid, _, user, _, _) = Jwt.Validate(token); + var (isValid, _, user, _, _, _) = Jwt.Validate(token);; // Token es invalido. if (!isValid) diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index 0df5897..05fdc83 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -38,7 +38,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr modelo.Contraseña = EncryptClass.Encrypt(password); // Validación del token - var (isValid, _, userID, _, _) = Jwt.Validate(token); + var (isValid, _, userID, _, _, _) = Jwt.Validate(token);; // Token es invalido if (!isValid) @@ -134,7 +134,7 @@ public async Task> ReadAll([FromHeader] string { // Información del token. - var (isValid, _, _, orgID, _) = Jwt.Validate(token); + var (isValid, _, _, orgID, _, _) = Jwt.Validate(token);; // Si el token es invalido. if (!isValid) diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index 2b9a8c0..b41f29b 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -92,7 +92,7 @@ public async Task> ReadOneByID([FromQuery return new(Responses.InvalidParam); // Validar el token - var (isValid, _, _, orgID, _) = Jwt.Validate(token); + var (isValid, _, _, orgID, _, _) = Jwt.Validate(token);; if (!isValid) diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index 6ab095c..47276bc 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -6,6 +6,33 @@ public class PolicyController : ControllerBase { + /// + /// Valida el acceso a un permiso de una identidad. + /// + /// ID de la identidad + /// ID de la política de permisos + [HttpPost] + public async Task Create([FromBody] PolicyModel policy, [FromHeader] string token) + { + + // Validación del token + var (isValid, _, _, _, _, _, _) = Jwt.Validate(token);; + + // Token es invalido + if (!isValid) + { + return new CreateResponse + { + Message = "Token invalido.", + Response = Responses.Unauthorized + }; + } + + + } + + + /// /// Valida el acceso a un permiso de una identidad. /// diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs index a716f3c..8d77a3e 100644 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ b/LIN.Identity/Hubs/PasskeyHub.cs @@ -153,7 +153,7 @@ public async Task ReceiveRequest(PassKeyModel modelo) { // Validación del token recibido - var (isValid, userUnique, userID, orgID, _) = Jwt.Validate(modelo.Token); + var (isValid, userUnique, userID, orgID, _, _) = Jwt.Validate(modelo.Token); // No es valido el token if (!isValid || modelo.Status != PassKeyStatus.Success) diff --git a/LIN.Identity/Services/Iam/Directories.cs b/LIN.Identity/Services/Iam/Directories.cs new file mode 100644 index 0000000..ef98bec --- /dev/null +++ b/LIN.Identity/Services/Iam/Directories.cs @@ -0,0 +1,40 @@ +namespace LIN.Identity.Services.Iam; + + +public static class Directories +{ + + + + + + public static async Task> ValidateAccess(int identity, int directory) + { + + + var (_, identities) = await Queries.Directories.Get(identity); + + + + var (context, contextKey) = Conexión.GetOneConnection(); + + + + var dirM = (from DM in context.DataBase.DirectoryMembers + where DM.DirectoryId == directory + && identities.Contains(DM.IdentityId) + select DM).GroupBy(b=>b.Rol).ToList(); + + + + return new() + { + Model = IamLevels.NotAccess + }; + + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Services/Jwt.cs b/LIN.Identity/Services/Jwt.cs index 9ef4fd4..a6ce96b 100644 --- a/LIN.Identity/Services/Jwt.cs +++ b/LIN.Identity/Services/Jwt.cs @@ -67,7 +67,7 @@ internal static string Generate(AccountModel user, int appID) /// Valida un JSON Web token /// /// Token a validar - internal static (bool isValid, string user, int userID, int orgID, int appID) Validate(string token) + internal static (bool isValid, string user, int userID, int orgID, int appID, int identity) Validate(string token) { try { @@ -75,7 +75,7 @@ internal static (bool isValid, string user, int userID, int orgID, int appID) Va // Comprobación if (string.IsNullOrWhiteSpace(token)) - return (false, string.Empty, 0, 0, 0); + return (false, string.Empty, 0, 0, 0, 0); // Configurar la clave secreta var key = Encoding.ASCII.GetBytes(JwtKey); @@ -110,7 +110,7 @@ internal static (bool isValid, string user, int userID, int orgID, int appID) Va // Devuelve una respuesta exitosa - return (true, user ?? string.Empty, id, orgID, appID); + return (true, user ?? string.Empty, id, orgID, appID, identityId); } catch (SecurityTokenException) @@ -121,9 +121,11 @@ internal static (bool isValid, string user, int userID, int orgID, int appID) Va } catch { } - return (false, string.Empty, 0, 0, 0); + return (false, string.Empty, 0, 0, 0, 0); } + + } \ No newline at end of file From b7d9833eb534c4494c7bf7daa890fd79cdfe5f97 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 13 Dec 2023 16:00:19 -0500 Subject: [PATCH 047/178] Validacion Iam en directorios --- .../Areas/Policies/PolicyController.cs | 44 ++++++++++++++++--- LIN.Identity/Queries/Directories.cs | 2 +- LIN.Identity/Services/Iam/Directories.cs | 16 +++++-- 3 files changed, 51 insertions(+), 11 deletions(-) diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index 47276bc..62ad856 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -7,28 +7,58 @@ public class PolicyController : ControllerBase /// - /// Valida el acceso a un permiso de una identidad. + /// Crear política. /// - /// ID de la identidad - /// ID de la política de permisos + /// Modelo. + /// Token de acceso. [HttpPost] public async Task Create([FromBody] PolicyModel policy, [FromHeader] string token) { + // Validar parámetros. + if (policy == null || string.IsNullOrEmpty(token) || policy.DirectoryId <= 0) + return new CreateResponse + { + Message = "Parámetros inválidos.", + Response = Responses.InvalidParam + }; + // Validación del token - var (isValid, _, _, _, _, _, _) = Jwt.Validate(token);; + var (isValid, _, _, _, _, identity) = Jwt.Validate(token); ; // Token es invalido if (!isValid) - { return new CreateResponse { Message = "Token invalido.", Response = Responses.Unauthorized }; - } + // Acceso IAM. + var iam = await Services.Iam.Directories.ValidateAccess(identity, policy.DirectoryId); + + // SI no tiene permisos de modificación. + if (iam.Model != IamLevels.Privileged) + return new CreateResponse + { + Message = "No tienes permiso para modificar este directorio.", + Response = Responses.Unauthorized + }; + + + return await Data.Policies.Create(new() + { + Creation = DateTime.Now, + Directory = new() + { + ID = policy.DirectoryId, + }, + Id = 0, + Type = policy.Type, + ValueJson = policy.ValueJson, + }); + } @@ -43,7 +73,7 @@ public async Task> ReadAll([FromQuery] int identity, [ { // Validar parámetros. - if (identity <= 0 || policy <=0) + if (identity <= 0 || policy <= 0) return new() { Message = "Parámetros inválidos.", diff --git a/LIN.Identity/Queries/Directories.cs b/LIN.Identity/Queries/Directories.cs index 5f1ef98..3a4e82f 100644 --- a/LIN.Identity/Queries/Directories.cs +++ b/LIN.Identity/Queries/Directories.cs @@ -11,7 +11,7 @@ public class Directories /// Identidad base public static async Task<(List directories, List identities)> Get(int identity) { - List identities = []; + List identities = [identity]; List directories = []; var (context, contextKey) = Conexión.GetOneConnection(); diff --git a/LIN.Identity/Services/Iam/Directories.cs b/LIN.Identity/Services/Iam/Directories.cs index ef98bec..8fcb2e0 100644 --- a/LIN.Identity/Services/Iam/Directories.cs +++ b/LIN.Identity/Services/Iam/Directories.cs @@ -19,17 +19,27 @@ public static async Task> ValidateAccess(int identity var (context, contextKey) = Conexión.GetOneConnection(); - + // Encuentra el acceso en order Admin -> User. var dirM = (from DM in context.DataBase.DirectoryMembers where DM.DirectoryId == directory && identities.Contains(DM.IdentityId) - select DM).GroupBy(b=>b.Rol).ToList(); + select DM).OrderByDescending(t => t.Rol).FirstOrDefault(); + if (dirM == null) + return new() + { + Model = IamLevels.NotAccess + }; + if (dirM.Rol == DirectoryRoles.Administrator) + return new() + { + Model = IamLevels.Privileged + }; return new() { - Model = IamLevels.NotAccess + Model = IamLevels.Visualizer }; From eec40dd972775e75793fd31c1655134450a7a9a0 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 13 Dec 2023 16:18:22 -0500 Subject: [PATCH 048/178] actualizacion en controller de Policy --- LIN.Identity/Areas/Policies/PolicyController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index 62ad856..1510f48 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -69,7 +69,7 @@ public async Task Create([FromBody] PolicyModel policy, [Fro /// ID de la identidad /// ID de la política de permisos [HttpGet("access")] - public async Task> ReadAll([FromQuery] int identity, [FromQuery] int policy) + public async Task> ValidatePermissions([FromQuery] int identity, [FromQuery] int policy) { // Validar parámetros. From 97e258d2a0e640015c71e712e2734d84bb96dbc8 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 13 Dec 2023 23:26:29 -0500 Subject: [PATCH 049/178] Correccion en DirectoryController --- .../Data/Directories/DirectoryMembers.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/LIN.Identity/Data/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Directories/DirectoryMembers.cs index f3bbe60..e757418 100644 --- a/LIN.Identity/Data/Directories/DirectoryMembers.cs +++ b/LIN.Identity/Data/Directories/DirectoryMembers.cs @@ -98,27 +98,29 @@ public static async Task> ReadAll(int identityI try { + var (_, identities) = await Queries.Directories.Get(identityId); + // Directorios. - var directories = await (from directory in context.DataBase.DirectoryMembers - where directory.IdentityId == identityId + var directories = await (from directoryMember in context.DataBase.DirectoryMembers + where identities.Contains(directoryMember.IdentityId) select new DirectoryMember { - Rol = directory.Rol, - DirectoryId = directory.DirectoryId, + Rol = directoryMember.Rol, + DirectoryId = directoryMember.DirectoryId, Directory = new() { - ID = directory.Directory.ID, - Creación = directory.Directory.Creación, - Nombre = directory.Directory.Nombre, - IdentityId = directory.Directory.IdentityId, + ID = directoryMember.Directory.ID, + Creación = directoryMember.Directory.Creación, + Nombre = directoryMember.Directory.Nombre, + IdentityId = directoryMember.Directory.IdentityId, Identity = new() { - Id = directory.Directory.Identity.Id, - Unique = directory.Directory.Identity.Unique, - Type = directory.Directory.Identity.Type + Id = directoryMember.Directory.Identity.Id, + Unique = directoryMember.Directory.Identity.Unique, + Type = directoryMember.Directory.Identity.Type } }, - IdentityId = directory.IdentityId + IdentityId = directoryMember.IdentityId }).ToListAsync(); return new(Responses.Success, directories); From 4a356fc885885941a7e3dcd45325e69b58c5abb6 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 14 Dec 2023 10:36:34 -0500 Subject: [PATCH 050/178] Solucion de errores --- .../Areas/Directories/DirectoryController.cs | 6 +-- LIN.Identity/Data/Directories/Directories.cs | 4 +- .../Data/Directories/DirectoryMembers.cs | 6 +++ LIN.Identity/Queries/Accounts.cs | 41 ++++++++++--------- 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index 0980b70..b8dd7d2 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -11,7 +11,7 @@ public class DirectoryController : ControllerBase /// ID del directorio. /// Token de acceso. [HttpGet("read/id")] - public async Task> Read([FromQuery] int id, [FromHeader] string token) + public async Task> Read([FromQuery] int id, [FromHeader] string token) { // Id es invalido. @@ -23,7 +23,7 @@ public async Task> Read([FromQuery] int id, // Token es invalido. if (!isValid) - return new ReadOneResponse() + return new ReadOneResponse() { Response = Responses.Unauthorized, Message = "Token invalido." @@ -34,7 +34,7 @@ public async Task> Read([FromQuery] int id, // Si es erróneo if (response.Response != Responses.Success) - return new ReadOneResponse() + return new ReadOneResponse() { Response = response.Response }; diff --git a/LIN.Identity/Data/Directories/Directories.cs b/LIN.Identity/Data/Directories/Directories.cs index 262f483..87d9a93 100644 --- a/LIN.Identity/Data/Directories/Directories.cs +++ b/LIN.Identity/Data/Directories/Directories.cs @@ -30,7 +30,7 @@ public static async Task Create(DirectoryModel directory) /// Obtener un directorio. /// /// Id del directorio. - public static async Task> Read(int id) + public static async Task> Read(int id) { // Obtiene la conexión @@ -92,7 +92,7 @@ public static async Task Create(DirectoryModel model, Conexión /// /// Id del directorio. /// Contexto de conexión. - public static async Task> Read(int id, Conexión context) + public static async Task> Read(int id, Conexión context) { // Ejecución diff --git a/LIN.Identity/Data/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Directories/DirectoryMembers.cs index e757418..bf4347e 100644 --- a/LIN.Identity/Data/Directories/DirectoryMembers.cs +++ b/LIN.Identity/Data/Directories/DirectoryMembers.cs @@ -120,6 +120,12 @@ where identities.Contains(directoryMember.IdentityId) Type = directoryMember.Directory.Identity.Type } }, + Identity = new() + { + Id = directoryMember.Identity.Id, + Unique = directoryMember.Identity.Unique, + Type = directoryMember.Identity.Type + }, IdentityId = directoryMember.IdentityId }).ToListAsync(); diff --git a/LIN.Identity/Queries/Accounts.cs b/LIN.Identity/Queries/Accounts.cs index 06a88ed..369b568 100644 --- a/LIN.Identity/Queries/Accounts.cs +++ b/LIN.Identity/Queries/Accounts.cs @@ -130,21 +130,19 @@ where account.Identity.Unique.Contains(pattern) - public static IQueryable GetDirectory(int id, Conexión context) + public static IQueryable GetDirectory(int id, Conexión context) { // Query general - IQueryable accounts; - - - var directories = from directory in context.DataBase.Directories - where directory.ID == id - select directory; + IQueryable accounts; + var directory = from dm in context.DataBase.DirectoryMembers + where dm.Directory.ID == id + select dm; // Armar el modelo - accounts = BuildModel(directories); + accounts = BuildModel(directory); // Retorno return accounts; @@ -263,22 +261,27 @@ private static IQueryable BuildModel(IQueryable quer /// /// Query base /// Filtros - private static IQueryable BuildModel(IQueryable query) + private static IQueryable BuildModel(IQueryable query) { return from d in query - select new DirectoryModel + select new DirectoryMember { - Creación = d.Creación, - ID = d.ID, - Identity = new() + Rol = d.Rol, + IdentityId = d.IdentityId, + Directory = new() { - Id = d.Identity.Id, - Type = d.Identity.Type, - Unique = d.Identity.Unique - }, - IdentityId = d.Identity.Id, - Nombre = d.Nombre + Nombre = d.Directory.Nombre, + Creación = d.Directory.Creación, + ID = d.Directory.ID, + Identity = new() + { + Id = d.Directory.Identity.Id, + Type = d.Directory.Identity.Type, + Unique = d.Directory.Identity.Unique + }, + IdentityId = d.Directory.Identity.Id + } }; } From 32739d8b462ce69ed1317c7c1dd41b111eeb9d59 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 14 Dec 2023 10:39:35 -0500 Subject: [PATCH 051/178] Validacion Iam en obtener un directorio. --- .../Areas/Directories/DirectoryController.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index b8dd7d2..85fd783 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -19,7 +19,7 @@ public async Task> Read([FromQuery] int id, return new(Responses.InvalidParam); // Información del token. - var (isValid, _, user, orgId, _, _) = Jwt.Validate(token);; + var (isValid, _, _, _, _, identity) = Jwt.Validate(token);; // Token es invalido. if (!isValid) @@ -29,6 +29,18 @@ public async Task> Read([FromQuery] int id, Message = "Token invalido." }; + + // Acceso IAM. + var iam = await Services.Iam.Directories.ValidateAccess(identity, id); + + // Validar Iam. + if (iam.Model == IamLevels.NotAccess) + return new ReadOneResponse() + { + Message = "No tienes acceso a este directorio, si crees que es un error comunícate con tu administrador.", + Response = Responses.Unauthorized + }; + // Obtiene el usuario. var response = await Data.Directories.Read(id); From 99a4564bc7ec48a9c738e59c153dd12ddf1ca355 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 14 Dec 2023 11:32:01 -0500 Subject: [PATCH 052/178] Correcciones --- LIN.Identity/Areas/Directories/DirectoryController.cs | 5 +++-- LIN.Identity/Data/Directories/Directories.cs | 10 ++++++---- LIN.Identity/Queries/Accounts.cs | 6 +++++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index 85fd783..5dab87c 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -9,9 +9,10 @@ public class DirectoryController : ControllerBase /// Obtener un directorio. /// /// ID del directorio. + /// Identidad de contexto. /// Token de acceso. [HttpGet("read/id")] - public async Task> Read([FromQuery] int id, [FromHeader] string token) + public async Task> Read([FromQuery] int id, [FromQuery] int findIdentity, [FromHeader] string token) { // Id es invalido. @@ -42,7 +43,7 @@ public async Task> Read([FromQuery] int id, }; // Obtiene el usuario. - var response = await Data.Directories.Read(id); + var response = await Data.Directories.Read(id, findIdentity); // Si es erróneo if (response.Response != Responses.Success) diff --git a/LIN.Identity/Data/Directories/Directories.cs b/LIN.Identity/Data/Directories/Directories.cs index 87d9a93..1a5407f 100644 --- a/LIN.Identity/Data/Directories/Directories.cs +++ b/LIN.Identity/Data/Directories/Directories.cs @@ -30,13 +30,14 @@ public static async Task Create(DirectoryModel directory) /// Obtener un directorio. /// /// Id del directorio. - public static async Task> Read(int id) + /// Identidad del contexto (No necesaria).. + public static async Task> Read(int id, int identityContext = 0) { // Obtiene la conexión var (context, connectionKey) = Conexión.GetOneConnection(); - var res = await Read(id, context); + var res = await Read(id,identityContext, context ); context.CloseActions(connectionKey); return res; @@ -91,8 +92,9 @@ public static async Task Create(DirectoryModel model, Conexión /// Obtener un directorio. /// /// Id del directorio. + /// Identidad de contexto (No necesaria). /// Contexto de conexión. - public static async Task> Read(int id, Conexión context) + public static async Task> Read(int id, int identityContext, Conexión context) { // Ejecución @@ -100,7 +102,7 @@ public static async Task> Read(int id, Conexió { // Consulta. - var query = Queries.Accounts.GetDirectory(id, context); + var query = Queries.Accounts.GetDirectory(id, identityContext, context); // Obtiene el usuario var result = await query.FirstOrDefaultAsync(); diff --git a/LIN.Identity/Queries/Accounts.cs b/LIN.Identity/Queries/Accounts.cs index 369b568..e3aef2f 100644 --- a/LIN.Identity/Queries/Accounts.cs +++ b/LIN.Identity/Queries/Accounts.cs @@ -130,7 +130,7 @@ where account.Identity.Unique.Contains(pattern) - public static IQueryable GetDirectory(int id, Conexión context) + public static IQueryable GetDirectory(int id, int identityContext, Conexión context) { // Query general @@ -141,6 +141,9 @@ public static IQueryable GetDirectory(int id, Conexión context where dm.Directory.ID == id select dm; + if (identityContext > 0) + directory = directory.Where(t => t.IdentityId == identityContext); + // Armar el modelo accounts = BuildModel(directory); @@ -155,6 +158,7 @@ public static IQueryable GetDirectory(int id, Conexión context + /// /// Construir la consulta /// From e6d943ea00b3832aa2a26737be48a6064b4a4fc1 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 14 Dec 2023 15:26:51 -0500 Subject: [PATCH 053/178] Nueva ruta --- .../Areas/Directories/DirectoryController.cs | 50 ++++++++++++++ .../Data/Directories/DirectoryMembers.cs | 69 +++++++++++++++++-- LIN.Identity/Services/Iam/Directories.cs | 6 ++ 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index 5dab87c..0617937 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -95,4 +95,54 @@ public async Task> ReadAll([FromHeader] str + /// + /// Obtener los integrantes asociados a un directorio. + /// + /// Token de acceso. + /// ID del directorio. + [HttpGet("read/members")] + public async Task> ReadAll([FromHeader] string token, [FromQuery] int directory) + { + + // Información del token. + var (isValid, _, user, _, _, identity) = Jwt.Validate(token); ; + + // Token es invalido. + if (!isValid) + return new ReadAllResponse() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + + // Acceso IAM. + var iam = await Services.Iam.Directories.ValidateAccess(identity, directory); + + IEnumerable have = [IamLevels.Privileged, IamLevels.Visualizer]; + + if (!have.Contains(iam.Model)) + return new() + { + Message = "No tienes acceso a este recurso.", + Response = Responses.Unauthorized + }; + + // Obtiene el usuario. + var response = await Data.DirectoryMembers.ReadMembers(directory); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + } \ No newline at end of file diff --git a/LIN.Identity/Data/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Directories/DirectoryMembers.cs index bf4347e..ff0931a 100644 --- a/LIN.Identity/Data/Directories/DirectoryMembers.cs +++ b/LIN.Identity/Data/Directories/DirectoryMembers.cs @@ -27,16 +27,33 @@ public static async Task Create(DirectoryMember directory) /// - /// Obtiene los directorios donde un usuario es integrante. + /// Obtiene los directorios donde una identidad es integrante. /// - /// Id de la cuenta. - public static async Task> ReadAll(int accountId) + /// Id de la identidad. + public static async Task> ReadAll(int identityId) { // Obtiene la conexión var (context, connectionKey) = Conexión.GetOneConnection(); - var res = await ReadAll(accountId, context); + var res = await ReadAll(identityId, context); + context.CloseActions(connectionKey); + return res; + + } + + + /// + /// Obtiene los integrantes de un directorio. + /// + /// Id del directorio. + public static async Task> ReadMembers(int identityId) + { + + // Obtiene la conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + var res = await ReadMembers(identityId, context); context.CloseActions(connectionKey); return res; @@ -87,9 +104,9 @@ public static async Task Create(DirectoryMember model, Conexión /// - /// Obtiene los directorios donde un usuario es integrante. + /// Obtiene los directorios donde una identidad es integrante. /// - /// Id de la cuenta. + /// Id de la identidad. /// Contexto de conexión. public static async Task> ReadAll(int identityId, Conexión context) { @@ -140,4 +157,44 @@ where identities.Contains(directoryMember.IdentityId) } + + /// + /// Obtiene los integrantes de un directorio. + /// + /// Id del directorio. + /// Contexto de conexión. + public static async Task> ReadMembers(int directoryId, Conexión context) + { + + // Ejecución + try + { + + // Directorios. + var directories = await (from directoryMember in context.DataBase.DirectoryMembers + where directoryMember.DirectoryId == directoryId + select new DirectoryMember + { + Rol = directoryMember.Rol, + DirectoryId = directoryMember.DirectoryId, + Identity = new() + { + Id = directoryMember.Identity.Id, + Unique = directoryMember.Identity.Unique, + Type = directoryMember.Identity.Type + }, + IdentityId = directoryMember.IdentityId + }).ToListAsync(); + + return new(Responses.Success, directories); + + } + catch (Exception) + { + } + + return new(); + } + + } \ No newline at end of file diff --git a/LIN.Identity/Services/Iam/Directories.cs b/LIN.Identity/Services/Iam/Directories.cs index 8fcb2e0..e1be994 100644 --- a/LIN.Identity/Services/Iam/Directories.cs +++ b/LIN.Identity/Services/Iam/Directories.cs @@ -37,6 +37,12 @@ public static async Task> ValidateAccess(int identity Model = IamLevels.Privileged }; + if (dirM.Rol == DirectoryRoles.Guest) + return new() + { + Model = IamLevels.Guest + }; + return new() { Model = IamLevels.Visualizer From 8b35361587d79442954c2cecc6b339e5bf89f124 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 14 Dec 2023 15:32:48 -0500 Subject: [PATCH 054/178] Mejoras --- LIN.Identity/Areas/Directories/DirectoryController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index 0617937..c9d0516 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -119,8 +119,10 @@ public async Task> ReadAll([FromHeader] str // Acceso IAM. var iam = await Services.Iam.Directories.ValidateAccess(identity, directory); + // Roles disponibles. IEnumerable have = [IamLevels.Privileged, IamLevels.Visualizer]; + // No tiene un permitido. if (!have.Contains(iam.Model)) return new() { From 41e3e7322f29b62910da6f9c3f7f2609b1cab11d Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 15 Dec 2023 09:55:31 -0500 Subject: [PATCH 055/178] Compatibilidad con Linux --- LIN.Identity/Data/Directories/Directories.cs | 1 - LIN.Identity/Data/Organizations/Members.cs | 1 - .../Data/Organizations/Organizations.cs | 2 - LIN.Identity/LIN.Identity.csproj | 8 ++- LIN.Identity/Services/Configuration.cs | 1 + LIN.Identity/Services/EmailWorker.cs | 1 + LIN.Identity/Services/Image.cs | 65 ++++++++++++++++++- LIN.Identity/Validations/Account.cs | 8 ++- LIN.Identity/Validations/AccountPassword.cs | 3 +- 9 files changed, 79 insertions(+), 11 deletions(-) diff --git a/LIN.Identity/Data/Directories/Directories.cs b/LIN.Identity/Data/Directories/Directories.cs index 1a5407f..d1dc395 100644 --- a/LIN.Identity/Data/Directories/Directories.cs +++ b/LIN.Identity/Data/Directories/Directories.cs @@ -44,7 +44,6 @@ public static async Task> Read(int id, int iden } - #endregion diff --git a/LIN.Identity/Data/Organizations/Members.cs b/LIN.Identity/Data/Organizations/Members.cs index a0a0da6..6e91000 100644 --- a/LIN.Identity/Data/Organizations/Members.cs +++ b/LIN.Identity/Data/Organizations/Members.cs @@ -44,7 +44,6 @@ public static async Task> Create(AccountModel data - /// /// Crea una cuenta en una organización /// diff --git a/LIN.Identity/Data/Organizations/Organizations.cs b/LIN.Identity/Data/Organizations/Organizations.cs index b5fb21f..1c86290 100644 --- a/LIN.Identity/Data/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Organizations/Organizations.cs @@ -168,7 +168,6 @@ public static async Task> Create(Organization - /// /// Obtiene una organización. /// @@ -211,5 +210,4 @@ public static async Task> Read(int id, Conexi - } \ No newline at end of file diff --git a/LIN.Identity/LIN.Identity.csproj b/LIN.Identity/LIN.Identity.csproj index 21d27ac..fa302bd 100644 --- a/LIN.Identity/LIN.Identity.csproj +++ b/LIN.Identity/LIN.Identity.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -7,6 +7,7 @@ + @@ -28,4 +29,9 @@ + + + + + diff --git a/LIN.Identity/Services/Configuration.cs b/LIN.Identity/Services/Configuration.cs index 9ab901d..8bac1fb 100644 --- a/LIN.Identity/Services/Configuration.cs +++ b/LIN.Identity/Services/Configuration.cs @@ -5,6 +5,7 @@ public class Configuration { + private static IConfiguration? Config; private static bool _isStart = false; diff --git a/LIN.Identity/Services/EmailWorker.cs b/LIN.Identity/Services/EmailWorker.cs index 5ae2b4e..f2c3571 100644 --- a/LIN.Identity/Services/EmailWorker.cs +++ b/LIN.Identity/Services/EmailWorker.cs @@ -11,6 +11,7 @@ public class EmailWorker private static string Password { get; set; } = string.Empty; + /// /// Inicia el servicio /// diff --git a/LIN.Identity/Services/Image.cs b/LIN.Identity/Services/Image.cs index e168be0..1fa3559 100644 --- a/LIN.Identity/Services/Image.cs +++ b/LIN.Identity/Services/Image.cs @@ -1,5 +1,6 @@ using System.Drawing; using System.Drawing.Imaging; +using System.Runtime.Versioning; namespace LIN.Identity.Services; @@ -7,6 +8,8 @@ namespace LIN.Identity.Services; public class Image { + + /// /// Comprime una imagen /// @@ -15,6 +18,24 @@ public class Image /// Alto /// Maximo public static byte[] Zip(byte[] originalImage, int width = 50, int height = 50, int max = 1900) + { + if (OperatingSystem.IsWindows()) + return ZipOnWindows(originalImage, width, height, max); + else + return ZipOthers(originalImage, width, height, max); + } + + + + /// + /// Comprime una imagen + /// + /// Original image + /// Ancho + /// Alto + /// Maximo + [SupportedOSPlatform("windows")] + public static byte[] ZipOnWindows(byte[] originalImage, int width = 50, int height = 50, int max = 1900) { try { @@ -43,7 +64,7 @@ public static byte[] Zip(byte[] originalImage, int width = 50, int height = 50, byte[] imagenBytes; using (MemoryStream stream = new()) { - nuevaImagen.Save(stream, ImageFormat.Png); + nuevaImagen.Save(stream, ImageFormat.Jpeg); imagenBytes = stream.ToArray(); } @@ -51,11 +72,51 @@ public static byte[] Zip(byte[] originalImage, int width = 50, int height = 50, image.Dispose(); return imagenBytes; + + } + catch + { + } + return []; + } + + + + /// + /// Comprime una imagen + /// + /// Original image + /// Ancho + /// Alto + /// Maximo + public static byte[] ZipOthers(byte[] originalImage, int width = 100, int height = 100, int max = 1900) + { + try + { + + MemoryStream memoryStream = new(originalImage); + + // Cargar imagen + using Aspose.Imaging.Image pic = Aspose.Imaging.Image.Load(memoryStream); + + // Cambiar el tamaño de la imagen y guardar la imagen redimensionada + + pic.ResizeWidthProportionally(100); + + byte[] imagenBytes; + using MemoryStream stream = new(); + pic.Save(stream); + imagenBytes = stream.ToArray(); + + var ss = Convert.ToBase64String(imagenBytes); + + return imagenBytes; + } catch { } - return Array.Empty(); + return []; } diff --git a/LIN.Identity/Validations/Account.cs b/LIN.Identity/Validations/Account.cs index d27e572..1d93696 100644 --- a/LIN.Identity/Validations/Account.cs +++ b/LIN.Identity/Validations/Account.cs @@ -23,8 +23,9 @@ public static AccountModel Process(AccountModel modelo) Type = IdentityTypes.Account, Unique = modelo.Identity.Unique }, - + IdentityId = 0, + Gender = modelo.Gender, Visibilidad = modelo.Visibilidad, Contraseña = modelo.Contraseña = EncryptClass.Encrypt(modelo.Contraseña), Creación = modelo.Creación = DateTime.Now, @@ -32,12 +33,13 @@ public static AccountModel Process(AccountModel modelo) Birthday = modelo.Birthday, Insignia = modelo.Insignia = AccountBadges.None, Rol = modelo.Rol = AccountRoles.User, - Perfil = modelo.Perfil = modelo.Perfil.Length == 0 + Perfil = modelo.Perfil = modelo.Perfil?.Length == 0 ? File.ReadAllBytes("wwwroot/profile.png") : modelo.Perfil }; - model.Perfil = Image.Zip(model.Perfil); + model.Perfil = Image.Zip(model.Perfil ?? []); + return model; } diff --git a/LIN.Identity/Validations/AccountPassword.cs b/LIN.Identity/Validations/AccountPassword.cs index c27aef6..235aac0 100644 --- a/LIN.Identity/Validations/AccountPassword.cs +++ b/LIN.Identity/Validations/AccountPassword.cs @@ -17,7 +17,7 @@ public static async Task ValidatePassword(int identity, string password) var (context, contextKey) = Conexión.GetOneConnection(); // Directorios. - var(directories, _) = await Queries.Directories.Get(identity); + var (directories, _) = await Queries.Directories.Get(identity); // Política. var policy = await (from p in context.DataBase.Policies @@ -62,4 +62,5 @@ orderby p.Creation } + } \ No newline at end of file From d57d04e7688e0cbd435c30b85760a2a3734f0a47 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 15 Dec 2023 10:00:31 -0500 Subject: [PATCH 056/178] Solucion de error en controlador --- .../Areas/Directories/DirectoryController.cs | 4 +- LIN.Identity/Data/Directories/Directories.cs | 73 +++++++++++++++++++ .../Data/Directories/DirectoryMembers.cs | 68 ----------------- LIN.Identity/Services/Image.cs | 4 +- 4 files changed, 77 insertions(+), 72 deletions(-) diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index c9d0516..ab3980d 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -68,7 +68,7 @@ public async Task> ReadAll([FromHeader] str { // Información del token. - var (isValid, _, user, _, _, _) = Jwt.Validate(token);; + var (isValid, _, _, _, _, identity) = Jwt.Validate(token);; // Token es invalido. if (!isValid) @@ -79,7 +79,7 @@ public async Task> ReadAll([FromHeader] str }; // Obtiene el usuario. - var response = await Data.DirectoryMembers.ReadAll(user); + var response = await Data.Directories.ReadAll(identity); // Si es erróneo if (response.Response != Responses.Success) diff --git a/LIN.Identity/Data/Directories/Directories.cs b/LIN.Identity/Data/Directories/Directories.cs index d1dc395..82765ae 100644 --- a/LIN.Identity/Data/Directories/Directories.cs +++ b/LIN.Identity/Data/Directories/Directories.cs @@ -26,6 +26,24 @@ public static async Task Create(DirectoryModel directory) + /// + /// Obtiene los directorios donde una identidad es integrante. + /// + /// Id de la identidad. + public static async Task> ReadAll(int identityId) + { + + // Obtiene la conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + var res = await ReadAll(identityId, context); + context.CloseActions(connectionKey); + return res; + + } + + + /// /// Obtener un directorio. /// @@ -87,6 +105,61 @@ public static async Task Create(DirectoryModel model, Conexión + /// + /// Obtiene los directorios donde una identidad es integrante. + /// + /// Id de la identidad. + /// Contexto de conexión. + public static async Task> ReadAll(int identityId, Conexión context) + { + + // Ejecución + try + { + + var (_, identities) = await Queries.Directories.Get(identityId); + + // Directorios. + var directories = await (from directoryMember in context.DataBase.DirectoryMembers + where identities.Contains(directoryMember.IdentityId) + select new DirectoryMember + { + Rol = directoryMember.Rol, + DirectoryId = directoryMember.DirectoryId, + Directory = new() + { + ID = directoryMember.Directory.ID, + Creación = directoryMember.Directory.Creación, + Nombre = directoryMember.Directory.Nombre, + IdentityId = directoryMember.Directory.IdentityId, + Identity = new() + { + Id = directoryMember.Directory.Identity.Id, + Unique = directoryMember.Directory.Identity.Unique, + Type = directoryMember.Directory.Identity.Type + } + }, + Identity = new() + { + Id = directoryMember.Identity.Id, + Unique = directoryMember.Identity.Unique, + Type = directoryMember.Identity.Type + }, + IdentityId = directoryMember.IdentityId + }).ToListAsync(); + + return new(Responses.Success, directories); + + } + catch (Exception) + { + } + + return new(); + } + + + /// /// Obtener un directorio. /// diff --git a/LIN.Identity/Data/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Directories/DirectoryMembers.cs index ff0931a..76f0210 100644 --- a/LIN.Identity/Data/Directories/DirectoryMembers.cs +++ b/LIN.Identity/Data/Directories/DirectoryMembers.cs @@ -26,22 +26,6 @@ public static async Task Create(DirectoryMember directory) - /// - /// Obtiene los directorios donde una identidad es integrante. - /// - /// Id de la identidad. - public static async Task> ReadAll(int identityId) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadAll(identityId, context); - context.CloseActions(connectionKey); - return res; - - } - /// /// Obtiene los integrantes de un directorio. @@ -103,58 +87,6 @@ public static async Task Create(DirectoryMember model, Conexión - /// - /// Obtiene los directorios donde una identidad es integrante. - /// - /// Id de la identidad. - /// Contexto de conexión. - public static async Task> ReadAll(int identityId, Conexión context) - { - - // Ejecución - try - { - - var (_, identities) = await Queries.Directories.Get(identityId); - - // Directorios. - var directories = await (from directoryMember in context.DataBase.DirectoryMembers - where identities.Contains(directoryMember.IdentityId) - select new DirectoryMember - { - Rol = directoryMember.Rol, - DirectoryId = directoryMember.DirectoryId, - Directory = new() - { - ID = directoryMember.Directory.ID, - Creación = directoryMember.Directory.Creación, - Nombre = directoryMember.Directory.Nombre, - IdentityId = directoryMember.Directory.IdentityId, - Identity = new() - { - Id = directoryMember.Directory.Identity.Id, - Unique = directoryMember.Directory.Identity.Unique, - Type = directoryMember.Directory.Identity.Type - } - }, - Identity = new() - { - Id = directoryMember.Identity.Id, - Unique = directoryMember.Identity.Unique, - Type = directoryMember.Identity.Type - }, - IdentityId = directoryMember.IdentityId - }).ToListAsync(); - - return new(Responses.Success, directories); - - } - catch (Exception) - { - } - - return new(); - } diff --git a/LIN.Identity/Services/Image.cs b/LIN.Identity/Services/Image.cs index 1fa3559..4172f75 100644 --- a/LIN.Identity/Services/Image.cs +++ b/LIN.Identity/Services/Image.cs @@ -28,7 +28,7 @@ public static byte[] Zip(byte[] originalImage, int width = 50, int height = 50, /// - /// Comprime una imagen + /// Comprime una imagen (En Windows) /// /// Original image /// Ancho @@ -83,7 +83,7 @@ public static byte[] ZipOnWindows(byte[] originalImage, int width = 50, int heig /// - /// Comprime una imagen + /// Comprime una imagen (Plataformas .NET) /// /// Original image /// Ancho From 4943765ca1f13935ffa1055c2e2739080ed4d1fa Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 16 Dec 2023 12:44:54 -0500 Subject: [PATCH 057/178] Mejoras --- .../Areas/Directories/DirectoryController.cs | 1 + .../Organizations/OrganizationController.cs | 5 +- .../Areas/Policies/PolicyController.cs | 46 +++++++++++++++++++ .../Aspose/Drawing/SystemFontCache.xml | 28 +++++++++++ LIN.Identity/Data/Policies.cs | 28 ++++++----- LIN.Identity/LIN.Identity.csproj | 1 + LIN.Identity/Properties/launchSettings.json | 44 +++++++++++------- LIN.Identity/wwwroot/Plantillas/Email.html | 2 +- 8 files changed, 123 insertions(+), 32 deletions(-) create mode 100644 LIN.Identity/Aspose/Drawing/SystemFontCache.xml diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index ab3980d..da34ca1 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -5,6 +5,7 @@ public class DirectoryController : ControllerBase { + /// /// Obtener un directorio. /// diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index b41f29b..76d6449 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -57,13 +57,14 @@ public async Task Create([FromBody] OrganizationModel modelo member.Organization = modelo; } - // Creaci�n de la organización + // Creación de la organización var response = await Data.Organizations.Organizations.Create(modelo, context); - // Evaluaci�n + // Evaluación. if (response.Response != Responses.Success) return new(response.Response); + // Cerrar la conexión. context.CloseActions(connectionKey); // Retorna el resultado diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index 1510f48..638e7d9 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -87,4 +87,50 @@ public async Task> ValidatePermissions([FromQuery] int + /// + /// Obtiene las políticas asociadas a un directorio. + /// + /// Token de acceso. + /// Id del directorio. + [HttpGet("read/all")] + public async Task> ReadAll([FromHeader] string token, [FromQuery] int directory) + { + + // Validar parámetros. + if (directory <= 0) + return new() + { + Message = "Parámetros inválidos.", + Response = Responses.InvalidParam + }; + + // Validar JSON. + var (isValid, _, _, _, _, identity) = Jwt.Validate(token); + + // Token es invalido. + if (!isValid) + return new() + { + Message = "Token invalido.", + Response = Responses.Unauthorized + }; + + // Acceso IAM. + var iam = await Services.Iam.Directories.ValidateAccess(identity, directory); + + // No tiene acceso. + if (iam.Model == IamLevels.NotAccess) + return new() + { + Message = "No tienes acceso a este directorio.", + Response = Responses.Unauthorized + }; + + // Respuesta. + return await Data.Policies.ReadAll(directory); + + } + + + } \ No newline at end of file diff --git a/LIN.Identity/Aspose/Drawing/SystemFontCache.xml b/LIN.Identity/Aspose/Drawing/SystemFontCache.xml new file mode 100644 index 0000000..9a06c65 --- /dev/null +++ b/LIN.Identity/Aspose/Drawing/SystemFontCache.xml @@ -0,0 +1,28 @@ + + + 2023-11-22T21:37:07.9001826Z + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LIN.Identity/Data/Policies.cs b/LIN.Identity/Data/Policies.cs index 575ea81..8cc8005 100644 --- a/LIN.Identity/Data/Policies.cs +++ b/LIN.Identity/Data/Policies.cs @@ -24,14 +24,14 @@ public static async Task Create(PolicyModel data) /// - /// Obtener las políticas. + /// Obtener las políticas asociadas a un directorio /// - /// Id de la cuenta. - public static async Task> ReadAll(int id) + /// Id del directorio. + public static async Task> ReadAll(int directory) { var (context, contextKey) = Conexión.GetOneConnection(); - var res = await ReadAll(id, context); + var res = await ReadAll(directory, context); context.CloseActions(contextKey); return res; } @@ -97,9 +97,9 @@ public static async Task Create(PolicyModel data, Conexión cont /// - /// Obtiene las políticas asociadas a un usuario. + /// Obtiene las políticas asociadas a un directorio. /// - /// ID de la cuenta + /// ID del directorio /// Contexto de conexión public static async Task> ReadAll(int id, Conexión context) { @@ -108,12 +108,16 @@ public static async Task> ReadAll(int id, Conexión try { - var policies = await (from directory in context.DataBase.DirectoryMembers - where directory.IdentityId == id - join policie in context.DataBase.Policies - on directory.DirectoryId equals policie.DirectoryId - select policie).ToListAsync(); - + var policies = await (from policy in context.DataBase.Policies + where policy.DirectoryId == id + select new PolicyModel + { + Id = policy.Id, + Creation = policy.Creation, + DirectoryId = policy.DirectoryId, + Type = policy.Type, + ValueJson = policy.ValueJson + }).ToListAsync(); return new(Responses.Success, policies); diff --git a/LIN.Identity/LIN.Identity.csproj b/LIN.Identity/LIN.Identity.csproj index fa302bd..36a95a5 100644 --- a/LIN.Identity/LIN.Identity.csproj +++ b/LIN.Identity/LIN.Identity.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + c0e756b3-bd08-49b8-93e6-c3c6ce24d1fb diff --git a/LIN.Identity/Properties/launchSettings.json b/LIN.Identity/Properties/launchSettings.json index 54ba7df..f570cb9 100644 --- a/LIN.Identity/Properties/launchSettings.json +++ b/LIN.Identity/Properties/launchSettings.json @@ -1,33 +1,24 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:6638", - "sslPort": 44316 - } - }, +{ "profiles": { "http": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", - "applicationUrl": "http://localhost:5225", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5225" }, "https": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", - "applicationUrl": "https://localhost:7265;http://localhost:5225", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7265;http://localhost:5225" }, "IIS Express": { "commandName": "IISExpress", @@ -36,6 +27,25 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "WSL": { + "commandName": "WSL2", + "launchBrowser": true, + "launchUrl": "https://localhost:7265/swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "https://localhost:7265;http://localhost:5225" + }, + "distributionName": "" + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:6638", + "sslPort": 44316 } } -} +} \ No newline at end of file diff --git a/LIN.Identity/wwwroot/Plantillas/Email.html b/LIN.Identity/wwwroot/Plantillas/Email.html index c78d0fa..94366e9 100644 --- a/LIN.Identity/wwwroot/Plantillas/Email.html +++ b/LIN.Identity/wwwroot/Plantillas/Email.html @@ -23,7 +23,7 @@ - + Date: Mon, 18 Dec 2023 13:49:08 -0500 Subject: [PATCH 058/178] Mejoras de seguridad --- .../Areas/Policies/PolicyController.cs | 4 +-- .../Aspose/Drawing/SystemFontCache.xml | 28 ------------------- LIN.Identity/Program.cs | 6 +--- 3 files changed, 3 insertions(+), 35 deletions(-) delete mode 100644 LIN.Identity/Aspose/Drawing/SystemFontCache.xml diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index 638e7d9..90ba0b4 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -119,10 +119,10 @@ public async Task> ReadAll([FromHeader] string var iam = await Services.Iam.Directories.ValidateAccess(identity, directory); // No tiene acceso. - if (iam.Model == IamLevels.NotAccess) + if (iam.Model == IamLevels.NotAccess || iam.Model == IamLevels.Guest) return new() { - Message = "No tienes acceso a este directorio.", + Message = "No tienes acceso a este directoriopp.", Response = Responses.Unauthorized }; diff --git a/LIN.Identity/Aspose/Drawing/SystemFontCache.xml b/LIN.Identity/Aspose/Drawing/SystemFontCache.xml deleted file mode 100644 index 9a06c65..0000000 --- a/LIN.Identity/Aspose/Drawing/SystemFontCache.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - 2023-11-22T21:37:07.9001826Z - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index e94a5c7..5c64735 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -25,13 +25,9 @@ }); -#if DEBUG - var sqlConnection = builder.Configuration["ConnectionStrings:local"] ?? string.Empty; -#elif RELEASE + var sqlConnection = builder.Configuration["ConnectionStrings:somee"] ?? string.Empty; -#endif - // Servicio de BD builder.Services.AddDbContext(options => From 4b859cc52c9d9020770d8b5921d421332e1a5a21 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 25 Dec 2023 14:57:52 -0500 Subject: [PATCH 059/178] Mejoras --- .../Areas/Accounts/AccountController.cs | 131 ++++---- .../Areas/Accounts/AccountLogsController.cs | 11 +- .../Accounts/AccountSecurityController.cs | 20 +- .../Applications/ApplicationController.cs | 36 +-- .../AuthenticationController.cs | 30 +- .../Areas/Authentication/IntentsController.cs | 15 +- .../Areas/Directories/DirectoryController.cs | 57 ++-- .../Directories/DirectoryMemberController.cs | 59 ++++ .../Areas/Organizations/MemberController.cs | 63 ++-- .../Organizations/OrganizationController.cs | 77 ++--- .../Areas/Policies/PolicyController.cs | 39 ++- LIN.Identity/Data/Accounts/AccountsGet.cs | 82 +++-- LIN.Identity/Data/Accounts/AccountsUpdate.cs | 35 --- LIN.Identity/Data/Context.cs | 36 +-- LIN.Identity/Data/Directories/Directories.cs | 48 ++- .../Data/Directories/DirectoryMembers.cs | 1 - LIN.Identity/Data/Identities.cs | 56 ++++ LIN.Identity/Data/Organizations/Members.cs | 103 +++---- .../Data/Organizations/Organizations.cs | 155 +++++----- LIN.Identity/Data/Policies.cs | 2 +- LIN.Identity/Hubs/PasskeyHub.cs | 7 - .../{FilterModels => Models}/Account.cs | 7 +- LIN.Identity/Models/JwtModel.cs | 43 +++ LIN.Identity/Program.cs | 2 +- LIN.Identity/Queries/Directories.cs | 35 ++- .../Queries/{Accounts.cs => Identities.cs} | 107 ++++--- LIN.Identity/Services/Iam/Applications.cs | 6 +- LIN.Identity/Services/Iam/Directories.cs | 56 ---- LIN.Identity/Services/Jwt.cs | 26 +- LIN.Identity/Services/Login/LoginOnOrg.cs | 157 ---------- LIN.Identity/Services/OrgRoleExt.cs | 56 ---- LIN.Identity/Validations/Account.cs | 5 +- LIN.Identity/Validations/AccountPassword.cs | 2 +- LIN.Identity/Validations/Roles.cs | 286 ++++++++++++++++++ 34 files changed, 1011 insertions(+), 840 deletions(-) create mode 100644 LIN.Identity/Areas/Directories/DirectoryMemberController.cs create mode 100644 LIN.Identity/Data/Identities.cs rename LIN.Identity/{FilterModels => Models}/Account.cs (89%) create mode 100644 LIN.Identity/Models/JwtModel.cs rename LIN.Identity/Queries/{Accounts.cs => Identities.cs} (67%) delete mode 100644 LIN.Identity/Services/Iam/Directories.cs delete mode 100644 LIN.Identity/Services/Login/LoginOnOrg.cs delete mode 100644 LIN.Identity/Services/OrgRoleExt.cs create mode 100644 LIN.Identity/Validations/Roles.cs diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index 4b6e177..b9cfc60 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -65,12 +65,13 @@ public async Task> Read([FromQuery] int id, [F Message = "Uno o varios parámetros son inválidos." }; - // Información del token. - var (isValid, _, user, orgId, _, _) = Jwt.Validate(token);; - // Token es invalido. - if (!isValid) - return new ReadOneResponse() + // Token. + var tokenInfo = Jwt.Validate(token); + + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { Response = Responses.Unauthorized, Message = "Token invalido." @@ -79,12 +80,11 @@ public async Task> Read([FromQuery] int id, [F // Obtiene el usuario. var response = await Data.Accounts.Read(id, new() { - ContextOrg = orgId, - ContextUser = user, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.IncludeIf, + ContextAccount = tokenInfo.AccountId, + FindOn = Models.FindOn.StableAccounts, + IncludeOrg = Models.IncludeOrg.IncludeIf, IsAdmin = false, - OrgLevel = FilterModels.IncludeOrgLevel.Advance + OrgLevel = Models.IncludeOrgLevel.Advance }); // Si es erróneo @@ -117,11 +117,11 @@ public async Task> Read([FromQuery] string use Message = "Uno o varios parámetros son inválidos." }; - // Información del token. - var (isValid, _, userId, orgId, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Token es invalido. - if (!isValid) + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) return new() { Response = Responses.Unauthorized, @@ -131,11 +131,10 @@ public async Task> Read([FromQuery] string use // Obtiene el usuario. var response = await Data.Accounts.Read(user, new() { - ContextOrg = orgId, - ContextUser = userId, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.IncludeIf, - OrgLevel = FilterModels.IncludeOrgLevel.Advance + ContextAccount = tokenInfo.AccountId, + FindOn = Models.FindOn.StableAccounts, + IncludeOrg = Models.IncludeOrg.IncludeIf, + OrgLevel = Models.IncludeOrgLevel.Advance }); // Si es erróneo @@ -169,25 +168,24 @@ public async Task> Search([FromQuery] string p Message = "Uno o varios parámetros son inválidos." }; - // Info del token - var (isValid, _, userId, orgId, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Token es invalido - if (!isValid) - return new ReadAllResponse + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { - Message = "Token es invalido", - Response = Responses.Unauthorized + Response = Responses.Unauthorized, + Message = "Token invalido." }; // Obtiene el usuario var response = await Data.Accounts.Search(pattern, new() { - ContextOrg = orgId, - ContextUser = userId, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.IncludeIf, - OrgLevel = FilterModels.IncludeOrgLevel.Advance + ContextAccount = tokenInfo.AccountId, + FindOn = Models.FindOn.StableAccounts, + IncludeOrg = Models.IncludeOrg.IncludeIf, + OrgLevel = Models.IncludeOrgLevel.Advance }); return response; @@ -212,11 +210,11 @@ public async Task> ReadAll([FromBody] List> ReadAll([FromBody] List> ReadAll([FromBody] List> FindAll([FromQuery] string pattern, [FromHeader] string token) { - var (isValid, _, _, _, _, _) = Jwt.Validate(token);; - - - if (!isValid) - { - return new(Responses.Unauthorized); - } + // Token. + var tokenInfo = Jwt.Validate(token); + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; var rol = AccountRoles.User; @@ -268,10 +267,10 @@ public async Task> FindAll([FromQuery] string var response = await Data.Accounts.Search(pattern, new() { ContextOrg = 0, - OrgLevel = FilterModels.IncludeOrgLevel.Advance, - ContextUser = 0, - FindOn = FilterModels.FindOn.AllAccount, - IncludeOrg = FilterModels.IncludeOrg.Include, + OrgLevel = Models.IncludeOrgLevel.Advance, + ContextAccount = 0, + FindOn = Models.FindOn.AllAccount, + IncludeOrg = Models.IncludeOrg.Include, IsAdmin = true }); @@ -290,19 +289,19 @@ public async Task> FindAll([FromQuery] string public async Task Update([FromBody] AccountModel modelo, [FromHeader] string token) { - // Información del token. - var (isValid, _, userId, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Es invalido. - if (!isValid) - return new ResponseBase + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { Response = Responses.Unauthorized, - Message = "Token Invalido" + Message = "Token invalido." }; // Organizar el modelo. - modelo.Identity.Id = userId; + modelo.Identity.Id = tokenInfo.IdentityId; modelo.Perfil = Image.Zip(modelo.Perfil ?? []); if (modelo.Identity.Id <= 0 || modelo.Nombre.Any()) @@ -323,11 +322,11 @@ public async Task Update([FromBody] AccountModel modelo, [From public async Task Update([FromHeader] string token, [FromHeader] Genders genero) { - // Información del token. - var (isValid, _, id, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Token es invalido. - if (!isValid) + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) return new() { Response = Responses.Unauthorized, @@ -350,11 +349,11 @@ public async Task Update([FromHeader] string token, [FromHeade public async Task Update([FromHeader] string token, [FromHeader] AccountVisibility visibility) { - // Información del token. - var (isValid, _, id, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Token es invalido. - if (!isValid) + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) return new() { Response = Responses.Unauthorized, diff --git a/LIN.Identity/Areas/Accounts/AccountLogsController.cs b/LIN.Identity/Areas/Accounts/AccountLogsController.cs index 192d9bb..5f8975a 100644 --- a/LIN.Identity/Areas/Accounts/AccountLogsController.cs +++ b/LIN.Identity/Areas/Accounts/AccountLogsController.cs @@ -14,13 +14,14 @@ public class AccountLogsController : ControllerBase public async Task> GetAll([FromHeader] string token) { - // JWT. - var (isValid, _, userId, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Validación. - if (!isValid) - return new(Responses.Unauthorized) + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { + Response = Responses.Unauthorized, Message = "Token invalido." }; diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs index 6a73dfc..d15b225 100644 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -15,15 +15,15 @@ public class AccountSecurityController : ControllerBase public async Task Delete([FromHeader] string token) { - // Información del token. - var (isValid, _, userId, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Si es invalido. - if (!isValid) - return new ResponseBase + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { Response = Responses.Unauthorized, - Message = "Token invalido" + Message = "Token invalido." }; if (userId <= 0) @@ -65,11 +65,11 @@ public async Task UpdatePassword([FromHeader] int account, [Fr { ContextOrg = 0, SensibleInfo = true, - ContextUser = account, - FindOn = FilterModels.FindOn.StableAccounts, - IncludeOrg = FilterModels.IncludeOrg.None, + ContextAccount = account, + FindOn = Models.FindOn.StableAccounts, + IncludeOrg = Models.IncludeOrg.None, IsAdmin = true, - OrgLevel = FilterModels.IncludeOrgLevel.Basic + OrgLevel = Models.IncludeOrgLevel.Basic }); // Si no existe la cuenta. diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs index d3f21e0..86e2df4 100644 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -15,15 +15,15 @@ public class ApplicationController : ControllerBase public async Task Create([FromBody] ApplicationModel applicationModel, [FromHeader] string token) { - // Información del token. - var (isValid, _, userID, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Si el token es invalido. - if (!isValid) - return new CreateResponse() + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { Response = Responses.Unauthorized, - Message = "El token es invalido." + Message = "Token invalido." }; // Validaciones. @@ -54,15 +54,15 @@ public async Task Create([FromBody] ApplicationModel applica public async Task> GetAll([FromHeader] string token) { - // Información del token. - var (isValid, _, userID, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Si el token es invalido. - if (!isValid) - return new ReadAllResponse() + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { Response = Responses.Unauthorized, - Message = "El token es invalido." + Message = "Token invalido." }; // Obtiene la data. @@ -84,15 +84,15 @@ public async Task> GetAll([FromHeader] str public async Task> InsertAllow([FromHeader] string token, [FromHeader] int appId, [FromHeader] int accountId) { - // Información del token. - var (isValid, _, userId, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Si el token es invalido. - if (!isValid) - return new ReadOneResponse() + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { Response = Responses.Unauthorized, - Message = "El token es invalido." + Message = "Token invalido." }; // Respuesta de Iam. diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index de5e7f5..ce28259 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -31,9 +31,9 @@ public async Task> Login([FromQuery] string us { SensibleInfo = true, IsAdmin = true, - IncludeOrg = FilterModels.IncludeOrg.Include, - OrgLevel = FilterModels.IncludeOrgLevel.Advance, - FindOn = FilterModels.FindOn.StableAccounts + IncludeOrg = Models.IncludeOrg.Include, + OrgLevel = Models.IncludeOrgLevel.Advance, + FindOn = Models.FindOn.StableAccounts }); // Validación al obtener el usuario @@ -56,8 +56,7 @@ public async Task> Login([FromQuery] string us LoginService strategy; // Definir la estrategia - strategy = response.Model.OrganizationAccess == null ? new LoginNormal(response.Model, application, password) - : new LoginOnOrg(response.Model, application, password); + strategy = new LoginNormal(response.Model, application, password); // Respuesta del login var loginResponse = await strategy.Login(); @@ -91,24 +90,25 @@ public async Task> Login([FromQuery] string us public async Task> LoginWithToken([FromHeader] string token) { - // Información del token de acceso. - var (isValid, _, user, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Si el token es invalido. - if (!isValid) - return new(Responses.InvalidParam) + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { - Message = "El token proporcionado no es valido." + Response = Responses.Unauthorized, + Message = "Token invalido." }; // Obtiene el usuario - var response = await Data.Accounts.Read(user, new() + var response = await Data.Accounts.Read(user, new Models.Account() { SensibleInfo = true, IsAdmin = true, - IncludeOrg = FilterModels.IncludeOrg.Include, - OrgLevel = FilterModels.IncludeOrgLevel.Advance, - FindOn = FilterModels.FindOn.StableAccounts + IncludeOrg = Models.IncludeOrg.Include, + OrgLevel = Models.IncludeOrgLevel.Advance, + FindOn = Models.FindOn.StableAccounts }); if (response.Response != Responses.Success) diff --git a/LIN.Identity/Areas/Authentication/IntentsController.cs b/LIN.Identity/Areas/Authentication/IntentsController.cs index af76a01..febb95e 100644 --- a/LIN.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Identity/Areas/Authentication/IntentsController.cs @@ -14,16 +14,15 @@ public HttpReadAllResponse GetAll([FromHeader] string token) { try { + // Token. + var tokenInfo = Jwt.Validate(token); - // Info del token - var (isValid, user, _, _, _, _) = Jwt.Validate(token);; - - // Si el token es invalido - if (!isValid) - return new ReadAllResponse + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { - Message = "Invalid Token", - Response = Responses.Unauthorized + Response = Responses.Unauthorized, + Message = "Token invalido." }; diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index da34ca1..6f75766 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -20,12 +20,12 @@ public async Task> Read([FromQuery] int id, if (id <= 0) return new(Responses.InvalidParam); - // Información del token. - var (isValid, _, _, _, _, identity) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Token es invalido. - if (!isValid) - return new ReadOneResponse() + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { Response = Responses.Unauthorized, Message = "Token invalido." @@ -33,17 +33,17 @@ public async Task> Read([FromQuery] int id, // Acceso IAM. - var iam = await Services.Iam.Directories.ValidateAccess(identity, id); + var (_, _, roles) = await Queries.Directories.Get(tokenInfo.IdentityId); - // Validar Iam. - if (iam.Model == IamLevels.NotAccess) + // Si no hay acceso. + if (Roles.View(roles)) return new ReadOneResponse() { - Message = "No tienes acceso a este directorio, si crees que es un error comunícate con tu administrador.", - Response = Responses.Unauthorized + Response = Responses.Unauthorized, + Message = "No tienes permisos para acceder a este recurso." }; - // Obtiene el usuario. + // Obtiene el directorio. var response = await Data.Directories.Read(id, findIdentity); // Si es erróneo @@ -68,19 +68,19 @@ public async Task> Read([FromQuery] int id, public async Task> ReadAll([FromHeader] string token) { - // Información del token. - var (isValid, _, _, _, _, identity) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Token es invalido. - if (!isValid) - return new ReadAllResponse() + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { Response = Responses.Unauthorized, Message = "Token invalido." }; // Obtiene el usuario. - var response = await Data.Directories.ReadAll(identity); + var response = await Data.Directories.ReadAll(tokenInfo.IdentityId); // Si es erróneo if (response.Response != Responses.Success) @@ -105,12 +105,12 @@ public async Task> ReadAll([FromHeader] str public async Task> ReadAll([FromHeader] string token, [FromQuery] int directory) { - // Información del token. - var (isValid, _, user, _, _, identity) = Jwt.Validate(token); ; + // Token. + var tokenInfo = Jwt.Validate(token); - // Token es invalido. - if (!isValid) - return new ReadAllResponse() + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { Response = Responses.Unauthorized, Message = "Token invalido." @@ -118,17 +118,14 @@ public async Task> ReadAll([FromHeader] str // Acceso IAM. - var iam = await Services.Iam.Directories.ValidateAccess(identity, directory); + var (_, _, roles) = await Queries.Directories.Get(tokenInfo.IdentityId); - // Roles disponibles. - IEnumerable have = [IamLevels.Privileged, IamLevels.Visualizer]; - - // No tiene un permitido. - if (!have.Contains(iam.Model)) + // Si no hay acceso. + if (Roles.ViewMembers(roles)) return new() { - Message = "No tienes acceso a este recurso.", - Response = Responses.Unauthorized + Response = Responses.Unauthorized, + Message = "No tienes permisos para visualizar los integrantes de este recurso." }; // Obtiene el usuario. diff --git a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs new file mode 100644 index 0000000..02024d5 --- /dev/null +++ b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs @@ -0,0 +1,59 @@ +namespace LIN.Identity.Areas.Directories; + + +[Route("directory/members")] +public class DirectoryMembersController : ControllerBase +{ + + + [HttpPost] + public async Task Create([FromHeader] string token, [FromBody] DirectoryMember model) + { + + // Token. + var tokenInfo = Jwt.Validate(token); + + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; + + + // Acceso IAM. + var (_, _, roles) = await Queries.Directories.Get(identity); + + // Si no hay acceso. + if (Roles.AlterMembers(roles)) + return new() + { + Response = Responses.Unauthorized, + Message = "No tienes permisos para administrar los integrantes de este recurso." + }; + + + + var guestIdentity = await Data.Identities.Read(model.Identity.Id); + + if (guestIdentity.Response != Responses.Success) + return new() + { + Message = "Error", + Response = Responses.NotRows + }; + + + // Obtiene el usuario. + var response = await Data.DirectoryMembers.Create(model); + + + // Retorna el resultado + return response; + + } + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index 05fdc83..588be1b 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -13,7 +13,7 @@ public class MemberController : ControllerBase /// Token de acceso de un administrador /// Rol asignado [HttpPost] - public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] OrgRoles rol) + public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] DirectoryRoles rol) { // Validación del modelo. @@ -37,19 +37,16 @@ public async Task Create([FromBody] AccountModel modelo, [Fr // Contraseña default modelo.Contraseña = EncryptClass.Encrypt(password); - // Validación del token - var (isValid, _, userID, _, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - // Token es invalido - if (!isValid) - { - return new CreateResponse + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { - Message = "Token invalido.", - Response = Responses.Unauthorized + Response = Responses.Unauthorized, + Message = "Token invalido." }; - } - // Obtiene el usuario var userContext = await Data.Accounts.ReadBasic(userID); @@ -64,47 +61,37 @@ public async Task Create([FromBody] AccountModel modelo, [Fr }; } - // Si el usuario no tiene una organización - if (userContext.Model.OrganizationAccess == null) - { - return new CreateResponse - { - Message = $"El usuario '{userContext.Model.Identity.Unique}' no pertenece a una organización.", - Response = Responses.Unauthorized - }; - } - // Verificación del rol dentro de la organización - if (!userContext.Model.OrganizationAccess.Rol.IsAdmin()) + var orgBase = await Data.Organizations.Organizations.FindBaseDirectory(userContext.Model.IdentityId); + + if (orgBase.Response != Responses.Success) { - return new CreateResponse + return new() { - Message = $"El usuario '{userContext.Model.Identity.Unique}' no puede crear nuevos usuarios en esta organización.", - Response = Responses.Unauthorized + Response = Responses.NotRows, + Message = "No se encontró una organización permitida a este usuario." }; } + // Validar acceso en el directorio con IAM. + //var iam = await Services.Iam.Directories.ValidateAccess(userContext.Model.IdentityId, orgBase.Model.DirectoryId); - // Verificación del rol dentro de la organización - if (userContext.Model.OrganizationAccess.Rol.IsGretter(rol)) - { - return new CreateResponse - { - Message = $"El '{userContext.Model.Identity.Unique}' no puede crear nuevos usuarios con mas privilegios de los propios.", - Response = Responses.Unauthorized - }; - } + //DirectoryRoles[] roles = [DirectoryRoles.System, DirectoryRoles.SuperManager, DirectoryRoles.Manager, DirectoryRoles.AccountsOperator, DirectoryRoles.Operator]; - // ID de la organización - var org = userContext.Model.OrganizationAccess.OrganizationId; + //if (!roles.Contains(iam.Model)) + // return new CreateResponse + // { + // Message = $"No tienes permisos suficientes para crear nuevas cuentas en el directorio general de tu organización.", + // Response = Responses.Unauthorized + // }; // Conexión var (context, connectionKey) = Conexión.GetOneConnection(); // Creación del usuario - var response = await Data.Organizations.Members.Create(modelo, org, rol, context); + var response = await Data.Organizations.Members.Create(modelo, orgBase.Model.DirectoryId, rol, context); // Evaluación if (response.Response != Responses.Success) @@ -134,7 +121,7 @@ public async Task> ReadAll([FromHeader] string { // Información del token. - var (isValid, _, _, orgID, _, _) = Jwt.Validate(token);; + var (isValid, _, _, orgID, _, _) = Jwt.Validate(token); ; // Si el token es invalido. if (!isValid) diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index 76d6449..9bc2057 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -14,60 +14,37 @@ public class OrganizationsController : ControllerBase public async Task Create([FromBody] OrganizationModel modelo) { - // Comprobaciones - if (modelo == null || modelo.Domain.Length <= 0 || modelo.Name.Length <= 0 || modelo.Members.Count <= 0) - return new(Responses.InvalidParam); - - - // BD. - var (context, connectionKey) = Conexión.GetOneConnection(); - - - // organización del modelo - modelo.ID = 0; - - // Directorio - modelo.Directory = new() - { - ID = 0, - Creación = DateTime.Now, - Nombre = "Directorio General: " + modelo.Name, - Identity = new() + // Validar el modelo. + if (modelo == null || string.IsNullOrWhiteSpace(modelo.Name) || modelo.Directory == null || modelo.Directory.Identity == null || string.IsNullOrWhiteSpace(modelo.Directory.Identity.Unique)) + return new() { - Id = 0, - Type = IdentityTypes.Directory, - Unique = "d_" + modelo.Domain - }, - IdentityId = 0 - }; - + Response = Responses.InvalidParam, + Message = "Parámetros inválidos." + }; - foreach (var member in modelo.Members) + // Ordenar el modelo. { - modelo.Directory.Members.Add(new() - { - Identity = member.Member.Identity, - IdentityId =0, - Directory = modelo.Directory, - DirectoryId = 0 - }); - member.Member = Account.Process(member.Member); - member.Rol = OrgRoles.SuperManager; - member.Organization = modelo; + modelo.ID = 0; + modelo.Name = modelo.Name.Trim(); + modelo.Directory.Nombre = modelo.Directory.Nombre.Trim(); + modelo.Directory.Members = []; + modelo.Directory.Policies = []; + modelo.Directory.Creación = DateTime.Now; + modelo.Directory.Identity.Type = IdentityTypes.Directory; + modelo.Directory.Identity.DirectoryMembers = []; + modelo.Directory.Identity.Unique = modelo.Directory.Identity.Unique.Trim(); } - // Creación de la organización - var response = await Data.Organizations.Organizations.Create(modelo, context); + + // Creación de la organización. + var response = await Data.Organizations.Organizations.Create(modelo); // Evaluación. if (response.Response != Responses.Success) return new(response.Response); - // Cerrar la conexión. - context.CloseActions(connectionKey); - - // Retorna el resultado + // Retorna el resultado. return new CreateResponse() { LastID = response.Model.ID, @@ -92,12 +69,16 @@ public async Task> ReadOneByID([FromQuery if (id <= 0) return new(Responses.InvalidParam); - // Validar el token - var (isValid, _, _, orgID, _, _) = Jwt.Validate(token);; + // Token. + var tokenInfo = Jwt.Validate(token); - - if (!isValid) - return new(Responses.Unauthorized); + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() + { + Response = Responses.Unauthorized, + Message = "Token invalido." + }; // Obtiene la organización diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index 90ba0b4..0552eee 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -23,28 +23,27 @@ public async Task Create([FromBody] PolicyModel policy, [Fro Response = Responses.InvalidParam }; - // Validación del token - var (isValid, _, _, _, _, identity) = Jwt.Validate(token); ; + // Token. + var tokenInfo = Jwt.Validate(token); - // Token es invalido - if (!isValid) - return new CreateResponse + // Si el token no es valido. + if (!tokenInfo.IsAuthenticated) + return new() { - Message = "Token invalido.", - Response = Responses.Unauthorized + Response = Responses.Unauthorized, + Message = "Token invalido." }; - // Acceso IAM. - var iam = await Services.Iam.Directories.ValidateAccess(identity, policy.DirectoryId); + // var iam = await Services.Iam.Directories.ValidateAccess(identity, policy.DirectoryId); // SI no tiene permisos de modificación. - if (iam.Model != IamLevels.Privileged) - return new CreateResponse - { - Message = "No tienes permiso para modificar este directorio.", - Response = Responses.Unauthorized - }; + //if (iam.Model != IamLevels.Privileged) + // return new CreateResponse + // { + // Message = "No tienes permiso para modificar este directorio.", + // Response = Responses.Unauthorized + // }; return await Data.Policies.Create(new() @@ -116,14 +115,14 @@ public async Task> ReadAll([FromHeader] string }; // Acceso IAM. - var iam = await Services.Iam.Directories.ValidateAccess(identity, directory); + var (_, _, roles) = await Queries.Directories.Get(identity); - // No tiene acceso. - if (iam.Model == IamLevels.NotAccess || iam.Model == IamLevels.Guest) + // Si no hay acceso. + if (Roles.ViewPolicy(roles)) return new() { - Message = "No tienes acceso a este directoriopp.", - Response = Responses.Unauthorized + Response = Responses.Unauthorized, + Message = "No tienes permisos para visualizar las políticas de este recurso." }; // Respuesta. diff --git a/LIN.Identity/Data/Accounts/AccountsGet.cs b/LIN.Identity/Data/Accounts/AccountsGet.cs index 7b47066..f8f2bd9 100644 --- a/LIN.Identity/Data/Accounts/AccountsGet.cs +++ b/LIN.Identity/Data/Accounts/AccountsGet.cs @@ -8,7 +8,7 @@ internal static partial class Accounts #region Abstracciones - public static async Task> Read(int id, FilterModels.Account filters) + public static async Task> Read(int id, Models.Account filters) { // Obtiene la conexión @@ -23,6 +23,21 @@ public static async Task> Read(int id, FilterModel + public static async Task> ReadByIdentity(int id, Models.Account filters) + { + + // Obtiene la conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + var res = await ReadByIdentity(id, filters, context); + context.CloseActions(connectionKey); + return res; + + } + + + + /// @@ -30,7 +45,7 @@ public static async Task> Read(int id, FilterModel /// /// ID de la cuenta /// Incluir la organización - public static async Task> Read(string user, FilterModels.Account filters) + public static async Task> Read(string user, Models.Account filters) { // Obtiene la conexión @@ -87,7 +102,7 @@ public static async Task> ReadBasic(string user) /// Patron de búsqueda /// Mi ID /// ID de la org de contexto - public static async Task> Search(string pattern, FilterModels.Account filters) + public static async Task> Search(string pattern, Models.Account filters) { // Obtiene la conexión @@ -109,7 +124,7 @@ public static async Task> Search(string pattern, F /// Lista de IDs /// ID del usuario contexto /// ID de organización - public static async Task> FindAll(List ids, FilterModels.Account filters) + public static async Task> FindAll(List ids, Models.Account filters) { // Obtiene la conexión @@ -132,14 +147,47 @@ public static async Task> FindAll(List ids, F /// ID del usuario /// Filtros de búsqueda /// Contexto de base de datos - public static async Task> Read(int id, FilterModels.Account filters, Conexión context) + public static async Task> Read(int id, Models.Account filters, Conexión context) + { + + // Ejecución + try + { + + var query = Queries.Identities.GetAccounts(id, filters, context); + + // Obtiene el usuario + var result = await query.FirstOrDefaultAsync(); + + // Si no existe el modelo + if (result == null) + return new(Responses.NotExistAccount); + + return new(Responses.Success, result); + } + catch + { + } + + return new(); + } + + + + /// + /// Obtiene un usuario + /// + /// ID de la identidad + /// Filtros de búsqueda + /// Contexto de base de datos + public static async Task> ReadByIdentity(int id, Models.Account filters, Conexión context) { // Ejecución try { - var query = Queries.Accounts.GetAccounts(id, filters, context); + var query = Queries.Identities.GetAccountsByIdentity(id, filters, context); // Obtiene el usuario var result = await query.FirstOrDefaultAsync(); @@ -165,14 +213,14 @@ public static async Task> Read(int id, FilterModel /// Usuario único /// Filtros de búsqueda /// Contexto de base de datos - public static async Task> Read(string user, FilterModels.Account filters, Conexión context) + public static async Task> Read(string user, Models.Account filters, Conexión context) { // Ejecución try { - var query = Queries.Accounts.GetAccounts(user, filters, context); + var query = Queries.Identities.GetAccounts(user, filters, context); // Obtiene el usuario var result = await query.FirstOrDefaultAsync(); @@ -201,14 +249,14 @@ public static async Task> Read(string user, Filter /// ID de la organización de contexto /// Es administrador /// Contexto de base de datos - public static async Task> Search(string pattern, FilterModels.Account filters, Conexión context) + public static async Task> Search(string pattern, Models.Account filters, Conexión context) { // Ejecución try { - List accountModels = await Queries.Accounts.Search(pattern, filters, context).Take(10).ToListAsync(); + List accountModels = await Queries.Identities.Search(pattern, filters, context).Take(10).ToListAsync(); // Si no existe el modelo if (accountModels == null) @@ -232,14 +280,14 @@ public static async Task> Search(string pattern, F /// ID del usuario contexto /// ID de la organización de contexto /// Contexto de base de datos - public static async Task> FindAll(List ids, FilterModels.Account filters, Conexión context) + public static async Task> FindAll(List ids, Models.Account filters, Conexión context) { // Ejecución try { - var query = Queries.Accounts.GetAccounts(ids, filters, context); + var query = Queries.Identities.GetAccounts(ids, filters, context); // Ejecuta var result = await query.ToListAsync(); @@ -271,7 +319,7 @@ public static async Task> ReadBasic(int id, Conexi try { - var query = from account in Queries.Accounts.GetValidAccounts(context) + var query = from account in Queries.Identities.GetValidAccounts(context) where account.ID == id select new AccountModel { @@ -284,8 +332,7 @@ public static async Task> ReadBasic(int id, Conexi }, Contraseña = account.Contraseña, Estado = account.Estado, - Nombre = account.Nombre, - OrganizationAccess = account.OrganizationAccess + Nombre = account.Nombre }; // Obtiene el usuario @@ -318,7 +365,7 @@ public static async Task> ReadBasic(string user, C try { - var query = from account in Queries.Accounts.GetValidAccounts(context) + var query = from account in Queries.Identities.GetValidAccounts(context) where account.Identity.Unique == user select new AccountModel { @@ -331,8 +378,7 @@ public static async Task> ReadBasic(string user, C }, Contraseña = account.Contraseña, Estado = account.Estado, - Nombre = account.Nombre, - OrganizationAccess = account.OrganizationAccess + Nombre = account.Nombre }; // Obtiene el usuario diff --git a/LIN.Identity/Data/Accounts/AccountsUpdate.cs b/LIN.Identity/Data/Accounts/AccountsUpdate.cs index bfa9817..811511e 100644 --- a/LIN.Identity/Data/Accounts/AccountsUpdate.cs +++ b/LIN.Identity/Data/Accounts/AccountsUpdate.cs @@ -183,41 +183,6 @@ public static async Task Update(AccountModel modelo, Conexión con - /// - /// Actualiza la organización de una cuenta - /// - /// organización - /// Contexto de conexión con la BD - public static async Task Update(OrganizationModel newData, int id, Conexión context) - { - - // Encontrar el usuario - var usuario = await (from U in context.DataBase.OrganizationAccess - where U.Member.ID == id - select U).FirstOrDefaultAsync(); - - - var org = await (from U in context.DataBase.Organizations - where U.ID == newData.ID - select U - ).FirstOrDefaultAsync(); - - - - // Si el usuario no existe - if (usuario == null || org == null) - { - return new(Responses.NotExistAccount); - } - - // Cambiar Contraseña - usuario.Organization = org; - - context.DataBase.SaveChanges(); - return new(Responses.Success); - - } - /// diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index a692d05..bc7e3f0 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -35,11 +35,6 @@ public class Context : DbContext public DbSet DirectoryMembers { get; set; } - /// - /// Accesos a organizaciones. - /// - public DbSet OrganizationAccess { get; set; } - /// /// Tabla de aplicaciones @@ -88,11 +83,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasIndex(e => e.Unique) .IsUnique(); - // Indices y identidad. - modelBuilder.Entity() - .HasIndex(e => e.Domain) - .IsUnique(); - // Indices y identidad. modelBuilder.Entity() .HasIndex(e => e.Key) @@ -126,7 +116,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .WithMany() .HasForeignKey(p => p.ApplicationID) .OnDelete(DeleteBehavior.NoAction); - ; + ; modelBuilder.Entity() @@ -137,13 +127,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) - modelBuilder.Entity() - .HasOne(oa => oa.Organization) - .WithMany(o => o.Members) - .HasForeignKey(oa => oa.OrganizationId) - .OnDelete(DeleteBehavior.Restrict); - - modelBuilder.Entity() .HasOne(dm => dm.Directory) .WithMany(d => d.Members) @@ -163,20 +146,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasOne(p => p.Directory) .WithMany(d => d.Policies) .HasForeignKey(p => p.DirectoryId) - .OnDelete(DeleteBehavior.Restrict); - - + .OnDelete(DeleteBehavior.Restrict); - // Configure OrganizationAccessModel - modelBuilder.Entity() - .HasOne(o => o.Member) - .WithOne(a => a.OrganizationAccess) - .HasForeignKey(o => o.MemberId) - .IsRequired(); modelBuilder.Entity() .HasKey(t => new @@ -185,12 +160,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) t.DirectoryId }); - modelBuilder.Entity() - .HasKey(t => new - { - t.MemberId, - t.OrganizationId - }); // Nombre de la identidades. @@ -201,7 +170,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("EMAILS"); modelBuilder.Entity().ToTable("DIRECTORIES"); modelBuilder.Entity().ToTable("DIRECTORY_MEMBERS"); - modelBuilder.Entity().ToTable("ORGANIZATIONS_MEMBERS"); modelBuilder.Entity().ToTable("LOGS"); modelBuilder.Entity().ToTable("POLICIES"); diff --git a/LIN.Identity/Data/Directories/Directories.cs b/LIN.Identity/Data/Directories/Directories.cs index 82765ae..bf69daf 100644 --- a/LIN.Identity/Data/Directories/Directories.cs +++ b/LIN.Identity/Data/Directories/Directories.cs @@ -62,6 +62,20 @@ public static async Task> Read(int id, int iden } + + public static async Task> ReadByIdentity(int id) + { + + // Obtiene la conexión + var (context, connectionKey) = Conexión.GetOneConnection(); + + var res = await ReadByIdentity(id, context); + context.CloseActions(connectionKey); + return res; + + } + + #endregion @@ -117,14 +131,13 @@ public static async Task> ReadAll(int identityI try { - var (_, identities) = await Queries.Directories.Get(identityId); + var (_, identities,_) = await Queries.Directories.Get(identityId); // Directorios. var directories = await (from directoryMember in context.DataBase.DirectoryMembers where identities.Contains(directoryMember.IdentityId) select new DirectoryMember { - Rol = directoryMember.Rol, DirectoryId = directoryMember.DirectoryId, Directory = new() { @@ -174,7 +187,36 @@ public static async Task> Read(int id, int iden { // Consulta. - var query = Queries.Accounts.GetDirectory(id, identityContext, context); + var query = Queries.Identities.GetDirectory(id, identityContext, context); + + // Obtiene el usuario + var result = await query.FirstOrDefaultAsync(); + + // Si no existe el modelo + if (result == null) + return new(Responses.NotExistAccount); + + return new(Responses.Success, result); + } + catch (Exception) + { + } + + return new(); + } + + + + + public static async Task> ReadByIdentity(int id, Conexión context) + { + + // Ejecución + try + { + + // Consulta. + var query = Queries.Identities.GetDirectoryByIdentity(id, context); // Obtiene el usuario var result = await query.FirstOrDefaultAsync(); diff --git a/LIN.Identity/Data/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Directories/DirectoryMembers.cs index 76f0210..2a366a7 100644 --- a/LIN.Identity/Data/Directories/DirectoryMembers.cs +++ b/LIN.Identity/Data/Directories/DirectoryMembers.cs @@ -107,7 +107,6 @@ public static async Task> ReadMembers(int direc where directoryMember.DirectoryId == directoryId select new DirectoryMember { - Rol = directoryMember.Rol, DirectoryId = directoryMember.DirectoryId, Identity = new() { diff --git a/LIN.Identity/Data/Identities.cs b/LIN.Identity/Data/Identities.cs new file mode 100644 index 0000000..c7cac56 --- /dev/null +++ b/LIN.Identity/Data/Identities.cs @@ -0,0 +1,56 @@ +namespace LIN.Identity.Data; + + +public class Identities +{ + + + #region Abstracciones + + + + + public static async Task> Read(int identity) + { + var (context, contextKey) = Conexión.GetOneConnection(); + + var res = await Read(identity, context); + context.CloseActions(contextKey); + return res; + } + + + + + #endregion + + + + + public static async Task> Read(int id, Conexión context) + { + + // Ejecución + try + { + + var ids = await (from identity in context.DataBase.Identities + where identity.Id == id + select identity).FirstOrDefaultAsync(); + + + if (ids == null) + return new(Responses.NotRows); + + return new(Responses.Success, ids); + + } + catch + { + } + + return new(); + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Data/Organizations/Members.cs b/LIN.Identity/Data/Organizations/Members.cs index 6e91000..39d95a6 100644 --- a/LIN.Identity/Data/Organizations/Members.cs +++ b/LIN.Identity/Data/Organizations/Members.cs @@ -24,17 +24,11 @@ public static async Task> ReadAll(int id) - // - /// Crea una cuenta en una organización - /// - /// Modelo - /// ID de la organización - /// Rol dentro de la organización - public static async Task> Create(AccountModel data, int orgID, OrgRoles rol) + public static async Task> Create(AccountModel data, int dir, DirectoryRoles rol) { var (context, contextKey) = Conexión.GetOneConnection(); - var res = await Create(data, orgID, rol, context); + var res = await Create(data, dir, rol, context); context.CloseActions(contextKey); return res; } @@ -51,7 +45,7 @@ public static async Task> Create(AccountModel data /// ID de la organización /// Rol dentro de la organización /// Contexto de conexión - public static async Task> Create(AccountModel data, int orgID, OrgRoles rol, Conexión context) + public static async Task> Create(AccountModel data, int directory, DirectoryRoles rol, Conexión context) { data.ID = 0; @@ -62,46 +56,37 @@ public static async Task> Create(AccountModel data try { + // Guardar la cuenta. + context.DataBase.Accounts.Add(data); + context.DataBase.SaveChanges(); + // Obtiene la organización. - OrganizationModel? organization = await (from org in context.DataBase.Organizations - where org.ID == orgID - select org).FirstOrDefaultAsync(); + int directoryId = await (from org in context.DataBase.Organizations + where org.DirectoryId == directory + select org.DirectoryId).FirstOrDefaultAsync(); // No existe la organización. - if (organization == null) + if (directoryId <= 0) { transaction.Rollback(); return new(Responses.NotRows); } - // Modelo de acceso. - data.OrganizationAccess = new() - { - Member = data, - Rol = rol, - Organization = organization - }; - - - // Guardar la cuenta. - await context.DataBase.Accounts.AddAsync(data); - context.DataBase.SaveChanges(); - - // Miembro del directorio. - var memberOnDirectory = new DirectoryMember + // Modelo del integrante. + var member = new DirectoryMember() { - Identity= data.Identity, Directory = new() { - ID = organization.DirectoryId - } + ID = directoryId + }, + Identity = data.Identity }; - // Guardar el miembro en el directorio. - context.DataBase.Attach(memberOnDirectory.Directory); - context.DataBase.DirectoryMembers.Add(memberOnDirectory); + // El directorio ya existe. + context.DataBase.Attach(member.Directory); // Guardar cambios. + context.DataBase.DirectoryMembers.Add(member); context.DataBase.SaveChanges(); // Enviar la transacción. @@ -136,32 +121,30 @@ public static async Task> ReadAll(int id, Conexió { // Organización - var org = from O in context.DataBase.OrganizationAccess - where O.Organization.ID == id - select new AccountModel - { - Creación = O.Member.Creación, - ID = O.Member.ID, - Nombre = O.Member.Nombre, - Identity = new() - { - Id = O.Member.Identity.Id, - Type = O.Member.Identity.Type, - Unique = O.Member.Identity.Unique - }, - OrganizationAccess = new() - { - Rol = O.Member.OrganizationAccess == null ? OrgRoles.Undefine : O.Member.OrganizationAccess.Rol - } - }; - - var orgList = await org.ToListAsync(); - - // Email no existe - if (org == null) - return new(Responses.NotRows); - - return new(Responses.Success, orgList); + var members = from org in context.DataBase.Organizations + where org.ID == id + join m in context.DataBase.DirectoryMembers + on org.DirectoryId equals m.DirectoryId + select org.Directory; + + + var accounts = await (from account in context.DataBase.Accounts + join m in members + on account.IdentityId equals m.IdentityId + select new AccountModel + { + Creación = account.Creación, + ID = account.ID, + Nombre = account.Nombre, + Identity = new() + { + Id = account.Identity.Id, + Type = IdentityTypes.Account, + Unique = account.Identity.Unique + } + }).ToListAsync(); + + return new(Responses.Success, accounts); } catch { diff --git a/LIN.Identity/Data/Organizations/Organizations.cs b/LIN.Identity/Data/Organizations/Organizations.cs index 1c86290..cda1404 100644 --- a/LIN.Identity/Data/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Organizations/Organizations.cs @@ -39,6 +39,19 @@ public static async Task> Read(int id) + public static async Task> FindBaseDirectory(int id) + { + var (context, contextKey) = Conexión.GetOneConnection(); + + var res = await FindBaseDirectory(id, context); + context.CloseActions(contextKey); + return res; + } + + + + + #endregion @@ -60,95 +73,51 @@ public static async Task> Create(Organization try { - // Lista de cuentas. - List accounts = []; + // Guardar datos. + await context.DataBase.Organizations.AddAsync(data); - // Enumerar las cuentas actuales. - foreach (var account in data.Members.Select(t => t.Member)) - { - AccountModel accountModel = new() - { - Birthday = account.Birthday, - Contraseña = account.Contraseña, - Creación = account.Creación, - Estado = account.Estado, - ID = 0, - Identity = account.Identity, - Insignia = account.Insignia, - Nombre = account.Nombre, - Visibilidad = account.Visibilidad, - Rol = account.Rol, - Perfil = account.Perfil, - OrganizationAccess = null, - IdentityId = 0, - }; - - accounts.Add(accountModel); - context.DataBase.Accounts.Add(accountModel); - } - - // Guardar cambios. + // Guardar en BD. context.DataBase.SaveChanges(); - // Modelo la organización. - OrganizationModel org = new() + // Cuenta de administración. + AccountModel account = new() { + Contraseña = "root123", + Creación = DateTime.Now, + Estado = AccountStatus.Normal, + Gender = Genders.Undefined, ID = 0, - Domain = data.Domain, - Name = data.Name, - IsPublic = data.IsPublic, - Members = [], - Directory = new() + Identity = new() { - Creación = DateTime.Now, - ID = 0, - Identity = new() - { - Unique = data.Directory.Identity.Unique, - Type = IdentityTypes.Directory - }, - Nombre = data.Directory.Nombre, - IdentityId = 0, - Policies = - [ - new PolicyModel - { - Creation = DateTime.Now, - Id =0, - Type = PolicyTypes.PasswordLength, - ValueJson = """ {"length": 8} """ - } - ] - } + DirectoryMembers = [], + Id = 0, + Type = IdentityTypes.Account, + Unique = $"root@{data.Directory.Identity.Unique}", + Roles = [new() { Rol = DirectoryRoles.SuperManager }], + }, + Insignia = AccountBadges.None, + Nombre = $"Root user {data.Directory.Identity.Unique}", + Perfil = [], + Rol = AccountRoles.User, + Visibilidad = AccountVisibility.Hidden, + IdentityId = 0 }; + // Procesar la cuenta. + account = Account.Process(account); - // Agregar miembros. - foreach (var account in accounts) - { - // Miembros de la organización. - org.Members.Add(new() - { - Member = account, - Organization = org, - Rol = OrgRoles.SuperManager - }); - - // Miembros del directorio. - org.Directory.Members.Add(new() - { - Identity = account.Identity, - Directory = org.Directory, - Rol = DirectoryRoles.Administrator - }); - } + // Guardar la cuenta. + context.DataBase.Accounts.Add(account); + // Agregar el miembro. + data.Directory.Members.Add(new() + { + Directory = data.Directory, + Identity = account.Identity + }); - // Agregar el modelo. - await context.DataBase.Organizations.AddAsync(org); - // Guardar en la BD. - context.DataBase.SaveChanges(); + context.DataBase.SaveChanges(); ; // Commit cambios. transaction.Commit(); @@ -183,15 +152,12 @@ public static async Task> Read(int id, Conexi // Query var org = await (from E in context.DataBase.Organizations where E.ID == id - select new OrganizationModel { Directory = null!, DirectoryId = id, - Domain = E.Domain, ID = E.ID, IsPublic = E.IsPublic, - Members = null!, Name = E.Name, }).FirstOrDefaultAsync(); @@ -210,4 +176,33 @@ public static async Task> Read(int id, Conexi + public static async Task> FindBaseDirectory(int identity, Conexión context) + { + + // Ejecución + try + { + + // Roles de integrante base. + DirectoryRoles[] roles = [DirectoryRoles.AccountsOperator, DirectoryRoles.Operator, DirectoryRoles.Manager, DirectoryRoles.SuperManager, DirectoryRoles.Regular]; + + // Consulta. + var query = await (from org in context.DataBase.Organizations + select org.Directory.Members.Where(t => t.IdentityId == identity).FirstOrDefault()).FirstOrDefaultAsync(); + + // Email no existe + if (query == null) + return new(Responses.NotRows); + + return new(Responses.Success, query); + } + catch + { + } + + return new(); + } + + + } \ No newline at end of file diff --git a/LIN.Identity/Data/Policies.cs b/LIN.Identity/Data/Policies.cs index 8cc8005..ae0f019 100644 --- a/LIN.Identity/Data/Policies.cs +++ b/LIN.Identity/Data/Policies.cs @@ -144,7 +144,7 @@ public static async Task> ValidatePermission(int identity, try { - var (directories, _) = await Queries.Directories.Get(identity); + var (directories, _, _) = await Queries.Directories.Get(identity); // Consulta. diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs index 8d77a3e..5c4a9c8 100644 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ b/LIN.Identity/Hubs/PasskeyHub.cs @@ -266,13 +266,6 @@ public async Task ReceiveRequest(PassKeyModel modelo) Identity = new() { Unique = userUnique - }, - OrganizationAccess = new() - { - Organization = new() - { - ID = orgID - } } }, app.Model.ID); diff --git a/LIN.Identity/FilterModels/Account.cs b/LIN.Identity/Models/Account.cs similarity index 89% rename from LIN.Identity/FilterModels/Account.cs rename to LIN.Identity/Models/Account.cs index cb16299..7d26a91 100644 --- a/LIN.Identity/FilterModels/Account.cs +++ b/LIN.Identity/Models/Account.cs @@ -1,19 +1,16 @@ -namespace LIN.Identity.FilterModels; +namespace LIN.Identity.Models; public class Account { - public int ContextUser { get; set; } - public int ContextOrg { get; set; } + public int ContextAccount { get; set; } public bool IsAdmin { get; set; } public bool SensibleInfo { get; set; } public IncludeOrg IncludeOrg { get; set; } = IncludeOrg.None; public FindOn FindOn { get; set; } = FindOn.AllAccount; public IncludeOrgLevel OrgLevel { get; set; } = IncludeOrgLevel.Basic; - - } public enum IncludeOrg diff --git a/LIN.Identity/Models/JwtModel.cs b/LIN.Identity/Models/JwtModel.cs new file mode 100644 index 0000000..cdc28f3 --- /dev/null +++ b/LIN.Identity/Models/JwtModel.cs @@ -0,0 +1,43 @@ +namespace LIN.Identity.Models; + + +public class JwtModel +{ + + /// + /// El token esta autenticado. + /// + public bool IsAuthenticated { get; set; } + + + /// + /// Usuario. + /// + public string Unique { get; set; } = string.Empty; + + + /// + /// Id de la cuenta. + /// + public int AccountId { get; set; } + + + /// + /// Id de la identidad. + /// + public int IdentityId { get; set; } + + + /// + /// Id de la organización. + /// + public int OrganizationId { get; set; } + + + /// + /// Id de la aplicación. + /// + public int ApplicationId { get; set; } + + +} \ No newline at end of file diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index 5c64735..bef3492 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -26,7 +26,7 @@ - var sqlConnection = builder.Configuration["ConnectionStrings:somee"] ?? string.Empty; + var sqlConnection = builder.Configuration["ConnectionStrings:local"] ?? string.Empty; // Servicio de BD diff --git a/LIN.Identity/Queries/Directories.cs b/LIN.Identity/Queries/Directories.cs index 3a4e82f..ca373aa 100644 --- a/LIN.Identity/Queries/Directories.cs +++ b/LIN.Identity/Queries/Directories.cs @@ -9,19 +9,39 @@ public class Directories /// Obtiene las identidades y directorios. /// /// Identidad base - public static async Task<(List directories, List identities)> Get(int identity) + public static async Task<(List directories, List identities, List roles)> Get(int identity) { List identities = [identity]; List directories = []; + List roles = []; var (context, contextKey) = Conexión.GetOneConnection(); - await Get(identity, context, identities, directories); + await Get(identity, context, identities, directories, roles); context.CloseActions(contextKey); - return (directories, identities); + return (directories, identities, roles); } + + + + + + + + + + + + + + + + + + + /// @@ -31,7 +51,8 @@ public class Directories /// Contexto /// Lista de identidades. /// Directorios - private static async Task Get(int identityBase, Conexión context, List identities, List directories) + /// Roles + private static async Task Get(int identityBase, Conexión context, List identities, List directories, List roles) { // Consulta. var query = from DM in context.DataBase.DirectoryMembers @@ -40,7 +61,8 @@ private static async Task Get(int identityBase, Conexión context, List ide select new { Identity = DM.Directory.Identity.Id, - Directory = DM.Directory.ID + Directory = DM.Directory.ID, + Roles = DM.Identity.Roles.Select(x => x.Rol).DistinctBy(t => t) }; // Si hay elementos. @@ -49,9 +71,10 @@ private static async Task Get(int identityBase, Conexión context, List ide var local = query.ToList(); identities.AddRange(local.Select(t => t.Identity)); directories.AddRange(local.Select(t => t.Directory)); + roles.AddRange(local.Select(t => t.Roles).SelectMany(t => t)); foreach (var id in local) - await Get(id.Identity, context, identities, directories); + await Get(id.Identity, context, identities, directories, roles); } diff --git a/LIN.Identity/Queries/Accounts.cs b/LIN.Identity/Queries/Identities.cs similarity index 67% rename from LIN.Identity/Queries/Accounts.cs rename to LIN.Identity/Queries/Identities.cs index e3aef2f..84d0c99 100644 --- a/LIN.Identity/Queries/Accounts.cs +++ b/LIN.Identity/Queries/Identities.cs @@ -1,12 +1,13 @@ namespace LIN.Identity.Queries; -public class Accounts +public class Identities { + /// - /// Query general para todas las cuentas + /// Consulta sobre todas lac cuentas. /// /// Contexto DB public static IQueryable GetAccounts(Conexión context) @@ -23,11 +24,12 @@ public static IQueryable GetAccounts(Conexión context) /// - /// Solo tiene en cuenta cuentas validas + /// Consulta sobre las cuentas validas. /// /// Contexto DB public static IQueryable GetValidAccounts(Conexión context) { + // Query general IQueryable accounts = from account in GetAccounts(context) where account.Estado == AccountStatus.Normal @@ -46,13 +48,13 @@ public static IQueryable GetValidAccounts(Conexión context) - public static IQueryable GetAccounts(int id, FilterModels.Account filters, Conexión context) + public static IQueryable GetAccounts(int id, Models.Account filters, Conexión context) { // Query general IQueryable accounts; - if (filters.FindOn == FilterModels.FindOn.StableAccounts) + if (filters.FindOn == Models.FindOn.StableAccounts) accounts = from account in GetValidAccounts(context) where account.ID == id select account; @@ -69,14 +71,37 @@ public static IQueryable GetAccounts(int id, FilterModels.Account } + public static IQueryable GetAccountsByIdentity(int id, Models.Account filters, Conexión context) + { + + // Query general + IQueryable accounts; + + if (filters.FindOn == Models.FindOn.StableAccounts) + accounts = from account in GetValidAccounts(context) + where account.Identity.Id == id + select account; + else + accounts = from account in GetAccounts(context) + where account.Identity.Id == id + select account; + + // Armar el modelo + accounts = BuildModel(accounts, filters); + + // Retorno + return accounts; + + } + - public static IQueryable GetAccounts(string user, FilterModels.Account filters, Conexión context) + public static IQueryable GetAccounts(string user, Models.Account filters, Conexión context) { // Query general IQueryable accounts; - if (filters.FindOn == FilterModels.FindOn.StableAccounts) + if (filters.FindOn == Models.FindOn.StableAccounts) accounts = from account in GetValidAccounts(context) where account.Identity.Unique == user select account; @@ -94,7 +119,7 @@ public static IQueryable GetAccounts(string user, FilterModels.Acc } - public static IQueryable GetAccounts(IEnumerable ids, FilterModels.Account filters, Conexión context) + public static IQueryable GetAccounts(IEnumerable ids, Models.Account filters, Conexión context) { // Query general @@ -111,7 +136,7 @@ where ids.Contains(account.ID) } - public static IQueryable Search(string pattern, FilterModels.Account filters, Conexión context) + public static IQueryable Search(string pattern, Models.Account filters, Conexión context) { // Query general @@ -153,6 +178,26 @@ public static IQueryable GetDirectory(int id, int identityConte } + public static IQueryable GetDirectoryByIdentity(int id, Conexión context) + { + + // Query general + IQueryable accounts; + + + var directory = from dm in context.DataBase.DirectoryMembers + where dm.Directory.Identity.Id == id + select dm; + + // Armar el modelo + accounts = BuildModel(directory); + + // Retorno + return accounts; + + } + + @@ -164,7 +209,7 @@ public static IQueryable GetDirectory(int id, int identityConte /// /// Query base /// Filtros - private static IQueryable BuildModel(IQueryable query, FilterModels.Account filters) + private static IQueryable BuildModel(IQueryable query, Models.Account filters) { byte[] profile = @@ -195,63 +240,32 @@ private static IQueryable BuildModel(IQueryable quer // Nombre. Nombre = account.Visibilidad == AccountVisibility.Visible - || (account.OrganizationAccess != null - && account.OrganizationAccess.Organization.ID == filters.ContextOrg) - || account.ID == filters.ContextUser + || account.ID == filters.ContextAccount || filters.IsAdmin ? account.Nombre : "Usuario privado", // Cumpleaños. Birthday = account.Visibilidad == AccountVisibility.Visible - || (account.OrganizationAccess != null - && account.OrganizationAccess.Organization.ID == filters.ContextOrg) - || account.ID == filters.ContextUser + || account.ID == filters.ContextAccount || filters.IsAdmin ? account.Birthday : default, // Creación. Creación = account.Visibilidad == AccountVisibility.Visible - || (account.OrganizationAccess != null - && account.OrganizationAccess.Organization.ID == filters.ContextOrg) - || account.ID == filters.ContextUser + || account.ID == filters.ContextAccount || filters.IsAdmin ? account.Creación : default, // Perfil. Perfil = account.Visibilidad == AccountVisibility.Visible - || (account.OrganizationAccess != null - && account.OrganizationAccess.Organization.ID == filters.ContextOrg) - || account.ID == filters.ContextUser + || account.ID == filters.ContextAccount || filters.IsAdmin ? account.Perfil : profile, - - // Organización. - OrganizationAccess = account.OrganizationAccess != null - && filters.IncludeOrg != FilterModels.IncludeOrg.None - && (account.Visibilidad == AccountVisibility.Visible - && filters.IncludeOrg == FilterModels.IncludeOrg.Include - || account.OrganizationAccess.Organization.ID == filters.ContextOrg - || account.ID == filters.ContextUser - || filters.IsAdmin) - ? - new OrganizationAccessModel() - { - Rol = account.OrganizationAccess.Rol, - Organization = filters.OrgLevel == FilterModels.IncludeOrgLevel.Advance ? new() - { - ID = account.OrganizationAccess.Organization.ID, - Domain = !account.OrganizationAccess.Organization.IsPublic && !filters.IsAdmin && filters.IncludeOrg == FilterModels.IncludeOrg.IncludeIf && filters.ContextOrg != account.OrganizationAccess.Organization.ID ? "" - : account.OrganizationAccess.Organization.Domain, - Name = !account.OrganizationAccess.Organization.IsPublic && !filters.IsAdmin && filters.IncludeOrg == FilterModels.IncludeOrg.IncludeIf && filters.ContextOrg != account.OrganizationAccess.Organization.ID - ? "Organización privada" : account.OrganizationAccess.Organization.Name - } : new(), - Member = null!, - } - : null + }; return queryFinal; @@ -271,7 +285,6 @@ private static IQueryable BuildModel(IQueryable> ValidateAccess(int account, Model = IamLevels.NotAccess }; - // App publica. if (resource.Model.DirectoryId <= 0) - { return new() { Response = Responses.Success, Model = IamLevels.Visualizer }; - } + @@ -47,7 +45,7 @@ public static async Task> ValidateAccess(int account, select a.IdentityId).FirstOrDefault(); - var (directories, _) = await Queries.Directories.Get(identity); + var (directories, _,_) = await Queries.Directories.Get(identity); // Tiene acceso. diff --git a/LIN.Identity/Services/Iam/Directories.cs b/LIN.Identity/Services/Iam/Directories.cs deleted file mode 100644 index e1be994..0000000 --- a/LIN.Identity/Services/Iam/Directories.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace LIN.Identity.Services.Iam; - - -public static class Directories -{ - - - - - - public static async Task> ValidateAccess(int identity, int directory) - { - - - var (_, identities) = await Queries.Directories.Get(identity); - - - - var (context, contextKey) = Conexión.GetOneConnection(); - - - // Encuentra el acceso en order Admin -> User. - var dirM = (from DM in context.DataBase.DirectoryMembers - where DM.DirectoryId == directory - && identities.Contains(DM.IdentityId) - select DM).OrderByDescending(t => t.Rol).FirstOrDefault(); - - if (dirM == null) - return new() - { - Model = IamLevels.NotAccess - }; - - if (dirM.Rol == DirectoryRoles.Administrator) - return new() - { - Model = IamLevels.Privileged - }; - - if (dirM.Rol == DirectoryRoles.Guest) - return new() - { - Model = IamLevels.Guest - }; - - return new() - { - Model = IamLevels.Visualizer - }; - - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Services/Jwt.cs b/LIN.Identity/Services/Jwt.cs index a6ce96b..db8ee2b 100644 --- a/LIN.Identity/Services/Jwt.cs +++ b/LIN.Identity/Services/Jwt.cs @@ -1,4 +1,5 @@ -using System.IdentityModel.Tokens.Jwt; +using LIN.Identity.Models; +using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; namespace LIN.Identity.Services; @@ -46,7 +47,6 @@ internal static string Generate(AccountModel user, int appID) new Claim(ClaimTypes.PrimarySid, user.ID.ToString()), new Claim(ClaimTypes.NameIdentifier, user.Identity.Unique), new Claim(ClaimTypes.Role, ((int)user.Rol).ToString()), - new Claim(ClaimTypes.UserData, (user.OrganizationAccess?.Organization.ID).ToString() ?? ""), new Claim(ClaimTypes.GroupSid, (user.Identity.Id).ToString() ?? ""), new Claim(ClaimTypes.Authentication, appID.ToString()) }; @@ -67,7 +67,7 @@ internal static string Generate(AccountModel user, int appID) /// Valida un JSON Web token /// /// Token a validar - internal static (bool isValid, string user, int userID, int orgID, int appID, int identity) Validate(string token) + internal static JwtModel Validate(string token) { try { @@ -75,7 +75,10 @@ internal static (bool isValid, string user, int userID, int orgID, int appID, in // Comprobación if (string.IsNullOrWhiteSpace(token)) - return (false, string.Empty, 0, 0, 0, 0); + return new() + { + IsAuthenticated = false + }; // Configurar la clave secreta var key = Encoding.ASCII.GetBytes(JwtKey); @@ -104,13 +107,19 @@ internal static (bool isValid, string user, int userID, int orgID, int appID, in // _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.PrimarySid)?.Value, out var id); - _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.UserData)?.Value, out var orgID); _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Authentication)?.Value, out var appID); _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.GroupSid)?.Value, out var identityId); // Devuelve una respuesta exitosa - return (true, user ?? string.Empty, id, orgID, appID, identityId); + return new() + { + IsAuthenticated = true, + AccountId = id, + ApplicationId = appID, + IdentityId = identityId, + Unique = user ?? "" + }; } catch (SecurityTokenException) @@ -121,7 +130,10 @@ internal static (bool isValid, string user, int userID, int orgID, int appID, in } catch { } - return (false, string.Empty, 0, 0, 0, 0); + return new() + { + IsAuthenticated = false + }; } diff --git a/LIN.Identity/Services/Login/LoginOnOrg.cs b/LIN.Identity/Services/Login/LoginOnOrg.cs deleted file mode 100644 index c2363ae..0000000 --- a/LIN.Identity/Services/Login/LoginOnOrg.cs +++ /dev/null @@ -1,157 +0,0 @@ -namespace LIN.Identity.Services.Login; - - -public class LoginOnOrg : LoginService -{ - - - /// - /// Acceso a la organización - /// - private OrganizationAccessModel OrganizationAccess { get; set; } = new(); - - - - - /// - /// Nuevo login - /// - /// Datos de la cuenta - /// Llave - /// Contraseña - /// Tipo de inicio - public LoginOnOrg(AccountModel? account, string? application, string password, LoginTypes loginType = LoginTypes.Credentials) : base(account, application, password, loginType) - { - } - - - - /// - /// Valida parámetros necesarios para iniciar sesión en una organización - /// - private bool ValidateParams() - { - return Account.OrganizationAccess != null; - } - - - - /// - /// Valida parámetros necesarios para iniciar sesión en una organización - /// - private async Task LoadOrganization() - { - - var z = Account.OrganizationAccess?.Organization.ID; - - var orgResponse = await Data.Organizations.Organizations.Read(z ?? 0); - - - if (orgResponse.Response != Responses.Success) - { - return false; - } - - OrganizationAccess.Rol = Account.OrganizationAccess!.Rol; - OrganizationAccess.Organization = orgResponse.Model; - - return true; - - } - - - - /// - /// Valida las políticas de la organización - /// - private async Task ValidatePolicies() - { - - //// Si el inicio de sesión fue desactivado por la organización - //if (!OrganizationAccess!.Organization.LoginAccess && !OrganizationAccess.Rol.IsAdmin()) - // return new() - // { - // Message = "Tu organización a deshabilitado el inicio de sesión temporalmente.", - // Response = Responses.LoginBlockedByOrg - // }; - - - //// Si la organización tiene lista blanca - //if (OrganizationAccess.Organization.HaveWhiteList) - //{ - // var whiteList = await ValidateWhiteList(); - // if (!whiteList) - // return new() - // { - // Message = "Tu organización no permite iniciar sesión en esta aplicación.", - // Response = Responses.UnauthorizedByOrg - // }; - //} - - return new(Responses.Success); - - } - - - - public override async Task Login() - { - - // Valida la aplicación - var validateParams = ValidateParams(); - - // Retorna el error - if (!validateParams) - return new() - { - Message = "Este usuario no pertenece a una organización.", - Response = Responses.Undefined - }; - - - // Validar la organización - var validateAccess = await LoadOrganization(); - - // Retorna el error - if (!validateAccess) - return new() - { - Message = "Hubo un error al validar la organización.", - Response = Responses.Undefined - }; - - - - - // Validar credenciales y estado - var validateAccount = Validate(); - - // Retorna el error - if (validateAccount.Response != Responses.Success) - return validateAccount; - - - // Valida la aplicación - var validateApp = await ValidateApp(); - - // Retorna el error - if (validateApp.Response != Responses.Success) - return validateApp; - - // Validar las políticas - var validatePolicies = await ValidatePolicies(); - - // Retorna el error - if (validatePolicies.Response != Responses.Success) - return validatePolicies; - - // Genera el login - GenerateLogin(); - - return new(Responses.Success); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Services/OrgRoleExt.cs b/LIN.Identity/Services/OrgRoleExt.cs deleted file mode 100644 index 63f42bd..0000000 --- a/LIN.Identity/Services/OrgRoleExt.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace LIN.Identity.Services; - - -public static class OrgRoleExt -{ - - - - public static bool IsGretter(this OrgRoles me, OrgRoles other) - { - - switch (me) - { - - case OrgRoles.SuperManager: - { - return false; - } - - case OrgRoles.Manager: - { - return other == OrgRoles.SuperManager; - } - - case OrgRoles.Regular: - { - return other == OrgRoles.SuperManager || other == OrgRoles.Manager; - } - - case OrgRoles.Guest: - { - return other == OrgRoles.SuperManager || other == OrgRoles.Manager; - } - - case OrgRoles.Undefine: - { - return true; - } - - } - - return false; - - - - } - - - - public static bool IsAdmin(this OrgRoles rol) - { - return rol == OrgRoles.SuperManager || rol == OrgRoles.Manager; - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Validations/Account.cs b/LIN.Identity/Validations/Account.cs index 1d93696..c85338b 100644 --- a/LIN.Identity/Validations/Account.cs +++ b/LIN.Identity/Validations/Account.cs @@ -16,14 +16,13 @@ public static AccountModel Process(AccountModel modelo) { ID = 0, Nombre = modelo.Nombre, - OrganizationAccess = modelo.OrganizationAccess, Identity = new() { Id = 0, Type = IdentityTypes.Account, - Unique = modelo.Identity.Unique + Unique = modelo.Identity.Unique, + DirectoryMembers = [] }, - IdentityId = 0, Gender = modelo.Gender, Visibilidad = modelo.Visibilidad, diff --git a/LIN.Identity/Validations/AccountPassword.cs b/LIN.Identity/Validations/AccountPassword.cs index 235aac0..d802a7f 100644 --- a/LIN.Identity/Validations/AccountPassword.cs +++ b/LIN.Identity/Validations/AccountPassword.cs @@ -17,7 +17,7 @@ public static async Task ValidatePassword(int identity, string password) var (context, contextKey) = Conexión.GetOneConnection(); // Directorios. - var (directories, _) = await Queries.Directories.Get(identity); + var (directories, _, _) = await Queries.Directories.Get(identity); // Política. var policy = await (from p in context.DataBase.Policies diff --git a/LIN.Identity/Validations/Roles.cs b/LIN.Identity/Validations/Roles.cs new file mode 100644 index 0000000..dff7edc --- /dev/null +++ b/LIN.Identity/Validations/Roles.cs @@ -0,0 +1,286 @@ +namespace LIN.Identity.Validations; + +public static class Roles +{ + + /// + /// Confirmar si un rol tiene permisos de ver el directorio. + /// + /// Rol a confirmar. + public static bool View(DirectoryRoles rol) + { + + DirectoryRoles[] roles = + [ + DirectoryRoles.System, + DirectoryRoles.SuperManager, + DirectoryRoles.Manager, + DirectoryRoles.Operator, + DirectoryRoles.AccountsOperator, + DirectoryRoles.Regular, + DirectoryRoles.Guest, + DirectoryRoles.RoyalGuest + ]; + + return roles.Contains(rol); + + } + + + + /// + /// Confirmar si un rol tiene permisos de ver los integrantes. + /// + /// Rol a confirmar. + public static bool ViewMembers(DirectoryRoles rol) + { + + DirectoryRoles[] roles = + [ + DirectoryRoles.System, + DirectoryRoles.SuperManager, + DirectoryRoles.Manager, + DirectoryRoles.Operator, + DirectoryRoles.AccountsOperator, + DirectoryRoles.Regular, + DirectoryRoles.Guest, + DirectoryRoles.RoyalGuest + ]; + + return roles.Contains(rol); + + } + + + + /// + /// Confirmar si un rol tiene permisos para alterar los integrantes. + /// + /// Rol a confirmar. + public static bool AlterMembers(DirectoryRoles rol) + { + + DirectoryRoles[] roles = + [ + DirectoryRoles.System, + DirectoryRoles.SuperManager, + DirectoryRoles.Manager, + DirectoryRoles.Operator, + DirectoryRoles.AccountsOperator, + DirectoryRoles.RoyalGuest + ]; + + return roles.Contains(rol); + + } + + + /// + /// Confirmar si un rol tiene permisos de saltarse las directivas. + /// + /// Rol a confirmar. + public static bool UsePolicy(DirectoryRoles rol) + { + + DirectoryRoles[] roles = + [ + DirectoryRoles.System, + DirectoryRoles.Guest, + DirectoryRoles.RoyalGuest + ]; + + return roles.Contains(rol); + + } + + + /// + /// Confirmar si un rol tiene permisos de crear directivas. + /// + /// Rol a confirmar. + public static bool CreatePolicy(DirectoryRoles rol) + { + + DirectoryRoles[] roles = + [ + DirectoryRoles.System, + DirectoryRoles.SuperManager, + DirectoryRoles.Manager, + DirectoryRoles.Operator, + DirectoryRoles.RoyalGuest + ]; + + return roles.Contains(rol); + + } + + + + /// + /// Confirmar si un rol tiene permisos de alterar datos como nombres de la organización etc... + /// + /// Rol a confirmar. + public static bool DataAlter(DirectoryRoles rol) + { + + DirectoryRoles[] roles = + [ + DirectoryRoles.System, + DirectoryRoles.SuperManager, + DirectoryRoles.Manager, + ]; + + return roles.Contains(rol); + + } + + + /// + /// Confirmar si un rol tiene permisos de saltarse las directivas. + /// + /// Rol a confirmar. + public static bool ViewPolicy(DirectoryRoles rol) + { + + DirectoryRoles[] roles = + [ + DirectoryRoles.System, + DirectoryRoles.SuperManager, + DirectoryRoles.Manager, + DirectoryRoles.Operator, + DirectoryRoles.AccountsOperator, + DirectoryRoles.Regular, + DirectoryRoles.RoyalGuest + ]; + + return roles.Contains(rol); + + } + + + + + + + + /// + /// Confirmar si un rol tiene permisos de ver el directorio. + /// + /// Rol a confirmar. + public static bool View(IEnumerable roles) + { + + // Recorrer roles. + foreach(var rol in roles) + if (View(rol)) + return true; + + // No tiene permisos. + return false; + } + + + + /// + /// Confirmar si un rol tiene permisos de ver los integrantes. + /// + /// Rol a confirmar. + public static bool ViewMembers(IEnumerable roles) + { + + // Recorrer roles. + foreach (var rol in roles) + if (ViewMembers(rol)) + return true; + + // No tiene permisos. + return false; + } + + + + /// + /// Confirmar si un rol tiene permisos para alterar los integrantes. + /// + /// Rol a confirmar. + public static bool AlterMembers(IEnumerable roles) + { + // Recorrer roles. + foreach (var rol in roles) + if (AlterMembers(rol)) + return true; + + // No tiene permisos. + return false; + + } + + + /// + /// Confirmar si un rol tiene permisos de saltarse las directivas. + /// + /// Rol a confirmar. + public static bool UsePolicy(IEnumerable roles) + { + + // Recorrer roles. + foreach (var rol in roles) + if (UsePolicy(rol)) + return true; + + // No tiene permisos. + return false; + + } + + + /// + /// Confirmar si un rol tiene permisos de crear directivas. + /// + /// Rol a confirmar. + public static bool CreatePolicy(IEnumerable roles) + { + + // Recorrer roles. + foreach (var rol in roles) + if (CreatePolicy(rol)) + return true; + + // No tiene permisos. + return false; + + } + + + + /// + /// Confirmar si un rol tiene permisos de alterar datos como nombres de la organización etc... + /// + /// Rol a confirmar. + public static bool DataAlter(IEnumerable roles) + { + // Recorrer roles. + foreach (var rol in roles) + if (DataAlter(rol)) + return true; + + // No tiene permisos. + return false; + + } + + + + public static bool ViewPolicy(IEnumerable roles) + { + // Recorrer roles. + foreach (var rol in roles) + if (ViewPolicy(rol)) + return true; + + // No tiene permisos. + return false; + + } + +} \ No newline at end of file From 2d75889eb59880f0c2cf2835c2997b296e9d9d31 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 26 Dec 2023 08:47:15 -0500 Subject: [PATCH 060/178] Integraciones --- .../Areas/Accounts/AccountController.cs | 1 - .../Areas/Accounts/AccountLogsController.cs | 2 +- .../Accounts/AccountSecurityController.cs | 5 +- .../Applications/ApplicationController.cs | 2 +- .../Areas/Directories/DirectoryController.cs | 14 +-- .../Directories/DirectoryMemberController.cs | 8 +- .../Areas/Organizations/MemberController.cs | 8 +- .../Organizations/OrganizationController.cs | 4 +- .../Areas/Policies/PolicyController.cs | 2 +- LIN.Identity/Data/Applications.cs | 1 + .../Data/{ => Areas}/Accounts/AccountsGet.cs | 0 .../Data/{ => Areas}/Accounts/AccountsPost.cs | 0 .../{ => Areas}/Accounts/AccountsUpdate.cs | 0 .../{ => Areas}/Directories/Directories.cs | 8 +- .../Directories/DirectoryMembers.cs | 2 +- .../Data/{ => Areas}/Organizations/Members.cs | 5 +- .../Organizations/Organizations.cs | 90 +++++++++++++++---- LIN.Identity/Data/Context.cs | 23 ++--- .../{ => Data}/Queries/Directories.cs | 2 +- LIN.Identity/{ => Data}/Queries/Identities.cs | 4 +- LIN.Identity/Hubs/PasskeyHub.cs | 6 +- LIN.Identity/Services/EmailWorker.cs | 46 +++++----- LIN.Identity/Services/Iam/Applications.cs | 6 +- LIN.Identity/Services/Image.cs | 1 - LIN.Identity/Validations/AccountPassword.cs | 6 +- 25 files changed, 153 insertions(+), 93 deletions(-) rename LIN.Identity/Data/{ => Areas}/Accounts/AccountsGet.cs (100%) rename LIN.Identity/Data/{ => Areas}/Accounts/AccountsPost.cs (100%) rename LIN.Identity/Data/{ => Areas}/Accounts/AccountsUpdate.cs (100%) rename LIN.Identity/Data/{ => Areas}/Directories/Directories.cs (97%) rename LIN.Identity/Data/{ => Areas}/Directories/DirectoryMembers.cs (98%) rename LIN.Identity/Data/{ => Areas}/Organizations/Members.cs (97%) rename LIN.Identity/Data/{ => Areas}/Organizations/Organizations.cs (63%) rename LIN.Identity/{ => Data}/Queries/Directories.cs (98%) rename LIN.Identity/{ => Data}/Queries/Identities.cs (99%) diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index b9cfc60..fe95ed4 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -266,7 +266,6 @@ public async Task> FindAll([FromQuery] string // Obtiene el usuario var response = await Data.Accounts.Search(pattern, new() { - ContextOrg = 0, OrgLevel = Models.IncludeOrgLevel.Advance, ContextAccount = 0, FindOn = Models.FindOn.AllAccount, diff --git a/LIN.Identity/Areas/Accounts/AccountLogsController.cs b/LIN.Identity/Areas/Accounts/AccountLogsController.cs index 5f8975a..d2bda3d 100644 --- a/LIN.Identity/Areas/Accounts/AccountLogsController.cs +++ b/LIN.Identity/Areas/Accounts/AccountLogsController.cs @@ -26,7 +26,7 @@ public async Task> GetAll([FromHeader] string }; // Obtiene el usuario. - var result = await Data.Logins.ReadAll(userId); + var result = await Data.Logins.ReadAll(tokenInfo.AccountId); // Retorna el resultado. return result ?? new(); diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs index d15b225..f8654ab 100644 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -26,10 +26,10 @@ public async Task Delete([FromHeader] string token) Message = "Token invalido." }; - if (userId <= 0) + if (tokenInfo.AccountId <= 0) return new(Responses.InvalidParam); - var response = await Data.Accounts.Delete(userId); + var response = await Data.Accounts.Delete(tokenInfo.AccountId); return response; } @@ -63,7 +63,6 @@ public async Task UpdatePassword([FromHeader] int account, [Fr // Data actual. var actualData = await Data.Accounts.Read(account, new() { - ContextOrg = 0, SensibleInfo = true, ContextAccount = account, FindOn = Models.FindOn.StableAccounts, diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs index 86e2df4..51a6cea 100644 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -37,7 +37,7 @@ public async Task Create([FromBody] ApplicationModel applica // Preparar el modelo applicationModel.ApplicationUid = applicationModel.ApplicationUid.Trim().ToLower(); applicationModel.Name = applicationModel.Name.Trim().ToLower(); - applicationModel.AccountID = userID; + applicationModel.AccountID = tokenInfo.AccountId; // Crear la aplicación. return await Data.Applications.Create(applicationModel); diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index 6f75766..83596fa 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -1,4 +1,6 @@ -namespace LIN.Identity.Areas.Directories; +using LIN.Identity.Data.Areas.Directories; + +namespace LIN.Identity.Areas.Directories; [Route("directory")] @@ -33,7 +35,7 @@ public async Task> Read([FromQuery] int id, // Acceso IAM. - var (_, _, roles) = await Queries.Directories.Get(tokenInfo.IdentityId); + var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); // Si no hay acceso. if (Roles.View(roles)) @@ -44,7 +46,7 @@ public async Task> Read([FromQuery] int id, }; // Obtiene el directorio. - var response = await Data.Directories.Read(id, findIdentity); + var response = await Data.Areas.Directories.Directories.Read(id, findIdentity); // Si es erróneo if (response.Response != Responses.Success) @@ -80,7 +82,7 @@ public async Task> ReadAll([FromHeader] str }; // Obtiene el usuario. - var response = await Data.Directories.ReadAll(tokenInfo.IdentityId); + var response = await Data.Areas.Directories.Directories.ReadAll(tokenInfo.IdentityId); // Si es erróneo if (response.Response != Responses.Success) @@ -118,7 +120,7 @@ public async Task> ReadAll([FromHeader] str // Acceso IAM. - var (_, _, roles) = await Queries.Directories.Get(tokenInfo.IdentityId); + var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); // Si no hay acceso. if (Roles.ViewMembers(roles)) @@ -129,7 +131,7 @@ public async Task> ReadAll([FromHeader] str }; // Obtiene el usuario. - var response = await Data.DirectoryMembers.ReadMembers(directory); + var response = await DirectoryMembers.ReadMembers(directory); // Si es erróneo if (response.Response != Responses.Success) diff --git a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs index 02024d5..e880413 100644 --- a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs @@ -1,4 +1,6 @@ -namespace LIN.Identity.Areas.Directories; +using LIN.Identity.Data.Areas.Directories; + +namespace LIN.Identity.Areas.Directories; [Route("directory/members")] @@ -23,7 +25,7 @@ public async Task Create([FromHeader] string token, [FromBod // Acceso IAM. - var (_, _, roles) = await Queries.Directories.Get(identity); + var (_, _, roles) = await Data.Queries.Directories.Get(identity); // Si no hay acceso. if (Roles.AlterMembers(roles)) @@ -46,7 +48,7 @@ public async Task Create([FromHeader] string token, [FromBod // Obtiene el usuario. - var response = await Data.DirectoryMembers.Create(model); + var response = await DirectoryMembers.Create(model); // Retorna el resultado diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index 588be1b..c8a7c8b 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -1,3 +1,5 @@ +using LIN.Identity.Data.Areas.Organizations; + namespace LIN.Identity.Areas.Organizations; @@ -62,7 +64,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr } - var orgBase = await Data.Organizations.Organizations.FindBaseDirectory(userContext.Model.IdentityId); + var orgBase = await Data.Areas.Organizations.Organizations.FindBaseDirectory(userContext.Model.IdentityId); if (orgBase.Response != Responses.Success) { @@ -91,7 +93,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr var (context, connectionKey) = Conexión.GetOneConnection(); // Creación del usuario - var response = await Data.Organizations.Members.Create(modelo, orgBase.Model.DirectoryId, rol, context); + var response = await Members.Create(modelo, orgBase.Model.DirectoryId, rol, context); // Evaluación if (response.Response != Responses.Success) @@ -132,7 +134,7 @@ public async Task> ReadAll([FromHeader] string }; // Obtiene los miembros. - var members = await Data.Organizations.Members.ReadAll(orgID); + var members = await Members.ReadAll(orgID); // Error al obtener los integrantes. if (members.Response != Responses.Success) diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index 9bc2057..95264ad 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -38,7 +38,7 @@ public async Task Create([FromBody] OrganizationModel modelo // Creación de la organización. - var response = await Data.Organizations.Organizations.Create(modelo); + var response = await Data.Areas.Organizations.Organizations.Create(modelo); // Evaluación. if (response.Response != Responses.Success) @@ -82,7 +82,7 @@ public async Task> ReadOneByID([FromQuery // Obtiene la organización - var response = await Data.Organizations.Organizations.Read(id); + var response = await Data.Areas.Organizations.Organizations.Read(id); // Organización no encontrada. if (response.Response != Responses.Success) diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index 0552eee..fd5193c 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -115,7 +115,7 @@ public async Task> ReadAll([FromHeader] string }; // Acceso IAM. - var (_, _, roles) = await Queries.Directories.Get(identity); + var (_, _, roles) = await Data.Queries.Directories.Get(identity); // Si no hay acceso. if (Roles.ViewPolicy(roles)) diff --git a/LIN.Identity/Data/Applications.cs b/LIN.Identity/Data/Applications.cs index 1c227ec..9ebdbf2 100644 --- a/LIN.Identity/Data/Applications.cs +++ b/LIN.Identity/Data/Applications.cs @@ -318,4 +318,5 @@ public static async Task> AllowTo(int appId, int identityI } + } \ No newline at end of file diff --git a/LIN.Identity/Data/Accounts/AccountsGet.cs b/LIN.Identity/Data/Areas/Accounts/AccountsGet.cs similarity index 100% rename from LIN.Identity/Data/Accounts/AccountsGet.cs rename to LIN.Identity/Data/Areas/Accounts/AccountsGet.cs diff --git a/LIN.Identity/Data/Accounts/AccountsPost.cs b/LIN.Identity/Data/Areas/Accounts/AccountsPost.cs similarity index 100% rename from LIN.Identity/Data/Accounts/AccountsPost.cs rename to LIN.Identity/Data/Areas/Accounts/AccountsPost.cs diff --git a/LIN.Identity/Data/Accounts/AccountsUpdate.cs b/LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs similarity index 100% rename from LIN.Identity/Data/Accounts/AccountsUpdate.cs rename to LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs diff --git a/LIN.Identity/Data/Directories/Directories.cs b/LIN.Identity/Data/Areas/Directories/Directories.cs similarity index 97% rename from LIN.Identity/Data/Directories/Directories.cs rename to LIN.Identity/Data/Areas/Directories/Directories.cs index bf69daf..ebccc14 100644 --- a/LIN.Identity/Data/Directories/Directories.cs +++ b/LIN.Identity/Data/Areas/Directories/Directories.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Data; +namespace LIN.Identity.Data.Areas.Directories; public class Directories @@ -55,14 +55,14 @@ public static async Task> Read(int id, int iden // Obtiene la conexión var (context, connectionKey) = Conexión.GetOneConnection(); - var res = await Read(id,identityContext, context ); + var res = await Read(id, identityContext, context); context.CloseActions(connectionKey); return res; } - + public static async Task> ReadByIdentity(int id) { @@ -131,7 +131,7 @@ public static async Task> ReadAll(int identityI try { - var (_, identities,_) = await Queries.Directories.Get(identityId); + var (_, identities, _) = await Queries.Directories.Get(identityId); // Directorios. var directories = await (from directoryMember in context.DataBase.DirectoryMembers diff --git a/LIN.Identity/Data/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Areas/Directories/DirectoryMembers.cs similarity index 98% rename from LIN.Identity/Data/Directories/DirectoryMembers.cs rename to LIN.Identity/Data/Areas/Directories/DirectoryMembers.cs index 2a366a7..403b701 100644 --- a/LIN.Identity/Data/Directories/DirectoryMembers.cs +++ b/LIN.Identity/Data/Areas/Directories/DirectoryMembers.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Data; +namespace LIN.Identity.Data.Areas.Directories; public class DirectoryMembers diff --git a/LIN.Identity/Data/Organizations/Members.cs b/LIN.Identity/Data/Areas/Organizations/Members.cs similarity index 97% rename from LIN.Identity/Data/Organizations/Members.cs rename to LIN.Identity/Data/Areas/Organizations/Members.cs index 39d95a6..4dd2781 100644 --- a/LIN.Identity/Data/Organizations/Members.cs +++ b/LIN.Identity/Data/Areas/Organizations/Members.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Data.Organizations; +namespace LIN.Identity.Data.Areas.Organizations; public class Members @@ -8,6 +8,8 @@ public class Members #region Abstracciones + + /// /// Obtiene la lista de integrantes de una organización /// @@ -42,7 +44,6 @@ public static async Task> Create(AccountModel data /// Crea una cuenta en una organización /// /// Modelo - /// ID de la organización /// Rol dentro de la organización /// Contexto de conexión public static async Task> Create(AccountModel data, int directory, DirectoryRoles rol, Conexión context) diff --git a/LIN.Identity/Data/Organizations/Organizations.cs b/LIN.Identity/Data/Areas/Organizations/Organizations.cs similarity index 63% rename from LIN.Identity/Data/Organizations/Organizations.cs rename to LIN.Identity/Data/Areas/Organizations/Organizations.cs index cda1404..f99015c 100644 --- a/LIN.Identity/Data/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Areas/Organizations/Organizations.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Data.Organizations; +namespace LIN.Identity.Data.Areas.Organizations; public class Organizations @@ -9,7 +9,7 @@ public class Organizations /// - /// Crea una organización + /// Crea una organización. /// /// Modelo public static async Task> Create(OrganizationModel data) @@ -23,7 +23,7 @@ public static async Task> Create(Organization /// - /// Obtiene una organización + /// Obtiene una organización. /// /// ID de la organización public static async Task> Read(int id) @@ -37,13 +37,30 @@ public static async Task> Read(int id) + /// + /// Encontrar el directorio de una organización. + /// + /// Id del integrante directo. + public static async Task> FindBaseDirectory(int identity) + { + var (context, contextKey) = Conexión.GetOneConnection(); + + var res = await FindBaseDirectory(identity, context); + context.CloseActions(contextKey); + return res; + } + - public static async Task> FindBaseDirectory(int id) + /// + /// Encontrar una organización según la identidad de un integrante directo. + /// + /// Id del integrante. + public static async Task> FindOrganization(int identity) { var (context, contextKey) = Conexión.GetOneConnection(); - var res = await FindBaseDirectory(id, context); + var res = await FindBaseDirectory(identity, context); context.CloseActions(contextKey); return res; } @@ -51,13 +68,12 @@ public static async Task> FindBaseDirectory(int - #endregion /// - /// Crear una organización + /// Crear una organización. /// /// Modelo /// Contexto de conexión @@ -89,11 +105,9 @@ public static async Task> Create(Organization ID = 0, Identity = new() { - DirectoryMembers = [], Id = 0, Type = IdentityTypes.Account, - Unique = $"root@{data.Directory.Identity.Unique}", - Roles = [new() { Rol = DirectoryRoles.SuperManager }], + Unique = $"root@{data.Directory.Identity.Unique}" }, Insignia = AccountBadges.None, Nombre = $"Root user {data.Directory.Identity.Unique}", @@ -113,10 +127,11 @@ public static async Task> Create(Organization data.Directory.Members.Add(new() { Directory = data.Directory, - Identity = account.Identity + Identity = account.Identity, + Rol = DirectoryRoles.SuperManager }); - + // Guardar. context.DataBase.SaveChanges(); ; // Commit cambios. @@ -176,6 +191,11 @@ public static async Task> Read(int id, Conexi + /// + /// Encontrar el directorio de una organización. + /// + /// Id del integrante directo. + /// Contexto de conexión. public static async Task> FindBaseDirectory(int identity, Conexión context) { @@ -183,12 +203,50 @@ public static async Task> FindBaseDirectory(int try { - // Roles de integrante base. - DirectoryRoles[] roles = [DirectoryRoles.AccountsOperator, DirectoryRoles.Operator, DirectoryRoles.Manager, DirectoryRoles.SuperManager, DirectoryRoles.Regular]; + // Consulta el directorio base. + var query = await (from org in context.DataBase.Organizations + join dir in context.DataBase.Directories + on org.DirectoryId equals dir.ID + join mem in context.DataBase.DirectoryMembers + on dir.ID equals mem.DirectoryId + where identity == dir.IdentityId + select mem).FirstOrDefaultAsync(); + + // Email no existe + if (query == null) + return new(Responses.NotRows); + + return new(Responses.Success, query); + } + catch + { + } + + return new(); + } + + + + /// + /// Encontrar una organización según la identidad de un integrante directo. + /// + /// Id del integrante. + /// Contexto de conexión. + public static async Task> FindOrganization(int identity, Conexión context) + { + + // Ejecución + try + { - // Consulta. + // Consulta el directorio base. var query = await (from org in context.DataBase.Organizations - select org.Directory.Members.Where(t => t.IdentityId == identity).FirstOrDefault()).FirstOrDefaultAsync(); + join dir in context.DataBase.Directories + on org.DirectoryId equals dir.ID + join mem in context.DataBase.DirectoryMembers + on dir.ID equals mem.DirectoryId + where identity == dir.IdentityId + select org).FirstOrDefaultAsync(); // Email no existe if (query == null) diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index bc7e3f0..0065eab 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -112,19 +112,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() - .HasOne(p => p.Application) - .WithMany() - .HasForeignKey(p => p.ApplicationID) - .OnDelete(DeleteBehavior.NoAction); - ; - + .HasOne(p => p.Application) + .WithMany() + .HasForeignKey(p => p.ApplicationID) + .OnDelete(DeleteBehavior.NoAction); - modelBuilder.Entity() - .HasOne(p => p.Account) - .WithMany() - .HasForeignKey(p => p.AccountID); + modelBuilder.Entity() + .HasOne(p => p.Account) + .WithMany() + .HasForeignKey(p => p.AccountID); modelBuilder.Entity() @@ -141,7 +139,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Restrict); - modelBuilder.Entity() .HasOne(p => p.Directory) .WithMany(d => d.Policies) @@ -149,10 +146,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Restrict); - - - - modelBuilder.Entity() .HasKey(t => new { diff --git a/LIN.Identity/Queries/Directories.cs b/LIN.Identity/Data/Queries/Directories.cs similarity index 98% rename from LIN.Identity/Queries/Directories.cs rename to LIN.Identity/Data/Queries/Directories.cs index ca373aa..74c0e8a 100644 --- a/LIN.Identity/Queries/Directories.cs +++ b/LIN.Identity/Data/Queries/Directories.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Queries; +namespace LIN.Identity.Data.Queries; public class Directories { diff --git a/LIN.Identity/Queries/Identities.cs b/LIN.Identity/Data/Queries/Identities.cs similarity index 99% rename from LIN.Identity/Queries/Identities.cs rename to LIN.Identity/Data/Queries/Identities.cs index 84d0c99..c56d636 100644 --- a/LIN.Identity/Queries/Identities.cs +++ b/LIN.Identity/Data/Queries/Identities.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Queries; +namespace LIN.Identity.Data.Queries; public class Identities @@ -265,7 +265,7 @@ private static IQueryable BuildModel(IQueryable quer || filters.IsAdmin ? account.Perfil : profile, - + }; return queryFinal; diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs index 5c4a9c8..4bcfe94 100644 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ b/LIN.Identity/Hubs/PasskeyHub.cs @@ -1,4 +1,6 @@ -namespace LIN.Identity.Hubs; +using LIN.Identity.Data.Areas.Organizations; + +namespace LIN.Identity.Hubs; public class PassKeyHub : Hub @@ -197,7 +199,7 @@ public async Task ReceiveRequest(PassKeyModel modelo) if (orgID > 0) { // Obtiene la organización - var organization = await Data.Organizations.Organizations.Read(orgID); + var organization = await Organizations.Read(orgID); // Si tiene lista blanca //if (organization.Model.HaveWhiteList) diff --git a/LIN.Identity/Services/EmailWorker.cs b/LIN.Identity/Services/EmailWorker.cs index f2c3571..0a5ea0e 100644 --- a/LIN.Identity/Services/EmailWorker.cs +++ b/LIN.Identity/Services/EmailWorker.cs @@ -85,37 +85,35 @@ public static async Task SendMail(string to, string asunto, string body) try { - using (var client = new HttpClient()) - { - var url = "https://api.resend.com/emails"; - var accessToken = Password; // Reemplaza con tu token de acceso + using var client = new HttpClient(); + var url = "https://api.resend.com/emails"; + var accessToken = Password; // Reemplaza con tu token de acceso - var requestData = new + var requestData = new + { + from = "onboarding@resend.dev", + to = new[] { - from = "onboarding@resend.dev", - to = new[] - { - to - }, - subject = asunto - }; + to + }, + subject = asunto + }; - var json = System.Text.Json.JsonSerializer.Serialize(requestData); + var json = System.Text.Json.JsonSerializer.Serialize(requestData); - using var content = new StringContent(json, Encoding.UTF8, "application/json"); - client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken); + using var content = new StringContent(json, Encoding.UTF8, "application/json"); + client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken); - var response = await client.PostAsync(url, content); + var response = await client.PostAsync(url, content); - if (response.IsSuccessStatusCode) - { - return true; - } - else - { - _ = Logger.Log("Error al enviar un correo: ",response.StatusCode.ToString(), 3); - } + if (response.IsSuccessStatusCode) + { + return true; + } + else + { + _ = Logger.Log("Error al enviar un correo: ", response.StatusCode.ToString(), 3); } return true; } diff --git a/LIN.Identity/Services/Iam/Applications.cs b/LIN.Identity/Services/Iam/Applications.cs index 976a3b1..697bb4a 100644 --- a/LIN.Identity/Services/Iam/Applications.cs +++ b/LIN.Identity/Services/Iam/Applications.cs @@ -1,4 +1,6 @@ -namespace LIN.Identity.Services.Iam; +using LIN.Identity.Data.Queries; + +namespace LIN.Identity.Services.Iam; public static class Applications @@ -45,7 +47,7 @@ public static async Task> ValidateAccess(int account, select a.IdentityId).FirstOrDefault(); - var (directories, _,_) = await Queries.Directories.Get(identity); + var (directories, _,_) = await Directories.Get(identity); // Tiene acceso. diff --git a/LIN.Identity/Services/Image.cs b/LIN.Identity/Services/Image.cs index 4172f75..3b188a4 100644 --- a/LIN.Identity/Services/Image.cs +++ b/LIN.Identity/Services/Image.cs @@ -88,7 +88,6 @@ public static byte[] ZipOnWindows(byte[] originalImage, int width = 50, int heig /// Original image /// Ancho /// Alto - /// Maximo public static byte[] ZipOthers(byte[] originalImage, int width = 100, int height = 100, int max = 1900) { try diff --git a/LIN.Identity/Validations/AccountPassword.cs b/LIN.Identity/Validations/AccountPassword.cs index d802a7f..07dcb76 100644 --- a/LIN.Identity/Validations/AccountPassword.cs +++ b/LIN.Identity/Validations/AccountPassword.cs @@ -1,4 +1,6 @@ -namespace LIN.Identity.Validations; +using LIN.Identity.Data.Queries; + +namespace LIN.Identity.Validations; public class AccountPassword @@ -17,7 +19,7 @@ public static async Task ValidatePassword(int identity, string password) var (context, contextKey) = Conexión.GetOneConnection(); // Directorios. - var (directories, _, _) = await Queries.Directories.Get(identity); + var (directories, _, _) = await Directories.Get(identity); // Política. var policy = await (from p in context.DataBase.Policies From 63f805974f1e817316f245dc80600111355ac34a Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 26 Dec 2023 09:38:55 -0500 Subject: [PATCH 061/178] Mejoras con C# 12 --- .../Areas/Accounts/AccountController.cs | 1 - .../AuthenticationController.cs | 1 + LIN.Identity/Conexion.cs | 24 ++--- .../Data/Areas/Accounts/AccountsUpdate.cs | 4 +- LIN.Identity/Data/Context.cs | 90 +++++++++---------- LIN.Identity/Data/Logins.cs | 2 +- LIN.Identity/Data/Queries/Directories.cs | 9 +- LIN.Identity/Data/Queries/Identities.cs | 1 - LIN.Identity/Hubs/PasskeyHub.cs | 2 +- LIN.Identity/LIN.Identity.csproj | 2 +- LIN.Identity/Validations/Roles.cs | 2 - 11 files changed, 60 insertions(+), 78 deletions(-) diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index fe95ed4..c133ad0 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -236,7 +236,6 @@ public async Task> ReadAll([FromBody] List /// (ADMIN) encuentra diez (10) usuarios que coincidan con el patron /// diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index ce28259..18f9fed 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -122,4 +122,5 @@ public async Task> LoginWithToken([FromHeader] } + } \ No newline at end of file diff --git a/LIN.Identity/Conexion.cs b/LIN.Identity/Conexion.cs index deae4fa..0bebd91 100644 --- a/LIN.Identity/Conexion.cs +++ b/LIN.Identity/Conexion.cs @@ -136,17 +136,17 @@ public void SetOnUse() - private string mykey = string.Empty; + private string _myKey = string.Empty; public void CloseActions(string key) { lock (this) { - if (mykey != key) + if (_myKey != key) return; DataBase.ChangeTracker.Clear(); - mykey = string.Empty; + _myKey = string.Empty; OnUse = false; } } @@ -200,13 +200,13 @@ public static (Conexión context, string contextKey) GetOneConnection() // Obtiene una Conexión de la pool var con = CacheConnections.FirstOrDefault(T => !T.OnUseAction); - if (con != null && con.mykey == string.Empty) + if (con != null && con._myKey == string.Empty) { lock (con) { con.SetOnUse(); var key = KeyGen.Generate(10, "con."); - con.mykey = key; + con._myKey = key; return (con, key); } } @@ -214,22 +214,12 @@ public static (Conexión context, string contextKey) GetOneConnection() // Retorna la Conexión var conexión = new Conexión { - mykey = KeyGen.Generate(10, "con.") + _myKey = KeyGen.Generate(10, "con.") }; conexión.SetOnUse(); - return (conexión, conexión.mykey); + return (conexión, conexión._myKey); } - - /// - /// Obtiene una Conexión alterna a la base de datos - /// - public static Conexión GetForcedConnection(string? message = null) - { - return new(); - } - - } \ No newline at end of file diff --git a/LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs b/LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs index 811511e..db6bb4b 100644 --- a/LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs +++ b/LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs @@ -147,8 +147,8 @@ public static async Task Delete(int id, Conexión context) /// - /// Actualiza la información de una cuenta - /// ** No actualiza datos sensibles + /// Actualiza la información de una cuenta. + /// ** No actualiza datos sensibles. /// /// Modelo nuevo de la cuenta /// Contexto de conexión diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index 0065eab..884cc51 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -1,7 +1,10 @@ namespace LIN.Identity.Data; -public class Context : DbContext +/// +/// Nuevo contexto a la base de datos +/// +public class Context(DbContextOptions options) : DbContext(options) { @@ -35,21 +38,18 @@ public class Context : DbContext public DbSet DirectoryMembers { get; set; } - /// /// Tabla de aplicaciones /// public DbSet Applications { get; set; } - /// /// Tabla de registros de login /// public DbSet LoginLogs { get; set; } - /// /// Tabla de correos /// @@ -63,15 +63,6 @@ public class Context : DbContext - - /// - /// Nuevo contexto a la base de datos - /// - public Context(DbContextOptions options) : base(options) { } - - - - /// /// Naming DB /// @@ -80,8 +71,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // Indices y identidad. modelBuilder.Entity() - .HasIndex(e => e.Unique) - .IsUnique(); + .HasIndex(e => e.Unique) + .IsUnique(); // Indices y identidad. modelBuilder.Entity() @@ -90,68 +81,67 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // Indices y identidad. modelBuilder.Entity() - .HasIndex(e => e.ApplicationUid) - .IsUnique(); + .HasIndex(e => e.ApplicationUid) + .IsUnique(); // Indices y identidad. modelBuilder.Entity() - .HasIndex(e => e.Email) - .IsUnique(); + .HasIndex(e => e.Email) + .IsUnique(); modelBuilder.Entity() - .HasOne(p => p.Directory) - .WithMany() - .HasForeignKey(p => p.DirectoryId); + .HasOne(p => p.Directory) + .WithMany() + .HasForeignKey(p => p.DirectoryId); modelBuilder.Entity() - .HasOne(p => p.Directory) - .WithMany() - .HasForeignKey(p => p.DirectoryId); + .HasOne(p => p.Directory) + .WithMany() + .HasForeignKey(p => p.DirectoryId); modelBuilder.Entity() - .HasOne(p => p.Application) - .WithMany() - .HasForeignKey(p => p.ApplicationID) - .OnDelete(DeleteBehavior.NoAction); - + .HasOne(p => p.Application) + .WithMany() + .HasForeignKey(p => p.ApplicationID) + .OnDelete(DeleteBehavior.NoAction); modelBuilder.Entity() - .HasOne(p => p.Account) - .WithMany() - .HasForeignKey(p => p.AccountID); + .HasOne(p => p.Account) + .WithMany() + .HasForeignKey(p => p.AccountID); modelBuilder.Entity() - .HasOne(dm => dm.Directory) - .WithMany(d => d.Members) - .HasForeignKey(dm => dm.DirectoryId) - .OnDelete(DeleteBehavior.Restrict); + .HasOne(dm => dm.Directory) + .WithMany(d => d.Members) + .HasForeignKey(dm => dm.DirectoryId) + .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity() - .HasOne(p => p.Identity) - .WithMany(d => d.DirectoryMembers) - .HasForeignKey(p => p.IdentityId) - .OnDelete(DeleteBehavior.Restrict); + .HasOne(p => p.Identity) + .WithMany(d => d.DirectoryMembers) + .HasForeignKey(p => p.IdentityId) + .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity() - .HasOne(p => p.Directory) - .WithMany(d => d.Policies) - .HasForeignKey(p => p.DirectoryId) - .OnDelete(DeleteBehavior.Restrict); + .HasOne(p => p.Directory) + .WithMany(d => d.Policies) + .HasForeignKey(p => p.DirectoryId) + .OnDelete(DeleteBehavior.Restrict); modelBuilder.Entity() - .HasKey(t => new - { - t.IdentityId, - t.DirectoryId - }); + .HasKey(t => new + { + t.IdentityId, + t.DirectoryId + }); diff --git a/LIN.Identity/Data/Logins.cs b/LIN.Identity/Data/Logins.cs index 3866f68..23f5258 100644 --- a/LIN.Identity/Data/Logins.cs +++ b/LIN.Identity/Data/Logins.cs @@ -105,7 +105,7 @@ orderby L.Date descending } }; - // Resultado + // Resultado. var result = await logins.Take(50).ToListAsync(); return new(Responses.Success, result); diff --git a/LIN.Identity/Data/Queries/Directories.cs b/LIN.Identity/Data/Queries/Directories.cs index 74c0e8a..8334675 100644 --- a/LIN.Identity/Data/Queries/Directories.cs +++ b/LIN.Identity/Data/Queries/Directories.cs @@ -1,10 +1,15 @@ namespace LIN.Identity.Data.Queries; + public class Directories { + + + + /// /// Obtiene las identidades y directorios. /// @@ -62,7 +67,7 @@ private static async Task Get(int identityBase, Conexión context, List ide { Identity = DM.Directory.Identity.Id, Directory = DM.Directory.ID, - Roles = DM.Identity.Roles.Select(x => x.Rol).DistinctBy(t => t) + Roles = DM.Rol }; // Si hay elementos. @@ -71,7 +76,7 @@ private static async Task Get(int identityBase, Conexión context, List ide var local = query.ToList(); identities.AddRange(local.Select(t => t.Identity)); directories.AddRange(local.Select(t => t.Directory)); - roles.AddRange(local.Select(t => t.Roles).SelectMany(t => t)); + roles.AddRange(local.Select(t => t.Roles)); foreach (var id in local) await Get(id.Identity, context, identities, directories, roles); diff --git a/LIN.Identity/Data/Queries/Identities.cs b/LIN.Identity/Data/Queries/Identities.cs index c56d636..4700ec6 100644 --- a/LIN.Identity/Data/Queries/Identities.cs +++ b/LIN.Identity/Data/Queries/Identities.cs @@ -278,7 +278,6 @@ private static IQueryable BuildModel(IQueryable quer /// Construir la consulta /// /// Query base - /// Filtros private static IQueryable BuildModel(IQueryable query) { diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs index 4bcfe94..7d35ed3 100644 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ b/LIN.Identity/Hubs/PasskeyHub.cs @@ -154,7 +154,7 @@ public async Task ReceiveRequest(PassKeyModel modelo) try { - // Validación del token recibido + // Validación del token recibido. var (isValid, userUnique, userID, orgID, _, _) = Jwt.Validate(modelo.Token); // No es valido el token diff --git a/LIN.Identity/LIN.Identity.csproj b/LIN.Identity/LIN.Identity.csproj index 36a95a5..42928b2 100644 --- a/LIN.Identity/LIN.Identity.csproj +++ b/LIN.Identity/LIN.Identity.csproj @@ -8,7 +8,7 @@ - + diff --git a/LIN.Identity/Validations/Roles.cs b/LIN.Identity/Validations/Roles.cs index dff7edc..f531d66 100644 --- a/LIN.Identity/Validations/Roles.cs +++ b/LIN.Identity/Validations/Roles.cs @@ -27,7 +27,6 @@ public static bool View(DirectoryRoles rol) } - /// /// Confirmar si un rol tiene permisos de ver los integrantes. /// @@ -52,7 +51,6 @@ public static bool ViewMembers(DirectoryRoles rol) } - /// /// Confirmar si un rol tiene permisos para alterar los integrantes. /// From 9fc197781890aef99dab089eafb6f660f2bbb735 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 26 Dec 2023 12:35:27 -0500 Subject: [PATCH 062/178] Mejoras --- .../Areas/Accounts/AccountController.cs | 4 +-- .../Applications/ApplicationController.cs | 4 +-- .../AuthenticationController.cs | 2 +- .../Areas/Authentication/IntentsController.cs | 2 +- .../Directories/DirectoryMemberController.cs | 2 +- .../Areas/Organizations/MemberController.cs | 8 +++--- .../Organizations/OrganizationController.cs | 2 +- .../Areas/Policies/PolicyController.cs | 6 ++--- LIN.Identity/Hubs/PasskeyHub.cs | 14 +++++----- LIN.Identity/Services/Iam/Applications.cs | 27 +++++++++++++++++++ 10 files changed, 49 insertions(+), 22 deletions(-) diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index c133ad0..f666fb2 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -332,7 +332,7 @@ public async Task Update([FromHeader] string token, [FromHeade }; // Realizar actualización. - return await Data.Accounts.Update(id, genero); + return await Data.Accounts.Update(tokenInfo.AccountId, genero); } @@ -359,7 +359,7 @@ public async Task Update([FromHeader] string token, [FromHeade }; // Actualización. - return await Data.Accounts.Update(id, visibility); + return await Data.Accounts.Update(tokenInfo.AccountId, visibility); } diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs index 51a6cea..12bfeed 100644 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -66,7 +66,7 @@ public async Task> GetAll([FromHeader] str }; // Obtiene la data. - var data = await Data.Applications.ReadAll(userID); + var data = await Data.Applications.ReadAll(tokenInfo.AccountId); return data; @@ -96,7 +96,7 @@ public async Task> InsertAllow([FromHeader] string tok }; // Respuesta de Iam. - var iam = await Services.Iam.Applications.ValidateAccess(userId, appId); + var iam = await Services.Iam.Applications.ValidateAccess(tokenInfo.AccountId, appId); // Validación de Iam if (iam.Model != IamLevels.Privileged) diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index 18f9fed..f4479bf 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -102,7 +102,7 @@ public async Task> LoginWithToken([FromHeader] }; // Obtiene el usuario - var response = await Data.Accounts.Read(user, new Models.Account() + var response = await Data.Accounts.Read(tokenInfo.AccountId, new Models.Account() { SensibleInfo = true, IsAdmin = true, diff --git a/LIN.Identity/Areas/Authentication/IntentsController.cs b/LIN.Identity/Areas/Authentication/IntentsController.cs index febb95e..c8e0d1d 100644 --- a/LIN.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Identity/Areas/Authentication/IntentsController.cs @@ -28,7 +28,7 @@ public HttpReadAllResponse GetAll([FromHeader] string token) // Cuenta var account = (from a in PassKeyHub.Attempts - where a.Key == user.ToLower() + where a.Key == tokenInfo.Unique select a).FirstOrDefault().Value ?? new(); // Hora actual diff --git a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs index e880413..5cfc46c 100644 --- a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs @@ -25,7 +25,7 @@ public async Task Create([FromHeader] string token, [FromBod // Acceso IAM. - var (_, _, roles) = await Data.Queries.Directories.Get(identity); + var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); // Si no hay acceso. if (Roles.AlterMembers(roles)) diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index c8a7c8b..28c6dcd 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -51,7 +51,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr }; // Obtiene el usuario - var userContext = await Data.Accounts.ReadBasic(userID); + var userContext = await Data.Accounts.ReadBasic(tokenInfo.AccountId); // Error al encontrar el usuario if (userContext.Response != Responses.Success) @@ -123,10 +123,10 @@ public async Task> ReadAll([FromHeader] string { // Información del token. - var (isValid, _, _, orgID, _, _) = Jwt.Validate(token); ; + var tokenInfo = Jwt.Validate(token); ; // Si el token es invalido. - if (!isValid) + if (!tokenInfo.IsAuthenticated) return new ReadAllResponse { Message = "El token es invalido.", @@ -134,7 +134,7 @@ public async Task> ReadAll([FromHeader] string }; // Obtiene los miembros. - var members = await Members.ReadAll(orgID); + var members = await Members.ReadAll(tokenInfo.OrganizationId); // Error al obtener los integrantes. if (members.Response != Responses.Success) diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index 95264ad..7f35403 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -93,7 +93,7 @@ public async Task> ReadOneByID([FromQuery }; // No es publica y no pertenece a ella - if (!response.Model.IsPublic && orgID != response.Model.ID) + if (!response.Model.IsPublic && tokenInfo.OrganizationId != response.Model.ID) return new ReadOneResponse() { Response = Responses.Unauthorized, diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index fd5193c..3c66890 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -104,10 +104,10 @@ public async Task> ReadAll([FromHeader] string }; // Validar JSON. - var (isValid, _, _, _, _, identity) = Jwt.Validate(token); + var tokenInfo = Jwt.Validate(token); // Token es invalido. - if (!isValid) + if (!tokenInfo.IsAuthenticated) return new() { Message = "Token invalido.", @@ -115,7 +115,7 @@ public async Task> ReadAll([FromHeader] string }; // Acceso IAM. - var (_, _, roles) = await Data.Queries.Directories.Get(identity); + var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); // Si no hay acceso. if (Roles.ViewPolicy(roles)) diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs index 7d35ed3..1b054dd 100644 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ b/LIN.Identity/Hubs/PasskeyHub.cs @@ -155,10 +155,10 @@ public async Task ReceiveRequest(PassKeyModel modelo) { // Validación del token recibido. - var (isValid, userUnique, userID, orgID, _, _) = Jwt.Validate(modelo.Token); + var tokenInfo = Jwt.Validate(modelo.Token); // No es valido el token - if (!isValid || modelo.Status != PassKeyStatus.Success) + if (!tokenInfo.IsAuthenticated || modelo.Status != PassKeyStatus.Success) { // Modelo de falla PassKeyModel badPass = new() @@ -196,10 +196,10 @@ public async Task ReceiveRequest(PassKeyModel modelo) } // Validación de la organización - if (orgID > 0) + if (tokenInfo.OrganizationId > 0) { // Obtiene la organización - var organization = await Organizations.Read(orgID); + var organization = await Organizations.Read(tokenInfo.OrganizationId); // Si tiene lista blanca //if (organization.Model.HaveWhiteList) @@ -248,7 +248,7 @@ public async Task ReceiveRequest(PassKeyModel modelo) // Guarda el acceso. LoginLogModel loginLog = new() { - AccountID = userID, + AccountID = tokenInfo.AccountId, Application = new() { ID = app.Model.ID @@ -264,10 +264,10 @@ public async Task ReceiveRequest(PassKeyModel modelo) // Nuevo token var newToken = Jwt.Generate(new() { - ID = userID, + ID = tokenInfo.AccountId, Identity = new() { - Unique = userUnique + Unique = tokenInfo.Unique } }, app.Model.ID); diff --git a/LIN.Identity/Services/Iam/Applications.cs b/LIN.Identity/Services/Iam/Applications.cs index 697bb4a..aceebca 100644 --- a/LIN.Identity/Services/Iam/Applications.cs +++ b/LIN.Identity/Services/Iam/Applications.cs @@ -70,4 +70,31 @@ public static async Task> ValidateAccess(int account, + + + + + + + + + + + + + + + + + + + + + + + + + + + } \ No newline at end of file From e736bd08f2dbed4857226fca4fec913ff90eb39d Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 27 Dec 2023 11:26:06 -0500 Subject: [PATCH 063/178] Integracion con filtro de seguridad Auth --- .../Areas/Accounts/AccountController.cs | 78 +++---------------- .../Areas/Accounts/AccountLogsController.cs | 12 +-- .../Accounts/AccountSecurityController.cs | 11 +-- .../Applications/ApplicationController.cs | 29 +------ .../AuthenticationController.cs | 13 +--- .../Areas/Authentication/IntentsController.cs | 17 ++-- .../Areas/Directories/DirectoryController.cs | 29 ++----- .../Directories/DirectoryMemberController.cs | 11 +-- .../Areas/Organizations/MemberController.cs | 72 +++++++++-------- .../Organizations/OrganizationController.cs | 12 +-- .../Areas/Policies/PolicyController.cs | 21 +---- LIN.Identity/Data/Applications.cs | 6 +- .../Data/Areas/Organizations/Organizations.cs | 6 +- LIN.Identity/Data/Policies.cs | 4 +- LIN.Identity/Filters/Token.cs | 46 +++++++++++ LIN.Identity/Hubs/PasskeyHub.cs | 1 - LIN.Identity/Models/Account.cs | 1 + LIN.Identity/Program.cs | 3 +- LIN.Identity/Services/EmailWorker.cs | 10 ++- LIN.Identity/Services/Image.cs | 2 - LIN.Identity/Services/Login/LoginService.cs | 6 +- LIN.Identity/Usings.cs | 5 +- 22 files changed, 153 insertions(+), 242 deletions(-) create mode 100644 LIN.Identity/Filters/Token.cs diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index f666fb2..1476da4 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -1,3 +1,5 @@ +using Account = LIN.Identity.Validations.Account; + namespace LIN.Identity.Areas.Accounts; @@ -67,15 +69,7 @@ public async Task> Read([FromQuery] int id, [F // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); // Obtiene el usuario. var response = await Data.Accounts.Read(id, new() @@ -118,15 +112,9 @@ public async Task> Read([FromQuery] string use }; // Token. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + // Obtiene el usuario. var response = await Data.Accounts.Read(user, new() @@ -169,15 +157,8 @@ public async Task> Search([FromQuery] string p }; // Token. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; // Obtiene el usuario var response = await Data.Accounts.Search(pattern, new() @@ -211,15 +192,8 @@ public async Task> ReadAll([FromBody] List> FindAll([FromQuery] string { // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); var rol = AccountRoles.User; @@ -288,15 +254,7 @@ public async Task Update([FromBody] AccountModel modelo, [From { // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); // Organizar el modelo. modelo.Identity.Id = tokenInfo.IdentityId; @@ -321,15 +279,8 @@ public async Task Update([FromHeader] string token, [FromHeade { // Token. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; // Realizar actualización. return await Data.Accounts.Update(tokenInfo.AccountId, genero); @@ -348,15 +299,8 @@ public async Task Update([FromHeader] string token, [FromHeade { // Token. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; // Actualización. return await Data.Accounts.Update(tokenInfo.AccountId, visibility); diff --git a/LIN.Identity/Areas/Accounts/AccountLogsController.cs b/LIN.Identity/Areas/Accounts/AccountLogsController.cs index d2bda3d..0d3bf37 100644 --- a/LIN.Identity/Areas/Accounts/AccountLogsController.cs +++ b/LIN.Identity/Areas/Accounts/AccountLogsController.cs @@ -1,3 +1,5 @@ + + namespace LIN.Identity.Areas.Accounts; @@ -15,15 +17,7 @@ public async Task> GetAll([FromHeader] string { // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); // Obtiene el usuario. var result = await Data.Logins.ReadAll(tokenInfo.AccountId); diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs index f8654ab..b2bbc22 100644 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -16,16 +16,9 @@ public async Task Delete([FromHeader] string token) { // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); + if (tokenInfo.AccountId <= 0) return new(Responses.InvalidParam); diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs index 12bfeed..0d1f1b8 100644 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -16,15 +16,7 @@ public async Task Create([FromBody] ApplicationModel applica { // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); // Validaciones. if (applicationModel == null || applicationModel.ApplicationUid.Trim().Length < 4 || applicationModel.Name.Trim().Length < 4) @@ -55,15 +47,9 @@ public async Task> GetAll([FromHeader] str { // Token. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + // Obtiene la data. var data = await Data.Applications.ReadAll(tokenInfo.AccountId); @@ -85,15 +71,8 @@ public async Task> InsertAllow([FromHeader] string tok { // Token. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; // Respuesta de Iam. var iam = await Services.Iam.Applications.ValidateAccess(tokenInfo.AccountId, appId); diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index f4479bf..5564d8e 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,3 +1,4 @@ +using LIN.Identity.Models; using LIN.Identity.Services.Login; namespace LIN.Identity.Areas.Authentication; @@ -25,7 +26,6 @@ public async Task> Login([FromQuery] string us Message = "Uno o varios parámetros son invalido." }; - // Obtiene el usuario. var response = await Data.Accounts.Read(user, new() { @@ -87,19 +87,12 @@ public async Task> Login([FromQuery] string us /// /// Token de acceso [HttpGet("LoginWithToken")] + [TokenAuth] public async Task> LoginWithToken([FromHeader] string token) { // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); // Obtiene el usuario var response = await Data.Accounts.Read(tokenInfo.AccountId, new Models.Account() diff --git a/LIN.Identity/Areas/Authentication/IntentsController.cs b/LIN.Identity/Areas/Authentication/IntentsController.cs index c8e0d1d..d1d473f 100644 --- a/LIN.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Identity/Areas/Authentication/IntentsController.cs @@ -1,3 +1,5 @@ +using LIN.Identity.Models; + namespace LIN.Identity.Areas.Authentication; @@ -10,22 +12,15 @@ public class IntentsController : ControllerBase /// /// Token de acceso [HttpGet] + [TokenAuth] public HttpReadAllResponse GetAll([FromHeader] string token) { try { - // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - + // Token. + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); + // Cuenta var account = (from a in PassKeyHub.Attempts where a.Key == tokenInfo.Unique diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index 83596fa..f5e4569 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -23,15 +23,9 @@ public async Task> Read([FromQuery] int id, return new(Responses.InvalidParam); // Token. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + // Acceso IAM. @@ -71,15 +65,10 @@ public async Task> ReadAll([FromHeader] str { // Token. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + // Obtiene el usuario. var response = await Data.Areas.Directories.Directories.ReadAll(tokenInfo.IdentityId); @@ -108,15 +97,9 @@ public async Task> ReadAll([FromHeader] str { // Token. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + // Acceso IAM. diff --git a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs index 5cfc46c..c101542 100644 --- a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs @@ -13,16 +13,7 @@ public async Task Create([FromHeader] string token, [FromBod { // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); // Acceso IAM. var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index 28c6dcd..77812ed 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -1,4 +1,5 @@ using LIN.Identity.Data.Areas.Organizations; +using Account = LIN.Identity.Validations.Account; namespace LIN.Identity.Areas.Organizations; @@ -18,62 +19,65 @@ public class MemberController : ControllerBase public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] DirectoryRoles rol) { - // Validación del modelo. - if (modelo == null || !modelo.Identity.Unique.Trim().Any() || !modelo.Nombre.Trim().Any()) - return new CreateResponse + // Validar el modelo. + if (modelo == null || modelo.Identity == null || string.IsNullOrWhiteSpace(modelo.Identity.Unique) || string.IsNullOrWhiteSpace(modelo.Nombre)) + return new() { Response = Responses.InvalidParam, Message = "Uno o varios parámetros inválidos." }; + // Token. + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Visibilidad oculta - modelo.Visibilidad = AccountVisibility.Hidden; + - // Organización del modelo + // Ajustar el modelo. + modelo.Visibilidad = AccountVisibility.Hidden; + modelo.Contraseña = $"ChangePwd@{DateTime.Now.Year}"; modelo = Account.Process(modelo); - // Establece la contraseña default - var password = $"ChangePwd@{modelo.Creación:dd.MM.yyyy}"; - - // Contraseña default - modelo.Contraseña = EncryptClass.Encrypt(password); - - // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - - // Obtiene el usuario + // Obtiene el usuario. var userContext = await Data.Accounts.ReadBasic(tokenInfo.AccountId); // Error al encontrar el usuario if (userContext.Response != Responses.Success) - { return new CreateResponse { Message = "No se encontró un usuario valido.", Response = Responses.Unauthorized }; - } - + // Encontrar el directorio de la organización. var orgBase = await Data.Areas.Organizations.Organizations.FindBaseDirectory(userContext.Model.IdentityId); + // Si no se encontró el directorio. if (orgBase.Response != Responses.Success) - { return new() { Response = Responses.NotRows, Message = "No se encontró una organización permitida a este usuario." }; - } + + // Permisos para alterar los integrantes. + var iam = Roles.AlterMembers(orgBase.Model.Rol); + + // No tienes permisos. + if (!iam) + return new() + { + Response = Responses.Unauthorized, + Message = "No tienes permisos para modificar los integrantes de esta organización." + }; + + + + + + + + + // Validar acceso en el directorio con IAM. //var iam = await Services.Iam.Directories.ValidateAccess(userContext.Model.IdentityId, orgBase.Model.DirectoryId); @@ -123,15 +127,9 @@ public async Task> ReadAll([FromHeader] string { // Información del token. - var tokenInfo = Jwt.Validate(token); ; + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); ; - // Si el token es invalido. - if (!tokenInfo.IsAuthenticated) - return new ReadAllResponse - { - Message = "El token es invalido.", - Response = Responses.Unauthorized - }; + // Obtiene los miembros. var members = await Members.ReadAll(tokenInfo.OrganizationId); diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index 7f35403..69e2b55 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -70,17 +70,9 @@ public async Task> ReadOneByID([FromQuery return new(Responses.InvalidParam); // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; - + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); + // Obtiene la organización var response = await Data.Areas.Organizations.Organizations.Read(id); diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index 3c66890..999ee76 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -24,16 +24,9 @@ public async Task Create([FromBody] PolicyModel policy, [Fro }; // Token. - var tokenInfo = Jwt.Validate(token); - - // Si el token no es valido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Response = Responses.Unauthorized, - Message = "Token invalido." - }; + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); + // Acceso IAM. // var iam = await Services.Iam.Directories.ValidateAccess(identity, policy.DirectoryId); @@ -104,15 +97,9 @@ public async Task> ReadAll([FromHeader] string }; // Validar JSON. - var tokenInfo = Jwt.Validate(token); + JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Token es invalido. - if (!tokenInfo.IsAuthenticated) - return new() - { - Message = "Token invalido.", - Response = Responses.Unauthorized - }; + // Acceso IAM. var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); diff --git a/LIN.Identity/Data/Applications.cs b/LIN.Identity/Data/Applications.cs index 9ebdbf2..e5ddc9c 100644 --- a/LIN.Identity/Data/Applications.cs +++ b/LIN.Identity/Data/Applications.cs @@ -90,7 +90,7 @@ public static async Task> Read(string key) /// - /// Crear aplicación + /// Crear aplicación. /// /// Modelo /// Contexto de conexión @@ -126,7 +126,7 @@ public static async Task Create(ApplicationModel data, Conexión /// - /// Obtiene la lista de apps asociados a una cuenta + /// Obtiene la lista de apps asociados a una cuenta. /// /// ID de la cuenta /// Contexto de conexión @@ -154,7 +154,7 @@ public static async Task> ReadAll(int id, Cone /// - /// Obtiene una app + /// Obtiene una app. /// /// ID de la app /// Contexto de conexión diff --git a/LIN.Identity/Data/Areas/Organizations/Organizations.cs b/LIN.Identity/Data/Areas/Organizations/Organizations.cs index f99015c..6b67a81 100644 --- a/LIN.Identity/Data/Areas/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Areas/Organizations/Organizations.cs @@ -1,4 +1,6 @@ -namespace LIN.Identity.Data.Areas.Organizations; +using Account = LIN.Identity.Validations.Account; + +namespace LIN.Identity.Data.Areas.Organizations; public class Organizations @@ -209,6 +211,8 @@ join dir in context.DataBase.Directories on org.DirectoryId equals dir.ID join mem in context.DataBase.DirectoryMembers on dir.ID equals mem.DirectoryId + where mem.Rol != DirectoryRoles.Guest + && mem.Rol != DirectoryRoles.RoyalGuest where identity == dir.IdentityId select mem).FirstOrDefaultAsync(); diff --git a/LIN.Identity/Data/Policies.cs b/LIN.Identity/Data/Policies.cs index ae0f019..f33b054 100644 --- a/LIN.Identity/Data/Policies.cs +++ b/LIN.Identity/Data/Policies.cs @@ -39,9 +39,10 @@ public static async Task> ReadAll(int directory) /// - /// Valida el acceso a un permiso de una identidad. + /// Valida el acceso a un permiso de una identidad a una política. /// /// ID de la identidad + /// ID de la política public static async Task> ValidatePermission(int identity, int policy) { var (context, contextKey) = Conexión.GetOneConnection(); @@ -136,6 +137,7 @@ public static async Task> ReadAll(int id, Conexión /// Valida el acceso a un permiso de una identidad. /// /// ID de la identidad + /// ID de la política /// Contexto de conexión public static async Task> ValidatePermission(int identity, int policyId, Conexión context) { diff --git a/LIN.Identity/Filters/Token.cs b/LIN.Identity/Filters/Token.cs new file mode 100644 index 0000000..4643e81 --- /dev/null +++ b/LIN.Identity/Filters/Token.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Mvc.Filters; + +namespace LIN.Identity.Filters; + +public class TokenAuth : ActionFilterAttribute +{ + + + + /// + /// Filtro del token. + /// + /// Contexto HTTP. + /// Siguiente. + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + + // Contexto HTTP. + var httpContext = context.HttpContext; + + // Obtiene el valor. + bool can = httpContext.Request.Headers.TryGetValue("token", out Microsoft.Extensions.Primitives.StringValues value); + + // Información del token. + var tokenInfo = Jwt.Validate(value.ToString()); + + // Error de autenticación. + if (!can || !tokenInfo.IsAuthenticated) + { + httpContext.Response.StatusCode = 401; + await httpContext.Response.WriteAsJsonAsync(new ResponseBase() + { + Message = "Token invalido.", + Response = Responses.Unauthorized + }); + return; + } + + // Agrega la información del token. + context.HttpContext.Items.Add("token", tokenInfo); + await base.OnActionExecutionAsync(context, next); + + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs index 1b054dd..3dd1ed5 100644 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ b/LIN.Identity/Hubs/PasskeyHub.cs @@ -47,7 +47,6 @@ public async Task JoinIntent(PassKeyModel attempt) // Agrega el modelo if (!Attempts.ContainsKey(attempt.User.ToLower())) Attempts.Add(attempt.User.ToLower(), [attempt]); - else Attempts[attempt.User.ToLower()].Add(attempt); diff --git a/LIN.Identity/Models/Account.cs b/LIN.Identity/Models/Account.cs index 7d26a91..564184c 100644 --- a/LIN.Identity/Models/Account.cs +++ b/LIN.Identity/Models/Account.cs @@ -13,6 +13,7 @@ public class Account } + public enum IncludeOrg { diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index bef3492..c29d187 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -12,6 +12,7 @@ // Add services to the container. builder.Services.AddSignalR(); + builder.Services.AddSingleton(); builder.Services.AddCors(options => { @@ -86,4 +87,4 @@ { Logger.AppName = "LIN.Identity.V3"; await Logger.Log(ex, 3); -} \ No newline at end of file +} diff --git a/LIN.Identity/Services/EmailWorker.cs b/LIN.Identity/Services/EmailWorker.cs index 0a5ea0e..4864056 100644 --- a/LIN.Identity/Services/EmailWorker.cs +++ b/LIN.Identity/Services/EmailWorker.cs @@ -1,4 +1,6 @@ -namespace LIN.Identity.Services; +using System.Diagnostics; + +namespace LIN.Identity.Services; public class EmailWorker @@ -126,4 +128,10 @@ public static async Task SendMail(string to, string asunto, string body) + + + + + + } \ No newline at end of file diff --git a/LIN.Identity/Services/Image.cs b/LIN.Identity/Services/Image.cs index 3b188a4..257539f 100644 --- a/LIN.Identity/Services/Image.cs +++ b/LIN.Identity/Services/Image.cs @@ -107,8 +107,6 @@ public static byte[] ZipOthers(byte[] originalImage, int width = 100, int height pic.Save(stream); imagenBytes = stream.ToArray(); - var ss = Convert.ToBase64String(imagenBytes); - return imagenBytes; } diff --git a/LIN.Identity/Services/Login/LoginService.cs b/LIN.Identity/Services/Login/LoginService.cs index 26834f2..b33575a 100644 --- a/LIN.Identity/Services/Login/LoginService.cs +++ b/LIN.Identity/Services/Login/LoginService.cs @@ -73,10 +73,10 @@ public ResponseBase Validate() Message = "Esta cuenta fue eliminada o desactivada." }; - + // Valida la contraseña - - if (Account.Contraseña != EncryptClass.Encrypt(Password)) + var ee = EncryptClass.Encrypt(Password); + if (Account.Contraseña != ee) return new() { Response = Responses.InvalidPassword, diff --git a/LIN.Identity/Usings.cs b/LIN.Identity/Usings.cs index ad4bafc..a0ab3c5 100644 --- a/LIN.Identity/Usings.cs +++ b/LIN.Identity/Usings.cs @@ -13,4 +13,7 @@ global using Microsoft.IdentityModel.Tokens; global using System.Text; global using LIN.Access.Logger; -global using LIN.Identity.Validations; \ No newline at end of file +global using LIN.Identity.Validations; + +global using LIN.Identity.Models; +global using LIN.Identity.Filters; \ No newline at end of file From b38441f85e6b6a6ac613f1b5d21f450580344c1b Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 27 Dec 2023 13:33:42 -0500 Subject: [PATCH 064/178] Seguridad --- .../Areas/Accounts/AccountController.cs | 21 +++++++++---------- .../Areas/Accounts/AccountLogsController.cs | 1 + .../Accounts/AccountSecurityController.cs | 2 ++ .../Applications/ApplicationController.cs | 3 +++ .../AuthenticationController.cs | 1 + .../Areas/Directories/DirectoryController.cs | 3 +++ .../Directories/DirectoryMemberController.cs | 1 + .../Areas/Organizations/MemberController.cs | 2 ++ .../Organizations/OrganizationController.cs | 1 + .../Areas/Policies/PolicyController.cs | 2 ++ LIN.Identity/Data/Context.cs | 2 +- LIN.Identity/Hubs/PasskeyHub.cs | 2 +- .../Filters/TokenAuth.cs} | 2 +- LIN.Identity/Usings.cs | 2 +- 14 files changed, 30 insertions(+), 15 deletions(-) rename LIN.Identity/{Filters/Token.cs => Services/Filters/TokenAuth.cs} (96%) diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs index 1476da4..e476a39 100644 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Identity/Areas/Accounts/AccountController.cs @@ -57,6 +57,7 @@ public async Task Create([FromBody] AccountModel? modelo) /// Id de la cuenta. /// Token de acceso. [HttpGet("read/id")] + [TokenAuth] public async Task> Read([FromQuery] int id, [FromHeader] string token) { @@ -101,6 +102,7 @@ public async Task> Read([FromQuery] int id, [F /// Identidad. /// Token de acceso. [HttpGet("read/user")] + [TokenAuth] public async Task> Read([FromQuery] string user, [FromHeader] string token) { @@ -146,11 +148,12 @@ public async Task> Read([FromQuery] string use /// Patron /// Token de acceso [HttpGet("search")] - public async Task> Search([FromQuery] string pattern, [FromHeader] string token) + [TokenAuth] + public async Task> Search([FromQuery] string pattern) { // Comprobación - if (pattern.Trim().Length <= 0 || string.IsNullOrWhiteSpace(pattern) || string.IsNullOrWhiteSpace(token)) + if (pattern.Trim().Length <= 0 || string.IsNullOrWhiteSpace(pattern)) return new(Responses.InvalidParam) { Message = "Uno o varios parámetros son inválidos." @@ -180,17 +183,10 @@ public async Task> Search([FromQuery] string p /// IDs de las cuentas /// Token de acceso [HttpPost("findAll")] + [TokenAuth] public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) { - // Comprobación - if (string.IsNullOrWhiteSpace(token)) - return new(Responses.InvalidParam) - { - Message = "Uno o varios parámetros son inválidos." - }; - - // Token. JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); @@ -216,6 +212,7 @@ public async Task> ReadAll([FromBody] List /// [HttpGet("admin/search")] + [TokenAuth] public async Task> FindAll([FromQuery] string pattern, [FromHeader] string token) { @@ -250,6 +247,7 @@ public async Task> FindAll([FromQuery] string /// Modelo /// Token de acceso [HttpPut("update")] + [TokenAuth] public async Task Update([FromBody] AccountModel modelo, [FromHeader] string token) { @@ -275,6 +273,7 @@ public async Task Update([FromBody] AccountModel modelo, [From /// Token de acceso /// Nuevo genero [HttpPatch("update/gender")] + [TokenAuth] public async Task Update([FromHeader] string token, [FromHeader] Genders genero) { @@ -295,13 +294,13 @@ public async Task Update([FromHeader] string token, [FromHeade /// Token de acceso. /// Nueva visibilidad. [HttpPatch("update/visibility")] + [TokenAuth] public async Task Update([FromHeader] string token, [FromHeader] AccountVisibility visibility) { // Token. JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Actualización. return await Data.Accounts.Update(tokenInfo.AccountId, visibility); diff --git a/LIN.Identity/Areas/Accounts/AccountLogsController.cs b/LIN.Identity/Areas/Accounts/AccountLogsController.cs index 0d3bf37..cf5c8d7 100644 --- a/LIN.Identity/Areas/Accounts/AccountLogsController.cs +++ b/LIN.Identity/Areas/Accounts/AccountLogsController.cs @@ -13,6 +13,7 @@ public class AccountLogsController : ControllerBase /// /// Token de acceso [HttpGet] + [TokenAuth] public async Task> GetAll([FromHeader] string token) { diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs index b2bbc22..a566dde 100644 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs @@ -12,6 +12,7 @@ public class AccountSecurityController : ControllerBase /// /// Token de acceso [HttpDelete("delete")] + [TokenAuth] public async Task Delete([FromHeader] string token) { @@ -35,6 +36,7 @@ public async Task Delete([FromHeader] string token) /// Contraseña actual. /// Nueva contraseña. [HttpPatch("update/password")] + [TokenAuth] public async Task UpdatePassword([FromHeader] int account, [FromQuery] string actualPassword, [FromHeader] string newPassword) { diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs index 0d1f1b8..4440181 100644 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ b/LIN.Identity/Areas/Applications/ApplicationController.cs @@ -12,6 +12,7 @@ public class ApplicationController : ControllerBase /// Modelo. /// Token de acceso. [HttpPost] + [TokenAuth] public async Task Create([FromBody] ApplicationModel applicationModel, [FromHeader] string token) { @@ -43,6 +44,7 @@ public async Task Create([FromBody] ApplicationModel applica /// /// Token de acceso [HttpGet] + [TokenAuth] public async Task> GetAll([FromHeader] string token) { @@ -67,6 +69,7 @@ public async Task> GetAll([FromHeader] str /// ID de la aplicación. /// ID del integrante. [HttpPut] + [TokenAuth] public async Task> InsertAllow([FromHeader] string token, [FromHeader] int appId, [FromHeader] int accountId) { diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index 5564d8e..9e84a05 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -16,6 +16,7 @@ public class AuthenticationController : ControllerBase /// Contraseña del usuario /// Key de aplicación [HttpGet("login")] + [TokenAuth] public async Task> Login([FromQuery] string user, [FromQuery] string password, [FromHeader] string application) { diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs index f5e4569..4bafde6 100644 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryController.cs @@ -15,6 +15,7 @@ public class DirectoryController : ControllerBase /// Identidad de contexto. /// Token de acceso. [HttpGet("read/id")] + [TokenAuth] public async Task> Read([FromQuery] int id, [FromQuery] int findIdentity, [FromHeader] string token) { @@ -61,6 +62,7 @@ public async Task> Read([FromQuery] int id, /// /// Token de acceso. [HttpGet("read/all")] + [TokenAuth] public async Task> ReadAll([FromHeader] string token) { @@ -93,6 +95,7 @@ public async Task> ReadAll([FromHeader] str /// Token de acceso. /// ID del directorio. [HttpGet("read/members")] + [TokenAuth] public async Task> ReadAll([FromHeader] string token, [FromQuery] int directory) { diff --git a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs index c101542..098f840 100644 --- a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs +++ b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs @@ -9,6 +9,7 @@ public class DirectoryMembersController : ControllerBase [HttpPost] + [TokenAuth] public async Task Create([FromHeader] string token, [FromBody] DirectoryMember model) { diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index 77812ed..d4f7ae8 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -16,6 +16,7 @@ public class MemberController : ControllerBase /// Token de acceso de un administrador /// Rol asignado [HttpPost] + [TokenAuth] public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] DirectoryRoles rol) { @@ -123,6 +124,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr /// /// Token de acceso [HttpGet] + [TokenAuth] public async Task> ReadAll([FromHeader] string token) { diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Identity/Areas/Organizations/OrganizationController.cs index 69e2b55..c949d38 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Identity/Areas/Organizations/OrganizationController.cs @@ -62,6 +62,7 @@ public async Task Create([FromBody] OrganizationModel modelo /// ID de la organización /// Token de acceso [HttpGet("read/id")] + [TokenAuth] public async Task> ReadOneByID([FromQuery] int id, [FromHeader] string token) { diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs index 999ee76..8cbffa6 100644 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ b/LIN.Identity/Areas/Policies/PolicyController.cs @@ -12,6 +12,7 @@ public class PolicyController : ControllerBase /// Modelo. /// Token de acceso. [HttpPost] + [TokenAuth] public async Task Create([FromBody] PolicyModel policy, [FromHeader] string token) { @@ -85,6 +86,7 @@ public async Task> ValidatePermissions([FromQuery] int /// Token de acceso. /// Id del directorio. [HttpGet("read/all")] + [TokenAuth] public async Task> ReadAll([FromHeader] string token, [FromQuery] int directory) { diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs index 884cc51..2f5df37 100644 --- a/LIN.Identity/Data/Context.cs +++ b/LIN.Identity/Data/Context.cs @@ -2,7 +2,7 @@ /// -/// Nuevo contexto a la base de datos +/// Nuevo contexto a la base de datos. /// public class Context(DbContextOptions options) : DbContext(options) { diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs index 3dd1ed5..33947db 100644 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ b/LIN.Identity/Hubs/PasskeyHub.cs @@ -10,7 +10,7 @@ public class PassKeyHub : Hub /// /// Lista de intentos Passkey. /// - public readonly static Dictionary> Attempts = new(); + public readonly static Dictionary> Attempts = []; diff --git a/LIN.Identity/Filters/Token.cs b/LIN.Identity/Services/Filters/TokenAuth.cs similarity index 96% rename from LIN.Identity/Filters/Token.cs rename to LIN.Identity/Services/Filters/TokenAuth.cs index 4643e81..5616f6f 100644 --- a/LIN.Identity/Filters/Token.cs +++ b/LIN.Identity/Services/Filters/TokenAuth.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc.Filters; -namespace LIN.Identity.Filters; +namespace LIN.Identity.Services.Filters; public class TokenAuth : ActionFilterAttribute { diff --git a/LIN.Identity/Usings.cs b/LIN.Identity/Usings.cs index a0ab3c5..455a5f9 100644 --- a/LIN.Identity/Usings.cs +++ b/LIN.Identity/Usings.cs @@ -16,4 +16,4 @@ global using LIN.Identity.Validations; global using LIN.Identity.Models; -global using LIN.Identity.Filters; \ No newline at end of file +global using LIN.Identity.Services.Filters; \ No newline at end of file From a85f1c8aad2ff7ebd49b906bd05d1d81f0600e0f Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 27 Dec 2023 14:03:35 -0500 Subject: [PATCH 065/178] Mejoras en los middlewares --- LIN.Identity/Program.cs | 6 ++- LIN.Identity/Services/Filters/IPMiddleware.cs | 46 +++++++++++++++++++ .../{TokenAuth.cs => TokenAuthAttribute.cs} | 2 +- 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 LIN.Identity/Services/Filters/IPMiddleware.cs rename LIN.Identity/Services/Filters/{TokenAuth.cs => TokenAuthAttribute.cs} (95%) diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index c29d187..ad49634 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -9,11 +9,12 @@ var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddIP(); + // Add services to the container. builder.Services.AddSignalR(); - builder.Services.AddSingleton(); - builder.Services.AddCors(options => { options.AddPolicy("AllowAnyOrigin", @@ -59,6 +60,7 @@ } + app.UseIP(); app.UseCors("AllowAnyOrigin"); app.MapHub("/realTime/auth/passkey"); diff --git a/LIN.Identity/Services/Filters/IPMiddleware.cs b/LIN.Identity/Services/Filters/IPMiddleware.cs new file mode 100644 index 0000000..b5f6683 --- /dev/null +++ b/LIN.Identity/Services/Filters/IPMiddleware.cs @@ -0,0 +1,46 @@ +namespace LIN.Identity.Services.Filters; + + +public class IPMiddleware : IMiddleware +{ + + /// + /// Middleware de IP. + /// + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + // Obtener la IP. + var ip = context.Connection.RemoteIpAddress; + + // Validar la IP. + if (ip == null) + { + context.Response.StatusCode = 400; + await context.Response.WriteAsJsonAsync(new ResponseBase + { + Message = "La IP del cliente no pudo ser resuelta.", + Response = Responses.Unauthorized + }); + return; + } + + // Item de IP. + context.Items.Add("IP", ip); + await next(context); + } + +} + + +public static class IPMiddlewareExtensions +{ + public static IApplicationBuilder UseIP(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + + public static IServiceCollection AddIP(this IServiceCollection builder) + { + return builder.AddSingleton(); + } +} \ No newline at end of file diff --git a/LIN.Identity/Services/Filters/TokenAuth.cs b/LIN.Identity/Services/Filters/TokenAuthAttribute.cs similarity index 95% rename from LIN.Identity/Services/Filters/TokenAuth.cs rename to LIN.Identity/Services/Filters/TokenAuthAttribute.cs index 5616f6f..683c7eb 100644 --- a/LIN.Identity/Services/Filters/TokenAuth.cs +++ b/LIN.Identity/Services/Filters/TokenAuthAttribute.cs @@ -2,7 +2,7 @@ namespace LIN.Identity.Services.Filters; -public class TokenAuth : ActionFilterAttribute +public class TokenAuthAttribute : ActionFilterAttribute { From ed0b920f51829e582b4d6086657820386e764f81 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 28 Dec 2023 10:39:59 -0500 Subject: [PATCH 066/178] Mejoras en Data --- .../AuthenticationController.cs | 10 +- .../Areas/Organizations/MemberController.cs | 4 +- .../Data/Areas/Accounts/AccountsGet.cs | 1 - .../Data/Areas/Organizations/Members.cs | 4 +- .../Data/Areas/Organizations/Organizations.cs | 6 +- LIN.Identity/Data/Identities.cs | 4 +- LIN.Identity/Data/Mails.cs | 2 - LIN.Identity/Data/Policies.cs | 1 - LIN.Identity/Data/Queries/Directories.cs | 46 ++----- LIN.Identity/Data/Queries/Identities.cs | 6 +- LIN.Identity/Models/Account.cs | 1 + LIN.Identity/Program.cs | 1 + LIN.Identity/Services/Filters/IPMiddleware.cs | 12 +- LIN.Identity/Validations/Roles.cs | 122 +++++++++--------- 14 files changed, 95 insertions(+), 125 deletions(-) diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Identity/Areas/Authentication/AuthenticationController.cs index 9e84a05..06cc8cb 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,4 +1,3 @@ -using LIN.Identity.Models; using LIN.Identity.Services.Login; namespace LIN.Identity.Areas.Authentication; @@ -62,7 +61,6 @@ public async Task> Login([FromQuery] string us // Respuesta del login var loginResponse = await strategy.Login(); - // Respuesta if (loginResponse.Response != Responses.Success) return new ReadOneResponse() @@ -95,14 +93,14 @@ public async Task> LoginWithToken([FromHeader] // Token. JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - // Obtiene el usuario + // Obtiene el usuario. var response = await Data.Accounts.Read(tokenInfo.AccountId, new Models.Account() { SensibleInfo = true, IsAdmin = true, - IncludeOrg = Models.IncludeOrg.Include, - OrgLevel = Models.IncludeOrgLevel.Advance, - FindOn = Models.FindOn.StableAccounts + IncludeOrg = IncludeOrg.Include, + OrgLevel = IncludeOrgLevel.Advance, + FindOn = FindOn.StableAccounts }); if (response.Response != Responses.Success) diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs index d4f7ae8..da25ceb 100644 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Identity/Areas/Organizations/MemberController.cs @@ -17,7 +17,7 @@ public class MemberController : ControllerBase /// Rol asignado [HttpPost] [TokenAuth] - public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] DirectoryRoles rol) + public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] Types.Identity.Enumerations.Roles rol) { // Validar el modelo. @@ -61,7 +61,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr }; // Permisos para alterar los integrantes. - var iam = Roles.AlterMembers(orgBase.Model.Rol); + var iam = Validations.Roles.AlterMembers(orgBase.Model.Rol); // No tienes permisos. if (!iam) diff --git a/LIN.Identity/Data/Areas/Accounts/AccountsGet.cs b/LIN.Identity/Data/Areas/Accounts/AccountsGet.cs index f8f2bd9..57e68c3 100644 --- a/LIN.Identity/Data/Areas/Accounts/AccountsGet.cs +++ b/LIN.Identity/Data/Areas/Accounts/AccountsGet.cs @@ -240,7 +240,6 @@ public static async Task> Read(string user, Models - /// /// Buscar usuarios por patron de búsqueda. /// diff --git a/LIN.Identity/Data/Areas/Organizations/Members.cs b/LIN.Identity/Data/Areas/Organizations/Members.cs index 4dd2781..f150399 100644 --- a/LIN.Identity/Data/Areas/Organizations/Members.cs +++ b/LIN.Identity/Data/Areas/Organizations/Members.cs @@ -26,7 +26,7 @@ public static async Task> ReadAll(int id) - public static async Task> Create(AccountModel data, int dir, DirectoryRoles rol) + public static async Task> Create(AccountModel data, int dir, Types.Identity.Enumerations.Roles rol) { var (context, contextKey) = Conexión.GetOneConnection(); @@ -46,7 +46,7 @@ public static async Task> Create(AccountModel data /// Modelo /// Rol dentro de la organización /// Contexto de conexión - public static async Task> Create(AccountModel data, int directory, DirectoryRoles rol, Conexión context) + public static async Task> Create(AccountModel data, int directory, Types.Identity.Enumerations.Roles rol, Conexión context) { data.ID = 0; diff --git a/LIN.Identity/Data/Areas/Organizations/Organizations.cs b/LIN.Identity/Data/Areas/Organizations/Organizations.cs index 6b67a81..211ecd5 100644 --- a/LIN.Identity/Data/Areas/Organizations/Organizations.cs +++ b/LIN.Identity/Data/Areas/Organizations/Organizations.cs @@ -130,7 +130,7 @@ public static async Task> Create(Organization { Directory = data.Directory, Identity = account.Identity, - Rol = DirectoryRoles.SuperManager + Rol = Types.Identity.Enumerations.Roles.SuperManager }); // Guardar. @@ -211,8 +211,8 @@ join dir in context.DataBase.Directories on org.DirectoryId equals dir.ID join mem in context.DataBase.DirectoryMembers on dir.ID equals mem.DirectoryId - where mem.Rol != DirectoryRoles.Guest - && mem.Rol != DirectoryRoles.RoyalGuest + where mem.Rol != Types.Identity.Enumerations.Roles.Guest + && mem.Rol != Types.Identity.Enumerations.Roles.RoyalGuest where identity == dir.IdentityId select mem).FirstOrDefaultAsync(); diff --git a/LIN.Identity/Data/Identities.cs b/LIN.Identity/Data/Identities.cs index c7cac56..0335c2a 100644 --- a/LIN.Identity/Data/Identities.cs +++ b/LIN.Identity/Data/Identities.cs @@ -35,8 +35,8 @@ public static async Task> Read(int id, Conexión { var ids = await (from identity in context.DataBase.Identities - where identity.Id == id - select identity).FirstOrDefaultAsync(); + where identity.Id == id + select identity).FirstOrDefaultAsync(); if (ids == null) diff --git a/LIN.Identity/Data/Mails.cs b/LIN.Identity/Data/Mails.cs index 1308070..10a6dab 100644 --- a/LIN.Identity/Data/Mails.cs +++ b/LIN.Identity/Data/Mails.cs @@ -121,10 +121,8 @@ public static async Task Create(EmailModel data, Conexión conte } catch (Exception ex) { - if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) return new(Responses.Undefined); - } return new(); diff --git a/LIN.Identity/Data/Policies.cs b/LIN.Identity/Data/Policies.cs index f33b054..89d500c 100644 --- a/LIN.Identity/Data/Policies.cs +++ b/LIN.Identity/Data/Policies.cs @@ -132,7 +132,6 @@ public static async Task> ReadAll(int id, Conexión - /// /// Valida el acceso a un permiso de una identidad. /// diff --git a/LIN.Identity/Data/Queries/Directories.cs b/LIN.Identity/Data/Queries/Directories.cs index 8334675..98a58bf 100644 --- a/LIN.Identity/Data/Queries/Directories.cs +++ b/LIN.Identity/Data/Queries/Directories.cs @@ -5,20 +5,15 @@ public class Directories { - - - - - /// /// Obtiene las identidades y directorios. /// /// Identidad base - public static async Task<(List directories, List identities, List roles)> Get(int identity) + public static async Task<(List directories, List identities, List roles)> Get(int identity) { List identities = [identity]; List directories = []; - List roles = []; + List roles = []; var (context, contextKey) = Conexión.GetOneConnection(); @@ -28,25 +23,6 @@ public class Directories return (directories, identities, roles); } - - - - - - - - - - - - - - - - - - - /// @@ -57,17 +33,19 @@ public class Directories /// Lista de identidades. /// Directorios /// Roles - private static async Task Get(int identityBase, Conexión context, List identities, List directories, List roles) + private static async Task Get(int identityBase, Conexión context, List identities, List directories, List roles) { // Consulta. - var query = from DM in context.DataBase.DirectoryMembers - where DM.IdentityId == identityBase - && !identities.Contains(DM.Directory.IdentityId) + var query = from member in context.DataBase.DirectoryMembers + where member.IdentityId == identityBase + where member.Rol != Roles.Guest + && member.Rol != Roles.RoyalGuest + && !identities.Contains(member.Directory.IdentityId) select new { - Identity = DM.Directory.Identity.Id, - Directory = DM.Directory.ID, - Roles = DM.Rol + Identity = member.Directory.Identity.Id, + Directory = member.Directory.ID, + Roles = member.Rol }; // Si hay elementos. @@ -80,10 +58,10 @@ private static async Task Get(int identityBase, Conexión context, List ide foreach (var id in local) await Get(id.Identity, context, identities, directories, roles); - } } + } \ No newline at end of file diff --git a/LIN.Identity/Data/Queries/Identities.cs b/LIN.Identity/Data/Queries/Identities.cs index 4700ec6..ed77ae2 100644 --- a/LIN.Identity/Data/Queries/Identities.cs +++ b/LIN.Identity/Data/Queries/Identities.cs @@ -139,12 +139,12 @@ where ids.Contains(account.ID) public static IQueryable Search(string pattern, Models.Account filters, Conexión context) { - // Query general + // Query general. IQueryable accounts = from account in GetValidAccounts(context) where account.Identity.Unique.Contains(pattern) select account; - // Armar el modelo + // Armar el modelo. accounts = BuildModel(accounts, filters); // Retorno @@ -158,7 +158,7 @@ where account.Identity.Unique.Contains(pattern) public static IQueryable GetDirectory(int id, int identityContext, Conexión context) { - // Query general + // Query general. IQueryable accounts; diff --git a/LIN.Identity/Models/Account.cs b/LIN.Identity/Models/Account.cs index 564184c..d685d29 100644 --- a/LIN.Identity/Models/Account.cs +++ b/LIN.Identity/Models/Account.cs @@ -14,6 +14,7 @@ public class Account } + public enum IncludeOrg { diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index ad49634..997e0dd 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -84,6 +84,7 @@ app.MapControllers(); app.Run(); + } catch (Exception ex) { diff --git a/LIN.Identity/Services/Filters/IPMiddleware.cs b/LIN.Identity/Services/Filters/IPMiddleware.cs index b5f6683..785b009 100644 --- a/LIN.Identity/Services/Filters/IPMiddleware.cs +++ b/LIN.Identity/Services/Filters/IPMiddleware.cs @@ -34,13 +34,9 @@ await context.Response.WriteAsJsonAsync(new ResponseBase public static class IPMiddlewareExtensions { - public static IApplicationBuilder UseIP(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - public static IServiceCollection AddIP(this IServiceCollection builder) - { - return builder.AddSingleton(); - } + public static IApplicationBuilder UseIP(this IApplicationBuilder builder) => builder.UseMiddleware(); + + public static IServiceCollection AddIP(this IServiceCollection builder) => builder.AddSingleton(); + } \ No newline at end of file diff --git a/LIN.Identity/Validations/Roles.cs b/LIN.Identity/Validations/Roles.cs index f531d66..0cd4f46 100644 --- a/LIN.Identity/Validations/Roles.cs +++ b/LIN.Identity/Validations/Roles.cs @@ -7,19 +7,19 @@ public static class Roles /// Confirmar si un rol tiene permisos de ver el directorio. /// /// Rol a confirmar. - public static bool View(DirectoryRoles rol) + public static bool View(Types.Identity.Enumerations.Roles rol) { - DirectoryRoles[] roles = + Types.Identity.Enumerations.Roles[] roles = [ - DirectoryRoles.System, - DirectoryRoles.SuperManager, - DirectoryRoles.Manager, - DirectoryRoles.Operator, - DirectoryRoles.AccountsOperator, - DirectoryRoles.Regular, - DirectoryRoles.Guest, - DirectoryRoles.RoyalGuest + Types.Identity.Enumerations.Roles.System, + Types.Identity.Enumerations.Roles.SuperManager, + Types.Identity.Enumerations.Roles.Manager, + Types.Identity.Enumerations.Roles.Operator, + Types.Identity.Enumerations.Roles.AccountsOperator, + Types.Identity.Enumerations.Roles.Regular, + Types.Identity.Enumerations.Roles.Guest, + Types.Identity.Enumerations.Roles.RoyalGuest ]; return roles.Contains(rol); @@ -31,19 +31,19 @@ public static bool View(DirectoryRoles rol) /// Confirmar si un rol tiene permisos de ver los integrantes. /// /// Rol a confirmar. - public static bool ViewMembers(DirectoryRoles rol) + public static bool ViewMembers(Types.Identity.Enumerations.Roles rol) { - DirectoryRoles[] roles = + Types.Identity.Enumerations.Roles[] roles = [ - DirectoryRoles.System, - DirectoryRoles.SuperManager, - DirectoryRoles.Manager, - DirectoryRoles.Operator, - DirectoryRoles.AccountsOperator, - DirectoryRoles.Regular, - DirectoryRoles.Guest, - DirectoryRoles.RoyalGuest + Types.Identity.Enumerations.Roles.System, + Types.Identity.Enumerations.Roles.SuperManager, + Types.Identity.Enumerations.Roles.Manager, + Types.Identity.Enumerations.Roles.Operator, + Types.Identity.Enumerations.Roles.AccountsOperator, + Types.Identity.Enumerations.Roles.Regular, + Types.Identity.Enumerations.Roles.Guest, + Types.Identity.Enumerations.Roles.RoyalGuest ]; return roles.Contains(rol); @@ -55,17 +55,17 @@ public static bool ViewMembers(DirectoryRoles rol) /// Confirmar si un rol tiene permisos para alterar los integrantes. /// /// Rol a confirmar. - public static bool AlterMembers(DirectoryRoles rol) + public static bool AlterMembers(Types.Identity.Enumerations.Roles rol) { - DirectoryRoles[] roles = + Types.Identity.Enumerations.Roles[] roles = [ - DirectoryRoles.System, - DirectoryRoles.SuperManager, - DirectoryRoles.Manager, - DirectoryRoles.Operator, - DirectoryRoles.AccountsOperator, - DirectoryRoles.RoyalGuest + Types.Identity.Enumerations.Roles.System, + Types.Identity.Enumerations.Roles.SuperManager, + Types.Identity.Enumerations.Roles.Manager, + Types.Identity.Enumerations.Roles.Operator, + Types.Identity.Enumerations.Roles.AccountsOperator, + Types.Identity.Enumerations.Roles.RoyalGuest ]; return roles.Contains(rol); @@ -77,14 +77,14 @@ public static bool AlterMembers(DirectoryRoles rol) /// Confirmar si un rol tiene permisos de saltarse las directivas. /// /// Rol a confirmar. - public static bool UsePolicy(DirectoryRoles rol) + public static bool UsePolicy(Types.Identity.Enumerations.Roles rol) { - DirectoryRoles[] roles = + Types.Identity.Enumerations.Roles[] roles = [ - DirectoryRoles.System, - DirectoryRoles.Guest, - DirectoryRoles.RoyalGuest + Types.Identity.Enumerations.Roles.System, + Types.Identity.Enumerations.Roles.Guest, + Types.Identity.Enumerations.Roles.RoyalGuest ]; return roles.Contains(rol); @@ -96,16 +96,16 @@ public static bool UsePolicy(DirectoryRoles rol) /// Confirmar si un rol tiene permisos de crear directivas. /// /// Rol a confirmar. - public static bool CreatePolicy(DirectoryRoles rol) + public static bool CreatePolicy(Types.Identity.Enumerations.Roles rol) { - DirectoryRoles[] roles = + Types.Identity.Enumerations.Roles[] roles = [ - DirectoryRoles.System, - DirectoryRoles.SuperManager, - DirectoryRoles.Manager, - DirectoryRoles.Operator, - DirectoryRoles.RoyalGuest + Types.Identity.Enumerations.Roles.System, + Types.Identity.Enumerations.Roles.SuperManager, + Types.Identity.Enumerations.Roles.Manager, + Types.Identity.Enumerations.Roles.Operator, + Types.Identity.Enumerations.Roles.RoyalGuest ]; return roles.Contains(rol); @@ -118,14 +118,14 @@ public static bool CreatePolicy(DirectoryRoles rol) /// Confirmar si un rol tiene permisos de alterar datos como nombres de la organización etc... /// /// Rol a confirmar. - public static bool DataAlter(DirectoryRoles rol) + public static bool DataAlter(Types.Identity.Enumerations.Roles rol) { - DirectoryRoles[] roles = + Types.Identity.Enumerations.Roles[] roles = [ - DirectoryRoles.System, - DirectoryRoles.SuperManager, - DirectoryRoles.Manager, + Types.Identity.Enumerations.Roles.System, + Types.Identity.Enumerations.Roles.SuperManager, + Types.Identity.Enumerations.Roles.Manager, ]; return roles.Contains(rol); @@ -137,18 +137,18 @@ public static bool DataAlter(DirectoryRoles rol) /// Confirmar si un rol tiene permisos de saltarse las directivas. /// /// Rol a confirmar. - public static bool ViewPolicy(DirectoryRoles rol) + public static bool ViewPolicy(Types.Identity.Enumerations.Roles rol) { - DirectoryRoles[] roles = + Types.Identity.Enumerations.Roles[] roles = [ - DirectoryRoles.System, - DirectoryRoles.SuperManager, - DirectoryRoles.Manager, - DirectoryRoles.Operator, - DirectoryRoles.AccountsOperator, - DirectoryRoles.Regular, - DirectoryRoles.RoyalGuest + Types.Identity.Enumerations.Roles.System, + Types.Identity.Enumerations.Roles.SuperManager, + Types.Identity.Enumerations.Roles.Manager, + Types.Identity.Enumerations.Roles.Operator, + Types.Identity.Enumerations.Roles.AccountsOperator, + Types.Identity.Enumerations.Roles.Regular, + Types.Identity.Enumerations.Roles.RoyalGuest ]; return roles.Contains(rol); @@ -165,7 +165,7 @@ public static bool ViewPolicy(DirectoryRoles rol) /// Confirmar si un rol tiene permisos de ver el directorio. /// /// Rol a confirmar. - public static bool View(IEnumerable roles) + public static bool View(IEnumerable roles) { // Recorrer roles. @@ -183,7 +183,7 @@ public static bool View(IEnumerable roles) /// Confirmar si un rol tiene permisos de ver los integrantes. /// /// Rol a confirmar. - public static bool ViewMembers(IEnumerable roles) + public static bool ViewMembers(IEnumerable roles) { // Recorrer roles. @@ -201,7 +201,7 @@ public static bool ViewMembers(IEnumerable roles) /// Confirmar si un rol tiene permisos para alterar los integrantes. /// /// Rol a confirmar. - public static bool AlterMembers(IEnumerable roles) + public static bool AlterMembers(IEnumerable roles) { // Recorrer roles. foreach (var rol in roles) @@ -218,7 +218,7 @@ public static bool AlterMembers(IEnumerable roles) /// Confirmar si un rol tiene permisos de saltarse las directivas. /// /// Rol a confirmar. - public static bool UsePolicy(IEnumerable roles) + public static bool UsePolicy(IEnumerable roles) { // Recorrer roles. @@ -236,7 +236,7 @@ public static bool UsePolicy(IEnumerable roles) /// Confirmar si un rol tiene permisos de crear directivas. /// /// Rol a confirmar. - public static bool CreatePolicy(IEnumerable roles) + public static bool CreatePolicy(IEnumerable roles) { // Recorrer roles. @@ -255,7 +255,7 @@ public static bool CreatePolicy(IEnumerable roles) /// Confirmar si un rol tiene permisos de alterar datos como nombres de la organización etc... /// /// Rol a confirmar. - public static bool DataAlter(IEnumerable roles) + public static bool DataAlter(IEnumerable roles) { // Recorrer roles. foreach (var rol in roles) @@ -269,7 +269,7 @@ public static bool DataAlter(IEnumerable roles) - public static bool ViewPolicy(IEnumerable roles) + public static bool ViewPolicy(IEnumerable roles) { // Recorrer roles. foreach (var rol in roles) From cfa889b34c5fbba8f3d077b81a6a860bb201201a Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 9 Feb 2024 19:00:51 -0500 Subject: [PATCH 067/178] -> LIN.Cloud.Identity --- LIN.Identity/Data/Queries/Identities.cs | 1 + LIN.Identity/Program.cs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/LIN.Identity/Data/Queries/Identities.cs b/LIN.Identity/Data/Queries/Identities.cs index ed77ae2..0a7b15a 100644 --- a/LIN.Identity/Data/Queries/Identities.cs +++ b/LIN.Identity/Data/Queries/Identities.cs @@ -71,6 +71,7 @@ public static IQueryable GetAccounts(int id, Models.Account filter } + public static IQueryable GetAccountsByIdentity(int id, Models.Account filters, Conexión context) { diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs index 997e0dd..09c9f4f 100644 --- a/LIN.Identity/Program.cs +++ b/LIN.Identity/Program.cs @@ -40,13 +40,11 @@ builder.Services.AddControllers(); - // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); - try { // Si la base de datos no existe From 74f6e94c37c97853565580775f4fae3b09416f34 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 19 Feb 2024 02:39:24 -0500 Subject: [PATCH 068/178] Mejoras con .NET 8 y caracteristicas --- LIN.Cloud.Identity.sln | 49 ++ .../.config/dotnet-tools.json | 2 +- .../Areas/Accounts/AccountController.cs | 259 ++++++++++ .../AuthenticationController.cs | 84 ++- .../Areas/Authentication/IntentsController.cs | 10 +- .../Areas/Directories/DirectoryController.cs | 103 ++++ .../Areas/Groups/GroupsController.cs | 221 ++++++++ .../Areas/Groups/GroupsMembersController.cs | 425 +++++++++++++++ .../Areas/Organizations/IdentityController.cs | 188 +++++++ .../Areas/Organizations/MemberController.cs | 143 +++++ .../Organizations/OrganizationController.cs | 83 ++- LIN.Cloud.Identity/Data/Accounts.cs | 408 +++++++++++++++ LIN.Cloud.Identity/Data/Builders/Account.cs | 252 +++++++++ .../Data/Builders/Identities.cs | 138 +++++ LIN.Cloud.Identity/Data/DirectoryMembers.cs | 473 +++++++++++++++++ LIN.Cloud.Identity/Data/GroupMembers.cs | 488 ++++++++++++++++++ LIN.Cloud.Identity/Data/Groups.cs | 475 +++++++++++++++++ LIN.Cloud.Identity/Data/Identities.cs | 205 ++++++++ LIN.Cloud.Identity/Data/IdentityRoles.cs | 264 ++++++++++ LIN.Cloud.Identity/Data/Organizations.cs | 341 ++++++++++++ LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 32 ++ LIN.Cloud.Identity/Program.cs | 65 +++ .../Properties/launchSettings.json | 14 +- .../Services/Auth/Authentication.cs | 123 +++++ .../Services/Auth/JwtService.cs | 19 +- .../Services/Configuration.cs | 2 +- .../Services/Database/DataContext.cs | 179 +++++++ .../Services/Database/DataService.cs | 126 +++++ .../Services/Database/Database.cs | 99 ++++ .../Filters/IdentityTokenAttribute.cs | 9 +- .../Services/Formats/Account.cs | 80 +++ .../Services/Formats/Identities.cs | 25 + LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 161 ++++++ .../Services/Middlewares}/IPMiddleware.cs | 16 +- .../Middlewares/MiddlewareExtensions.cs | 38 ++ .../Services/Middlewares/QuotaMiddleware.cs | 54 ++ .../Services}/Models/JwtModel.cs | 2 +- .../Services/Models/QueryAccountFilter.cs | 19 + .../Services/Models/QueryIdentityFilter.cs | 16 + .../Services/Realtime/PassKeyHub.cs | 236 +++++++++ .../Services/Realtime/PassKeyHubActions.cs | 76 +++ .../Services/Realtime/PassKeyHubFunctions.cs | 15 + LIN.Cloud.Identity/Usings.cs | 42 ++ .../appsettings.Development.json | 0 LIN.Cloud.Identity/appsettings.json | 16 + LIN.Identity.sln | 81 --- .../Areas/Accounts/AccountController.cs | 311 ----------- .../Areas/Accounts/AccountLogsController.cs | 32 -- .../Accounts/AccountSecurityController.cs | 100 ---- .../Applications/ApplicationController.cs | 101 ---- .../Areas/Directories/DirectoryController.cs | 136 ----- .../Directories/DirectoryMemberController.cs | 53 -- .../Areas/Organizations/MemberController.cs | 154 ------ .../Areas/Policies/PolicyController.cs | 124 ----- LIN.Identity/Conexion.cs | 225 -------- LIN.Identity/Data/Applications.cs | 322 ------------ .../Data/Areas/Accounts/AccountsGet.cs | 400 -------------- .../Data/Areas/Accounts/AccountsPost.cs | 63 --- .../Data/Areas/Accounts/AccountsUpdate.cs | 311 ----------- .../Data/Areas/Directories/Directories.cs | 239 --------- .../Areas/Directories/DirectoryMembers.cs | 131 ----- .../Data/Areas/Organizations/Members.cs | 158 ------ .../Data/Areas/Organizations/Organizations.cs | 270 ---------- LIN.Identity/Data/Context.cs | 162 ------ LIN.Identity/Data/Identities.cs | 56 -- LIN.Identity/Data/Logins.cs | 121 ----- LIN.Identity/Data/Mails.cs | 315 ----------- LIN.Identity/Data/Policies.cs | 188 ------- LIN.Identity/Data/Queries/Directories.cs | 67 --- LIN.Identity/Data/Queries/Identities.cs | 306 ----------- LIN.Identity/Hubs/PasskeyHub.cs | 300 ----------- LIN.Identity/LIN.Identity.csproj | 38 -- LIN.Identity/Models/Account.cs | 66 --- LIN.Identity/Program.cs | 91 ---- LIN.Identity/Services/EmailWorker.cs | 137 ----- LIN.Identity/Services/Iam/Applications.cs | 100 ---- LIN.Identity/Services/Image.cs | 120 ----- LIN.Identity/Services/Login/LoginNormal.cs | 46 -- LIN.Identity/Services/Login/LoginService.cs | 164 ------ LIN.Identity/Usings.cs | 19 - LIN.Identity/Validations/Account.cs | 47 -- LIN.Identity/Validations/AccountPassword.cs | 68 --- LIN.Identity/Validations/Roles.cs | 284 ---------- LIN.Identity/wwwroot/Plantillas/Email.html | 151 ------ LIN.Identity/wwwroot/Plantillas/Password.html | 153 ------ .../wwwroot/Plantillas/Plantilla.html | 68 --- LIN.Identity/wwwroot/profile.png | Bin 4178 -> 0 bytes LIN.Identity/wwwroot/user.png | Bin 956 -> 0 bytes 88 files changed, 5964 insertions(+), 6389 deletions(-) create mode 100644 LIN.Cloud.Identity.sln rename {LIN.Identity => LIN.Cloud.Identity}/.config/dotnet-tools.json (82%) create mode 100644 LIN.Cloud.Identity/Areas/Accounts/AccountController.cs rename {LIN.Identity => LIN.Cloud.Identity}/Areas/Authentication/AuthenticationController.cs (55%) rename {LIN.Identity => LIN.Cloud.Identity}/Areas/Authentication/IntentsController.cs (81%) create mode 100644 LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs create mode 100644 LIN.Cloud.Identity/Areas/Groups/GroupsController.cs create mode 100644 LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs create mode 100644 LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs create mode 100644 LIN.Cloud.Identity/Areas/Organizations/MemberController.cs rename {LIN.Identity => LIN.Cloud.Identity}/Areas/Organizations/OrganizationController.cs (53%) create mode 100644 LIN.Cloud.Identity/Data/Accounts.cs create mode 100644 LIN.Cloud.Identity/Data/Builders/Account.cs create mode 100644 LIN.Cloud.Identity/Data/Builders/Identities.cs create mode 100644 LIN.Cloud.Identity/Data/DirectoryMembers.cs create mode 100644 LIN.Cloud.Identity/Data/GroupMembers.cs create mode 100644 LIN.Cloud.Identity/Data/Groups.cs create mode 100644 LIN.Cloud.Identity/Data/Identities.cs create mode 100644 LIN.Cloud.Identity/Data/IdentityRoles.cs create mode 100644 LIN.Cloud.Identity/Data/Organizations.cs create mode 100644 LIN.Cloud.Identity/LIN.Cloud.Identity.csproj create mode 100644 LIN.Cloud.Identity/Program.cs rename {LIN.Identity => LIN.Cloud.Identity}/Properties/launchSettings.json (72%) create mode 100644 LIN.Cloud.Identity/Services/Auth/Authentication.cs rename LIN.Identity/Services/Jwt.cs => LIN.Cloud.Identity/Services/Auth/JwtService.cs (92%) rename {LIN.Identity => LIN.Cloud.Identity}/Services/Configuration.cs (93%) create mode 100644 LIN.Cloud.Identity/Services/Database/DataContext.cs create mode 100644 LIN.Cloud.Identity/Services/Database/DataService.cs create mode 100644 LIN.Cloud.Identity/Services/Database/Database.cs rename LIN.Identity/Services/Filters/TokenAuthAttribute.cs => LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs (80%) create mode 100644 LIN.Cloud.Identity/Services/Formats/Account.cs create mode 100644 LIN.Cloud.Identity/Services/Formats/Identities.cs create mode 100644 LIN.Cloud.Identity/Services/Iam/RolesIam.cs rename {LIN.Identity/Services/Filters => LIN.Cloud.Identity/Services/Middlewares}/IPMiddleware.cs (66%) create mode 100644 LIN.Cloud.Identity/Services/Middlewares/MiddlewareExtensions.cs create mode 100644 LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs rename {LIN.Identity => LIN.Cloud.Identity/Services}/Models/JwtModel.cs (93%) create mode 100644 LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs create mode 100644 LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs create mode 100644 LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs create mode 100644 LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs create mode 100644 LIN.Cloud.Identity/Services/Realtime/PassKeyHubFunctions.cs create mode 100644 LIN.Cloud.Identity/Usings.cs rename {LIN.Identity => LIN.Cloud.Identity}/appsettings.Development.json (100%) create mode 100644 LIN.Cloud.Identity/appsettings.json delete mode 100644 LIN.Identity.sln delete mode 100644 LIN.Identity/Areas/Accounts/AccountController.cs delete mode 100644 LIN.Identity/Areas/Accounts/AccountLogsController.cs delete mode 100644 LIN.Identity/Areas/Accounts/AccountSecurityController.cs delete mode 100644 LIN.Identity/Areas/Applications/ApplicationController.cs delete mode 100644 LIN.Identity/Areas/Directories/DirectoryController.cs delete mode 100644 LIN.Identity/Areas/Directories/DirectoryMemberController.cs delete mode 100644 LIN.Identity/Areas/Organizations/MemberController.cs delete mode 100644 LIN.Identity/Areas/Policies/PolicyController.cs delete mode 100644 LIN.Identity/Conexion.cs delete mode 100644 LIN.Identity/Data/Applications.cs delete mode 100644 LIN.Identity/Data/Areas/Accounts/AccountsGet.cs delete mode 100644 LIN.Identity/Data/Areas/Accounts/AccountsPost.cs delete mode 100644 LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs delete mode 100644 LIN.Identity/Data/Areas/Directories/Directories.cs delete mode 100644 LIN.Identity/Data/Areas/Directories/DirectoryMembers.cs delete mode 100644 LIN.Identity/Data/Areas/Organizations/Members.cs delete mode 100644 LIN.Identity/Data/Areas/Organizations/Organizations.cs delete mode 100644 LIN.Identity/Data/Context.cs delete mode 100644 LIN.Identity/Data/Identities.cs delete mode 100644 LIN.Identity/Data/Logins.cs delete mode 100644 LIN.Identity/Data/Mails.cs delete mode 100644 LIN.Identity/Data/Policies.cs delete mode 100644 LIN.Identity/Data/Queries/Directories.cs delete mode 100644 LIN.Identity/Data/Queries/Identities.cs delete mode 100644 LIN.Identity/Hubs/PasskeyHub.cs delete mode 100644 LIN.Identity/LIN.Identity.csproj delete mode 100644 LIN.Identity/Models/Account.cs delete mode 100644 LIN.Identity/Program.cs delete mode 100644 LIN.Identity/Services/EmailWorker.cs delete mode 100644 LIN.Identity/Services/Iam/Applications.cs delete mode 100644 LIN.Identity/Services/Image.cs delete mode 100644 LIN.Identity/Services/Login/LoginNormal.cs delete mode 100644 LIN.Identity/Services/Login/LoginService.cs delete mode 100644 LIN.Identity/Usings.cs delete mode 100644 LIN.Identity/Validations/Account.cs delete mode 100644 LIN.Identity/Validations/AccountPassword.cs delete mode 100644 LIN.Identity/Validations/Roles.cs delete mode 100644 LIN.Identity/wwwroot/Plantillas/Email.html delete mode 100644 LIN.Identity/wwwroot/Plantillas/Password.html delete mode 100644 LIN.Identity/wwwroot/Plantillas/Plantilla.html delete mode 100644 LIN.Identity/wwwroot/profile.png delete mode 100644 LIN.Identity/wwwroot/user.png diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln new file mode 100644 index 0000000..a41ac76 --- /dev/null +++ b/LIN.Cloud.Identity.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity", "LIN.Cloud.Identity\LIN.Cloud.Identity.csproj", "{D7EF6814-1C64-44BC-BEAF-87835D44EEF8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types.Cloud.Identity", "..\..\Tipos\LIN.Types.Cloud.Identity\LIN.Types.Cloud.Identity.csproj", "{EA3955F9-EF0C-48E4-A428-02739EF322E1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Modules", "..\..\Tipos\LIN.Modules\LIN.Modules.csproj", "{B784D380-1A0F-40C2-9F31-E1A7641DD3B5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http", "..\..\Tipos\Http\Http.csproj", "{26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types", "..\..\Tipos\LIN.Types\LIN.Types.csproj", "{E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.Build.0 = Release|Any CPU + {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Release|Any CPU.Build.0 = Release|Any CPU + {B784D380-1A0F-40C2-9F31-E1A7641DD3B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B784D380-1A0F-40C2-9F31-E1A7641DD3B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B784D380-1A0F-40C2-9F31-E1A7641DD3B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B784D380-1A0F-40C2-9F31-E1A7641DD3B5}.Release|Any CPU.Build.0 = Release|Any CPU + {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Debug|Any CPU.Build.0 = Debug|Any CPU + {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Release|Any CPU.ActiveCfg = Release|Any CPU + {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Release|Any CPU.Build.0 = Release|Any CPU + {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {742D8B13-7C99-485E-B3EF-E51281923F66} + EndGlobalSection +EndGlobal diff --git a/LIN.Identity/.config/dotnet-tools.json b/LIN.Cloud.Identity/.config/dotnet-tools.json similarity index 82% rename from LIN.Identity/.config/dotnet-tools.json rename to LIN.Cloud.Identity/.config/dotnet-tools.json index 558293e..fd9a39d 100644 --- a/LIN.Identity/.config/dotnet-tools.json +++ b/LIN.Cloud.Identity/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "7.0.10", + "version": "8.0.1", "commands": [ "dotnet-ef" ] diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs new file mode 100644 index 0000000..9b4e839 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -0,0 +1,259 @@ +namespace LIN.Cloud.Identity.Areas.Accounts; + + +[Route("account")] +public class AccountController : ControllerBase +{ + + + /// + /// Crear una cuenta LIN. + /// + /// Modelo de la cuenta. + [HttpPost("create")] + public async Task Create([FromBody] AccountModel? modelo) + { + + // Validaciones del modelo. + if (modelo == null || modelo.Identity == null || modelo.Password.Length < 4 || modelo.Name.Length <= 0 || modelo.Identity.Unique.Length <= 0) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; + + // Validar usuario y nombre. + var (pass, message) = Services.Formats.Account.Validate(modelo); + + // Si no fue valido. + if (!pass) + return new(Responses.InvalidParam) + { + Message = message + }; + + // Organización del modelo + modelo = Services.Formats.Account.Process(modelo); + + // Creación del usuario + var response = await Data.Accounts.Create(modelo, 0); + + // Evaluación + if (response.Response != Responses.Success) + return new(response.Response) + { + Message = "Hubo un error al crear la cuenta." + }; + + // Obtiene el usuario + var token = JwtService.Generate(response.Model, 0); + + // Retorna el resultado + return new CreateResponse() + { + LastID = response.Model.Identity.Id, + Response = Responses.Success, + Token = token, + Message = "Cuenta creada satisfactoriamente." + }; + + } + + + + /// + /// Obtener una cuenta. + /// + /// Id de la cuenta. + /// Token de acceso. + [HttpGet("read/id")] + [IdentityToken] + public async Task> Read([FromQuery] int id, [FromHeader] string token) + { + + // Id es invalido. + if (id <= 0) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtiene el usuario. + var response = await Data.Accounts.Read(id, new() + { + AccountContext = tokenInfo.AccountId, + FindOn = FindOn.StableAccounts, + IdentityContext = tokenInfo.IdentityId, + IsAdmin = false + }); + + // Si es erróneo + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtener una cuenta. + /// + /// Identidad. + /// Token de acceso. + [HttpGet("read/user")] + [IdentityToken] + public async Task> Read([FromQuery] string user, [FromHeader] string token) + { + + // Usuario es invalido. + if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(token)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + + + // Obtiene el usuario. + var response = await Data.Accounts.Read(user, new() + { + AccountContext = tokenInfo.AccountId, + FindOn = FindOn.StableAccounts, + IdentityContext = tokenInfo.IdentityId, + IsAdmin = false + }); + + // Si es erróneo + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = response.Response, + Model = new() + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtener una cuenta según Id de identidad. + /// + /// Id de la identidad. + /// Token de acceso. + [HttpGet("read/identity")] + [IdentityToken] + public async Task> ReadByIdentity([FromQuery] int id, [FromHeader] string token) + { + + // Id es invalido. + if (id <= 0) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtiene el usuario. + var response = await Data.Accounts.ReadByIdentity(id, new() + { + AccountContext = tokenInfo.AccountId, + FindOn = FindOn.StableAccounts, + IdentityContext = tokenInfo.IdentityId, + IsAdmin = false + }); + + // Si es erróneo + if (response.Response != Responses.Success) + return new ReadOneResponse() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtiene una lista de diez (10) usuarios que coincidan con un patron. + /// + /// Patron + /// Token de acceso + [HttpGet("search")] + [IdentityToken] + public async Task> Search([FromQuery] string pattern, [FromHeader] string token) + { + + // Comprobación + if (pattern.Trim().Length <= 0 || string.IsNullOrWhiteSpace(pattern)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son inválidos." + }; + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + + // Obtiene el usuario + var response = await Data.Accounts.Search(pattern, new() + { + AccountContext = tokenInfo.AccountId, + FindOn = FindOn.StableAccounts, + IsAdmin =false, + IdentityContext = tokenInfo.IdentityId, + }); + + return response; + } + + + + /// + /// Obtiene una lista cuentas. + /// + /// IDs de las cuentas + /// Token de acceso + [HttpPost("findAll")] + [IdentityToken] + public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtiene el usuario + var response = await Data.Accounts.FindAll(ids, new() + { + AccountContext = tokenInfo.AccountId, + FindOn = FindOn.StableAccounts, + IsAdmin = false, + IdentityContext = tokenInfo.IdentityId, + }); + + return response; + + } + + + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs similarity index 55% rename from LIN.Identity/Areas/Authentication/AuthenticationController.cs rename to LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 06cc8cb..b642761 100644 --- a/LIN.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,6 +1,4 @@ -using LIN.Identity.Services.Login; - -namespace LIN.Identity.Areas.Authentication; +namespace LIN.Cloud.Identity.Areas.Authentication; [Route("authentication")] @@ -15,7 +13,6 @@ public class AuthenticationController : ControllerBase /// Contraseña del usuario /// Key de aplicación [HttpGet("login")] - [TokenAuth] public async Task> Login([FromQuery] string user, [FromQuery] string password, [FromHeader] string application) { @@ -26,56 +23,57 @@ public async Task> Login([FromQuery] string us Message = "Uno o varios parámetros son invalido." }; - // Obtiene el usuario. - var response = await Data.Accounts.Read(user, new() - { - SensibleInfo = true, - IsAdmin = true, - IncludeOrg = Models.IncludeOrg.Include, - OrgLevel = Models.IncludeOrgLevel.Advance, - FindOn = Models.FindOn.StableAccounts - }); + + // Generar el servicio. + var service = new Services.Auth.Authentication(user, password, application); + + // Respuesta. + var response = await service.Start(); // Validación al obtener el usuario - switch (response.Response) + switch (response) { // Correcto case Responses.Success: break; + // No existe esta cuenta. + case Responses.NotExistAccount: + return new() + { + Response = Responses.NotExistAccount, + Message = "No existe esta cuenta." + }; + + // Contraseña invalida. + case Responses.InvalidPassword: + return new() + { + Response = Responses.InvalidPassword, + Message = "Contraseña incorrecta." + }; + // Incorrecto default: - return new(response.Response) + return new() { + Response = Responses.Undefined, Message = "Hubo un error grave." }; } - - // Estrategia de login - LoginService strategy; - - // Definir la estrategia - strategy = new LoginNormal(response.Model, application, password); - - // Respuesta del login - var loginResponse = await strategy.Login(); - - // Respuesta - if (loginResponse.Response != Responses.Success) - return new ReadOneResponse() - { - Message = loginResponse.Message, - Response = loginResponse.Response - }; - - // Genera el token - var token = Jwt.Generate(response.Model, 0); + var token = JwtService.Generate(service.Account!, 0); + // Respuesta. + var http = new ReadOneResponse + { + Model = service.Account!, + Response = Responses.Success, + Token = token + }; - response.Token = token; - return response; + return http; } @@ -86,29 +84,23 @@ public async Task> Login([FromQuery] string us /// /// Token de acceso [HttpGet("LoginWithToken")] - [TokenAuth] + [IdentityToken] public async Task> LoginWithToken([FromHeader] string token) { // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtiene el usuario. - var response = await Data.Accounts.Read(tokenInfo.AccountId, new Models.Account() + var response = await Data.Accounts.Read(tokenInfo.AccountId, new QueryAccountFilter() { - SensibleInfo = true, IsAdmin = true, - IncludeOrg = IncludeOrg.Include, - OrgLevel = IncludeOrgLevel.Advance, FindOn = FindOn.StableAccounts }); if (response.Response != Responses.Success) return new(response.Response); - if (response.Model.Estado != AccountStatus.Normal) - return new(Responses.NotExistAccount); - response.Token = token; return response; diff --git a/LIN.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs similarity index 81% rename from LIN.Identity/Areas/Authentication/IntentsController.cs rename to LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index d1d473f..6996e23 100644 --- a/LIN.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -1,6 +1,6 @@ -using LIN.Identity.Models; +using LIN.Cloud.Identity.Services.Realtime; -namespace LIN.Identity.Areas.Authentication; +namespace LIN.Cloud.Identity.Areas.Authentication; [Route("Intents")] @@ -12,18 +12,18 @@ public class IntentsController : ControllerBase /// /// Token de acceso [HttpGet] - [TokenAuth] + [IdentityToken] public HttpReadAllResponse GetAll([FromHeader] string token) { try { // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Cuenta var account = (from a in PassKeyHub.Attempts - where a.Key == tokenInfo.Unique + where a.Key == tokenInfo.Unique.ToLower() select a).FirstOrDefault().Value ?? new(); // Hora actual diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs new file mode 100644 index 0000000..b6e3dd6 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -0,0 +1,103 @@ +namespace LIN.Cloud.Identity.Areas.Directories; + + +[Route("directory")] +public class DirectoryController : ControllerBase +{ + + + /// + /// Obtener los directorios asociados. + /// + /// Token de acceso. + [HttpGet("read/all")] + [IdentityToken] + public async Task> ReadAll([FromHeader] string token, [FromHeader] int organization) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Validar organización. + if (organization <= 0) + return new() + { + Response = Responses.InvalidParam, + Message = "Id de la organización invalido." + }; + + // Obtiene el usuario. + var response = await Data.DirectoryMembers.Read(tokenInfo.IdentityId, organization); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtener los integrantes asociados a un directorio. + /// + /// Token de acceso. + /// ID del directorio. + [HttpGet("read/members")] + [IdentityToken] + public async Task> ReadMembers([FromHeader] string token, [FromQuery] int directory) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtener la organización. + var orgId = await Data.Groups.GetOwner(directory); + + // Si hubo un error. + if (orgId.Response != Responses.Success) + return new() + { + Message = "Hubo un error al encontrar la organización dueña de este grupo.", + Response = Responses.Unauthorized + }; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso a la información este directorio.", + Response = Responses.Unauthorized + }; + + + // Obtiene el usuario. + var response = await Data.DirectoryMembers.ReadMembers(directory); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs new file mode 100644 index 0000000..67fffe6 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -0,0 +1,221 @@ +namespace LIN.Cloud.Identity.Areas.Groups; + + +[Route("groups")] +public class GroupsController : ControllerBase +{ + + + /// + /// Crear nuevo grupo. + /// + /// Modelo del grupo. + /// Token de acceso. + [HttpPost] + [IdentityToken] + public async Task Create([FromBody] GroupModel group, [FromHeader] string token) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, group.OwnerId ?? 0); + + // Iam. + bool iam = ValidateRoles.ValidateAlterMembers(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso para crear grupos.", + Response = Responses.Unauthorized + }; + + // Formato de la identidad. + group.Identity.Type = IdentityType.Group; + Services.Formats.Identities.Process(group.Identity); + + // Obtener el modelo. + var response = await Data.Groups.Create(group); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return new CreateResponse() + { + Response = Responses.Success, + LastID = response.Model.Id + }; + + } + + + + + /// + /// Obtener los grupos asociados a una organización. + /// + /// Token de acceso. + /// Id de la organización. + [HttpGet("all")] + [IdentityToken] + public async Task> ReadAll([FromHeader] string token, [FromHeader] int organization) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso para leer grupos.", + Response = Responses.Unauthorized + }; + + // Obtener el modelo. + var response = await Data.Groups.ReadAll(organization); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + + /// + /// Obtener un grupo según el Id. + /// + /// Token de acceso. + /// Id del grupo. + [HttpGet] + [IdentityToken] + public async Task> ReadOne([FromHeader] string token, [FromHeader] int id) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + + // Obtener la organización. + var orgId = await Data.Groups.GetOwner(id); + + // Si hubo un error. + if (orgId.Response != Responses.Success) + return new() + { + Message = "Hubo un error al encontrar la organización dueña de este grupo.", + Response = Responses.Unauthorized + }; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso para leer grupos.", + Response = Responses.Unauthorized + }; + + // Obtener el modelo. + var response = await Data.Groups.Read(id); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtener un grupo según el Id. + /// + /// Token de acceso. + /// Id del grupo. + [HttpGet("identity")] + [IdentityToken] + public async Task> ReadIdentity([FromHeader] string token, [FromHeader] int id) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + + // Obtener la organización. + var orgId = await Data.Groups.GetOwnerByIdentity(id); + + // Si hubo un error. + if (orgId.Response != Responses.Success) + return new() + { + Message = "Hubo un error al encontrar la organización dueña de este grupo.", + Response = Responses.Unauthorized + }; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso para leer grupos.", + Response = Responses.Unauthorized + }; + + // Obtener el modelo. + var response = await Data.Groups.ReadByIdentity(id); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs new file mode 100644 index 0000000..ca6a3b4 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -0,0 +1,425 @@ +namespace LIN.Cloud.Identity.Areas.Groups; + + +[Route("Groups/members")] +public class GroupsMembersController : Controller +{ + + + /// + /// Crear nuevo integrante. + /// + /// Token de acceso. + /// Modelo. + [HttpPost] + [IdentityToken] + public async Task Create([FromHeader] string token, [FromBody] GroupMember model) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtener la organización. + var orgId = await Data.Groups.GetOwner(model.GroupId); + + // Si hubo un error. + if (orgId.Response != Responses.Success) + return new() + { + Message = "Hubo un error al encontrar la organización dueña de este grupo.", + Response = Responses.Unauthorized + }; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + + // Iam. + bool iam = ValidateRoles.ValidateAlterMembers(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso a la información este directorio.", + Response = Responses.Unauthorized + }; + + // Valida si el usuario pertenece a la organización. + var idIsIn = await Data.DirectoryMembers.IamIn(model.IdentityId, orgId.Model); + + // Si no existe. + if (idIsIn.Response != Responses.Success) + return new() + { + Message = $"La identidad {model.IdentityId} no pertenece al directorio de la organización {orgId.Model}.", + Response = Responses.Unauthorized + }; + + + // Crear el usuario. + var response = await Data.GroupMembers.Create(model); + + // Retorna el resultado + return response; + + } + + + + /// + /// Crear nuevos integrantes en un grupo. + /// + /// Token de acceso. + /// Id del grupo. + /// Identidades. + [HttpPost("list")] + [IdentityToken] + public async Task Create([FromHeader] string token, [FromHeader] int group, [FromBody] List ids) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtener la organización. + var orgId = await Data.Groups.GetOwner(group); + + // Si hubo un error. + if (orgId.Response != Responses.Success) + return new() + { + Message = "Hubo un error al encontrar la organización dueña de este grupo.", + Response = Responses.Unauthorized + }; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + + // Iam. + bool iam = ValidateRoles.ValidateAlterMembers(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso a la información este directorio.", + Response = Responses.Unauthorized + }; + + + // Solo elementos distintos. + ids = ids.Distinct().ToList(); + + // Valida si el usuario pertenece a la organización. + var idIsIn = await Data.DirectoryMembers.IamIn(ids, orgId.Model); + + // Si no existe. + if (idIsIn.Response != Responses.Success) + return new() + { + Message = $"Errores encontrados al validar si las identidades pertenecen a la organización {orgId.Model}.", + Response = Responses.Unauthorized + }; + + + // Crear el usuario. + var response = await Data.GroupMembers.Create(ids.Select(id => new GroupMember + { + Group = new() + { + Id = group, + }, + Identity = new() + { + Id = id + } + })); + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtener los integrantes asociados a un grupo. + /// + /// Token de acceso. + /// ID del grupo. + [HttpGet("read/all")] + [IdentityToken] + public async Task> ReadMembers([FromHeader] string token, [FromQuery] int group) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtener la organización. + var orgId = await Data.Groups.GetOwner(group); + + // Si hubo un error. + if (orgId.Response != Responses.Success) + return new() + { + Message = "Hubo un error al encontrar la organización dueña de este grupo.", + Response = Responses.Unauthorized + }; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso a la información este directorio.", + Response = Responses.Unauthorized + }; + + + // Obtiene el usuario. + var response = await Data.GroupMembers.ReadAll(group); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Buscar en los integrantes de un grupo. + /// + /// Token de acceso. + /// Grupo. + /// Patron de búsqueda. + [HttpGet("search")] + [IdentityToken] + public async Task> Search([FromHeader] string token, [FromHeader] int group, [FromQuery] string pattern) + { + + // Información del token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtener la organización. + var orgId = await Data.Groups.GetOwner(group); + + // Si hubo un error. + if (orgId.Response != Responses.Success) + return new() + { + Message = "Hubo un error al encontrar la organización dueña de este grupo.", + Response = Responses.Unauthorized + }; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso a la información este directorio.", + Response = Responses.Unauthorized + }; + + + // Obtiene los miembros. + var members = await Data.GroupMembers.Search(pattern, group); + + // Error al obtener los integrantes. + if (members.Response != Responses.Success) + return new() + { + Message = "Error.", + Response = Responses.NotRows + }; + + // Retorna el resultado + return members; + + } + + + + + /// + /// Buscar en los grupos de un grupo. + /// + /// Token de acceso. + /// Grupo. + /// Patron de búsqueda. + [HttpGet("search/groups")] + [IdentityToken] + public async Task> SearchOnGroups([FromHeader] string token, [FromHeader] int group, [FromQuery] string pattern) + { + + // Información del token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtener la organización. + var orgId = await Data.Groups.GetOwner(group); + + // Si hubo un error. + if (orgId.Response != Responses.Success) + return new() + { + Message = "Hubo un error al encontrar la organización dueña de este grupo.", + Response = Responses.Unauthorized + }; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso a la información este directorio.", + Response = Responses.Unauthorized + }; + + + // Obtiene los miembros. + var members = await Data.GroupMembers.SearchGroups(pattern, group); + + // Error al obtener los integrantes. + if (members.Response != Responses.Success) + return new() + { + Message = "Error.", + Response = Responses.NotRows + }; + + // Retorna el resultado + return members; + + } + + + + /// + /// Eliminar un integrante + /// + /// Token de acceso. + /// ID del grupo. + [HttpDelete("remove")] + [IdentityToken] + public async Task DeleteMembers([FromHeader] string token, [FromQuery] int identity, [FromQuery] int group) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtener la organización. + var orgId = await Data.Groups.GetOwner(group); + + // Si hubo un error. + if (orgId.Response != Responses.Success) + return new() + { + Message = "Hubo un error al encontrar la organización dueña de este grupo.", + Response = Responses.Unauthorized + }; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + + // Iam. + bool iam = ValidateRoles.ValidateAlterMembers(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso a la información este directorio.", + Response = Responses.Unauthorized + }; + + + // Obtiene el usuario. + var response = await Data.GroupMembers.Delete(identity, group); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + /// + /// Obtener los grupos a los que una identidad pertenece. + /// + /// Token de acceso. + /// ID del grupo. + [HttpGet("read/on/all")] + [IdentityToken] + public async Task> OnMembers([FromHeader] string token, [FromQuery] int organization, [FromQuery] int identity) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso a la información este directorio.", + Response = Responses.Unauthorized + }; + + + // Obtiene el usuario. + var response = await Data.GroupMembers.OnMembers(organization, identity); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs new file mode 100644 index 0000000..81290bc --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -0,0 +1,188 @@ +namespace LIN.Cloud.Identity.Areas.Organizations; + + +[Route("identity")] +public class IdentityController : ControllerBase +{ + + + /// + /// Crear nuevo grupo. + /// + /// Modelo del grupo. + /// Token de acceso. + [HttpPost] + [IdentityToken] + public async Task Create([FromBody] IdentityRolesModel rolModel, [FromHeader] string token) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, rolModel.OrganizationId); + + // Iam. + bool iam = ValidateRoles.ValidateAlterMembers(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso para crear grupos.", + Response = Responses.Unauthorized + }; + + // Identidad. + rolModel.Identity = new() + { + Id = rolModel.IdentityId + }; + + // Organización. + rolModel.Organization = new() + { + Id = rolModel.OrganizationId + }; + + // Obtener el modelo. + var response = await Data.IdentityRoles.Create(rolModel); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return new() + { + Response = Responses.Success + }; + + } + + + + + /// + /// Obtener los roles asociados a una identidad. + /// + /// Token de acceso. + /// Identidad + /// Id de la organización. + [HttpGet("roles/all")] + [IdentityToken] + public async Task> ReadAll([FromHeader] string token, [FromHeader] int identity, [FromHeader] int organization) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso para leer grupos.", + Response = Responses.Unauthorized + }; + + + + var isIn = await Data.DirectoryMembers.IamIn(identity, organization); + + + if (isIn.Response != Responses.Success) + return new() + { + Message = $"La identidad {identity} no pertenece a la organización de contexto.", + Response = Responses.NotFoundDirectory + }; + + + // Obtener el modelo. + var response = await Data.IdentityRoles.ReadAll(identity, organization); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + + /// + /// Eliminar los roles asociados a una identidad. + /// + /// Token de acceso. + /// Identidad + /// Id de la organización. + /// Rol. + [HttpDelete("roles")] + [IdentityToken] + public async Task ReadAll([FromHeader] string token, [FromHeader] int identity, [FromHeader] int organization, [FromHeader] Roles rol) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + + // Iam. + bool iam = ValidateRoles.ValidateAlterMembers(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso para leer grupos.", + Response = Responses.Unauthorized + }; + + + + var isIn = await Data.DirectoryMembers.IamIn(identity, organization); + + + if (isIn.Response != Responses.Success) + return new() + { + Message = $"La identidad {identity} no pertenece a la organización de contexto.", + Response = Responses.NotFoundDirectory + }; + + + // Obtener el modelo. + var response = await Data.IdentityRoles.Remove(identity, rol, organization); + + // Si es erróneo + if (response.Response != Responses.Success) + return new() + { + Response = response.Response + }; + + // Retorna el resultado + return response; + + } + + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs new file mode 100644 index 0000000..65a6254 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs @@ -0,0 +1,143 @@ +using LIN.Types.Cloud.Identity.Abstracts; + +namespace LIN.Cloud.Identity.Areas.Organizations; + + +[Route("orgs/members")] +public class MemberController : ControllerBase +{ + + + /// + /// Crea un nuevo miembro en una organización. + /// + /// Modelo de la cuenta. + /// Token de acceso de un administrador. + /// Id de la organización. + [HttpPost] + [IdentityToken] + public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] int organization) + { + + // Validar el modelo. + if (modelo == null || modelo.Identity == null || string.IsNullOrWhiteSpace(modelo.Identity.Unique) || string.IsNullOrWhiteSpace(modelo.Name)) + return new() + { + Response = Responses.InvalidParam, + Message = "Uno o varios parámetros inválidos." + }; + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + + + // Ajustar el modelo. + modelo.Visibility = Visibility.Hidden; + modelo.Password = $"pwd@{DateTime.Now.Year}"; + modelo = Services.Formats.Account.Process(modelo); + + // Organización. + var orgIdentity = await Data.Organizations.GetDomain(organization); + + // Validar. + if (orgIdentity.Response != Responses.Success) + return new(Responses.NotRows) + { + Message = $"No se encontró la organización con Id '{organization}'" + }; + + // Validar usuario y nombre. + var (pass, message) = Services.Formats.Account.Validate(modelo); + + // Si no fue valido. + if (!pass) + return new(Responses.InvalidParam) + { + Message = message + }; + + // Agregar la identidad. + modelo.Identity.Unique = $"{modelo.Identity.Unique}@{orgIdentity.Model.Unique}"; + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + + // Iam. + bool iam = ValidateRoles.ValidateAlterMembers(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso para crear cuentas en esta organización.", + Response = Responses.Unauthorized + }; + + + // Creación del usuario + var response = await Data.Accounts.Create(modelo, organization); + + // Evaluación + if (response.Response != Responses.Success) + return new(response.Response); + + + // Retorna el resultado + return new CreateResponse() + { + LastID = response.Model.Id, + Response = Responses.Success, + Message = "Success" + }; + + } + + + + /// + /// Obtiene la lista de integrantes asociados a una organización. + /// + /// Token de acceso + [HttpGet] + [IdentityToken] + public async Task>> ReadAll([FromHeader] string token, [FromHeader] int organization) + { + + // Información del token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); ; + + + // Confirmar el rol. + var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + + // Iam. + bool iam = ValidateRoles.ValidateRead(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes acceso a la información este directorio.", + Response = Responses.Unauthorized + }; + + + // Obtiene los miembros. + var members = await Data.DirectoryMembers.ReadMembersByOrg(organization); + + // Error al obtener los integrantes. + if (members.Response != Responses.Success) + return new ReadAllResponse> + { + Message = "No se encontró la organización.", + Response = Responses.Unauthorized + }; + + // Retorna el resultado + return members; + + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs similarity index 53% rename from LIN.Identity/Areas/Organizations/OrganizationController.cs rename to LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index c949d38..259ad3c 100644 --- a/LIN.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Areas.Organizations; +namespace LIN.Cloud.Identity.Areas.Organizations; [Route("organizations")] @@ -25,20 +25,20 @@ public async Task Create([FromBody] OrganizationModel modelo // Ordenar el modelo. { - modelo.ID = 0; + modelo.Id = 0; modelo.Name = modelo.Name.Trim(); - modelo.Directory.Nombre = modelo.Directory.Nombre.Trim(); + modelo.Creation = DateTime.Now; modelo.Directory.Members = []; - modelo.Directory.Policies = []; - modelo.Directory.Creación = DateTime.Now; - modelo.Directory.Identity.Type = IdentityTypes.Directory; - modelo.Directory.Identity.DirectoryMembers = []; - modelo.Directory.Identity.Unique = modelo.Directory.Identity.Unique.Trim(); + modelo.Directory.Name = modelo.Directory.Name.Trim(); + modelo.Directory.Identity.EffectiveTime = DateTime.Now; + modelo.Directory.Identity.CreationTime = DateTime.Now; + modelo.Directory.Identity.EffectiveTime = DateTime.Now.AddYears(10); + modelo.Directory.Identity.Status = IdentityStatus.Enable; } // Creación de la organización. - var response = await Data.Areas.Organizations.Organizations.Create(modelo); + var response = await Data.Organizations.Create(modelo); // Evaluación. if (response.Response != Responses.Success) @@ -47,7 +47,7 @@ public async Task Create([FromBody] OrganizationModel modelo // Retorna el resultado. return new CreateResponse() { - LastID = response.Model.ID, + LastID = response.LastID, Response = Responses.Success, Message = "Success" }; @@ -62,7 +62,7 @@ public async Task Create([FromBody] OrganizationModel modelo /// ID de la organización /// Token de acceso [HttpGet("read/id")] - [TokenAuth] + [IdentityToken] public async Task> ReadOneByID([FromQuery] int id, [FromHeader] string token) { @@ -71,11 +71,11 @@ public async Task> ReadOneByID([FromQuery return new(Responses.InvalidParam); // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + - // Obtiene la organización - var response = await Data.Areas.Organizations.Organizations.Read(id); + var response = await Data.Organizations.Read(id); // Organización no encontrada. if (response.Response != Responses.Success) @@ -86,18 +86,28 @@ public async Task> ReadOneByID([FromQuery }; // No es publica y no pertenece a ella - if (!response.Model.IsPublic && tokenInfo.OrganizationId != response.Model.ID) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "Esta organización es privada y tu usuario no esta vinculado a ella.", - Model = new() + if (!response.Model.IsPublic) + { + + var iamIn = await Data.DirectoryMembers.IamIn(tokenInfo.IdentityId, response.Model.Id); + + if (iamIn.Response != Responses.Success) + return new ReadOneResponse() { - ID = response.Model.ID, - IsPublic = false, - Name = "Organización privada" - } - }; + Response = Responses.Unauthorized, + Message = "Esta organización es privada y tu usuario no esta vinculado a ella.", + Model = new() + { + Id = response.Model.Id, + IsPublic = false, + Name = "Organización privada" + } + }; + + + + } + return new ReadOneResponse() { @@ -105,6 +115,29 @@ public async Task> ReadOneByID([FromQuery Model = response.Model }; + } + + + + + /// + /// Obtiene las organizaciones donde un usuario es miembro. + /// + /// Token de acceso + [HttpGet("read/all")] + [IdentityToken] + public async Task> ReadAll([FromHeader] string token) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + + // Obtiene la organización + var response = await Data.Organizations.ReadAll(tokenInfo.IdentityId); + + + return response; } diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs new file mode 100644 index 0000000..4b7fddc --- /dev/null +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -0,0 +1,408 @@ +namespace LIN.Cloud.Identity.Data; + + +public static class Accounts +{ + + + + #region Abstracciones + + + + /// + /// Crear nueva cuenta. [Transacción] + /// + /// Modelo de la cuenta. + public static async Task> Create(AccountModel modelo, int organization) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Create(modelo, context, organization); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener una cuenta según el Id. + /// + /// Id de la cuenta. + /// Filtros de búsqueda. + public static async Task> Read(int id, Services.Models.QueryAccountFilter filters) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Read(id, filters, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener una cuenta según el identificador único. + /// + /// Único. + /// Filtros de búsqueda. + public static async Task> Read(string unique, Services.Models.QueryAccountFilter filters) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Read(unique, filters, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener una cuenta según el identificador único. + /// + /// Id de la identidad. + /// Filtros de búsqueda. + public static async Task> ReadByIdentity(int id, Services.Models.QueryAccountFilter filters) + { + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await ReadByIdentity(id, filters, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Buscar por patron. + /// + /// patron de búsqueda + /// Filtros + /// Contexto de conexión + public static async Task> Search(string pattern, QueryAccountFilter filters) + { + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Search(pattern, filters, context); + + // Retornar. + context.Close(contextKey); + return response; + } + + + + /// + /// Obtiene los usuarios con IDs coincidentes + /// + /// Lista de IDs + /// Contexto de base de datos + public static async Task> FindAll(List ids, QueryAccountFilter filters) + { + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await FindAll(ids, filters, context); + + // Retornar. + context.Close(contextKey); + return response; + } + + + + #endregion + + + + /// + /// Crear nueva cuenta. [Transacción] + /// + /// Modelo de la cuenta. + /// Contexto de conexión. + public static async Task> Create(AccountModel modelo, DataContext context, int organization = 0) + { + + // Pre. + modelo.Id = 0; + modelo.IdentityId = 0; + + // Transacción. + using var transaction = context.Database.BeginTransaction(); + + try + { + + // Guardar la cuenta. + await context.Accounts.AddAsync(modelo); + context.SaveChanges(); + + + if (organization > 0) + { + + var generalGroup = (from org in context.Organizations + where org.Id == organization + select org.DirectoryId).FirstOrDefault(); + + if (generalGroup <= 0) + { + throw new Exception("Organización no encontrada."); + } + + var x = new GroupMember() + { + Group = new() + { + Id = generalGroup + }, + Identity = modelo.Identity, + Type = GroupMemberTypes.User + }; + + context.Attach(x.Group); + + context.GroupMembers.Add(x); + + context.SaveChanges(); + + } + + + // Confirmar los cambios. + transaction.Commit(); + + return new() + { + Response = Responses.Success, + Model = modelo + }; + + } + catch (Exception) + { + transaction.Rollback(); + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener una cuenta según el Id. + /// + /// Id de la cuenta. + /// Filtros de búsqueda. + /// Contexto de base de datos. + public static async Task> Read(int id, QueryAccountFilter filters, DataContext context) + { + + try + { + + // Consulta de las cuentas. + var account = await Builders.Account.GetAccounts(id, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener una cuenta según el identificador único. + /// + /// Único. + /// Filtros de búsqueda. + /// Contexto de base de datos. + public static async Task> Read(string unique, QueryAccountFilter filters, DataContext context) + { + + try + { + + // Consulta de las cuentas. + var account = await Builders.Account.GetAccounts(unique, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener una cuenta según el id de la identidad. + /// + /// Id de la identidad. + /// Filtros de búsqueda. + /// Contexto de base de datos. + public static async Task> ReadByIdentity(int id, QueryAccountFilter filters, DataContext context) + { + + try + { + + // Consulta de las cuentas. + var account = await Builders.Account.GetAccountsByIdentity(id, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Buscar por patron. + /// + /// patron de búsqueda + /// Filtros + /// Contexto de conexión + public static async Task> Search(string pattern, QueryAccountFilter filters, DataContext context) + { + + // Ejecución + try + { + + List accountModels = await Builders.Account.Search(pattern, filters, context).Take(10).ToListAsync(); + + // Si no existe el modelo + if (accountModels == null) + return new(Responses.NotRows); + + return new(Responses.Success, accountModels); + } + catch + { + } + + return new(); + } + + + + /// + /// Obtiene los usuarios con IDs coincidentes + /// + /// Lista de IDs + /// Contexto de base de datos + public static async Task> FindAll(List ids, QueryAccountFilter filters, DataContext context) + { + + // Ejecución + try + { + + var query = Builders.Account.FindAll(ids, filters, context); + + // Ejecuta + var result = await query.ToListAsync(); + + // Si no existe el modelo + if (result == null) + return new(Responses.NotRows); + + return new(Responses.Success, result); + } + catch + { + } + + return new(); + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Builders/Account.cs b/LIN.Cloud.Identity/Data/Builders/Account.cs new file mode 100644 index 0000000..3fd9177 --- /dev/null +++ b/LIN.Cloud.Identity/Data/Builders/Account.cs @@ -0,0 +1,252 @@ +namespace LIN.Cloud.Identity.Data.Builders; + +public class Account +{ + + + /// + /// Buscar en la cuentas estables. + /// + /// Contexto de base de datos. + public static IQueryable OnStable(DataContext context) + { + + // Hora actual. + var now = DateTime.Now; + + // Consulta. + var query = from account in context.Accounts + where account.Identity.Status != IdentityStatus.Disable + && account.Identity.EffectiveTime < now && account.Identity.ExpirationTime > now + select account; + + // Retornar. + return query; + } + + + + /// + /// Buscar en todas las cuentas. + /// + /// Contexto de base de datos. + public static IQueryable OnAll(DataContext context) + { + + // Hora actual. + var now = DateTime.Now; + + // Consulta. + var query = from account in context.Accounts + select account; + + // Retornar. + return query; + } + + + + /// + /// Obtener cuentas. + /// + /// Id de la cuenta + /// Filtros + /// Contexto + public static IQueryable GetAccounts(int id, Services.Models.QueryAccountFilter filters, DataContext context) + { + + // Query general + IQueryable accounts; + + if (filters.FindOn == Services.Models.FindOn.StableAccounts) + accounts = from account in OnStable(context) + where account.Id == id + select account; + else + accounts = from account in OnAll(context) + where account.Id == id + select account; + + // Armar el modelo + accounts = BuildModel(accounts, filters, context); + + // Retorno + return accounts; + + } + + + + /// + /// Obtener cuentas. + /// + /// Identidad unica. + /// Filtros + /// Contexto + public static IQueryable GetAccounts(string user, Services.Models.QueryAccountFilter filters, DataContext context) + { + + // Query general + IQueryable accounts; + + if (filters.FindOn == Services.Models.FindOn.StableAccounts) + accounts = from account in OnStable(context) + where account.Identity.Unique == user + select account; + else + accounts = from account in OnAll(context) + where account.Identity.Unique == user + select account; + + // Armar el modelo + accounts = BuildModel(accounts, filters, context); + + // Retorno + return accounts; + + } + + + + /// + /// Obtener cuentas. + /// + /// Id de la Identidad. + /// Filtros + /// Contexto + public static IQueryable GetAccountsByIdentity(int id, Services.Models.QueryAccountFilter filters, DataContext context) + { + + // Query general + IQueryable accounts; + + if (filters.FindOn == Services.Models.FindOn.StableAccounts) + accounts = from account in OnStable(context) + where account.Identity.Id == id + select account; + else + accounts = from account in OnAll(context) + where account.Identity.Id == id + select account; + + // Armar el modelo + accounts = BuildModel(accounts, filters, context); + + // Retorno + return accounts; + + } + + + + + + public static IQueryable Search(string pattern, QueryAccountFilter filters, DataContext context) + { + + // Query general. + IQueryable accounts = from account in OnStable(context) + where account.Identity.Unique.Contains(pattern) + select account; + + // Armar el modelo. + accounts = BuildModel(accounts, filters, context); + + // Retorno + return accounts; + + } + + + + + public static IQueryable FindAll(IEnumerable ids, QueryAccountFilter filters, DataContext context) + { + IQueryable accounts; + + if (filters.FindOn == FindOn.StableAccounts) + { + accounts = from account in OnStable(context) + where ids.Contains(account.Id) + select account; + } + else + { + accounts = from account in OnAll(context) + where ids.Contains(account.Id) + select account; + } + + + // Armar el modelo. + accounts = BuildModel(accounts, filters, context); + + // Retorno + return accounts; + + } + + + + + + + + /// + /// Construir el modelo. + /// + /// Consulta base. + /// Filtros. + private static IQueryable BuildModel(IQueryable query, Services.Models.QueryAccountFilter filters, DataContext context) + { + + byte[] profile = []; + + try + { + profile = File.ReadAllBytes("wwwroot/user.png"); + } + catch { } + + var queryFinal = from account in query + select new AccountModel + { + Id = account.Id, + Name = (filters.IsAdmin + || account.Visibility == Visibility.Visible + || filters.AccountContext == account.Id + || (context.GroupMembers.FirstOrDefault(t => t.Group.Members.Any(t => t.IdentityId == filters.IdentityContext)) != null) + ) + ? account.Name + : "Usuario privado", + Creation = (filters.IsAdmin + || account.Visibility == Visibility.Visible + || filters.AccountContext == account.Id) + || (context.GroupMembers.FirstOrDefault(t => t.Group.Members.Any(t => t.IdentityId == filters.IdentityContext)) != null) + ? account.Creation + : default, + Identity = new() + { + Id = account.Identity.Id, + Unique = account.Identity.Unique, + CreationTime = account.Identity.CreationTime, + EffectiveTime = account.Identity.EffectiveTime, + ExpirationTime = account.Identity.ExpirationTime, + Status = account.Identity.Status + }, + Password = account.Password, + Visibility = account.Visibility, + Profile = profile, + IdentityId = account.Identity.Id, + IdentityService = account.IdentityService + }; + + var s = queryFinal.ToQueryString(); + + return queryFinal; + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Builders/Identities.cs b/LIN.Cloud.Identity/Data/Builders/Identities.cs new file mode 100644 index 0000000..f3450fe --- /dev/null +++ b/LIN.Cloud.Identity/Data/Builders/Identities.cs @@ -0,0 +1,138 @@ +namespace LIN.Cloud.Identity.Data.Builders; + + +public class Identities +{ + + + /// + /// Buscar en la identidad estables. + /// + /// Contexto de base de datos. + public static IQueryable OnStable(DataContext context) + { + + // Hora actual. + var now = DateTime.Now; + + // Consulta. + var query = from identity in context.Identities + where identity.Status != IdentityStatus.Disable + && identity.Status == IdentityStatus.Enable + && identity.EffectiveTime > now && identity.ExpirationTime < now + select identity; + + // Retornar. + return query; + } + + + + /// + /// Buscar en todas las identidades. + /// + /// Contexto de base de datos. + public static IQueryable OnAll(DataContext context) + { + + // Hora actual. + var now = DateTime.Now; + + // Consulta. + var query = from identity in context.Identities + select identity; + + // Retornar. + return query; + } + + + + /// + /// Obtener identidades. + /// + /// Id + /// Filtros + /// Contexto + public static IQueryable GetIds(int id, Services.Models.QueryIdentityFilter filters, DataContext context) + { + + // Query general + IQueryable ids; + + if (filters.FindOn == Services.Models.FindOn.StableAccounts) + ids = from identity in OnStable(context) + where identity.Id == id + select identity; + else + ids = from account in OnAll(context) + where account.Id == id + select account; + + // Armar el modelo + ids = BuildModel(ids, filters); + + // Retorno + return ids; + + } + + + + /// + /// Obtener identidades. + /// + /// Unique + /// Filtros + /// Contexto + public static IQueryable GetIds(string unique, Services.Models.QueryIdentityFilter filters, DataContext context) + { + + // Query general + IQueryable ids; + + if (filters.FindOn == Services.Models.FindOn.StableAccounts) + ids = from identity in OnStable(context) + where identity.Unique == unique + select identity; + else + ids = from identity in OnAll(context) + where identity.Unique == unique + select identity; + + // Armar el modelo + ids = BuildModel(ids, filters); + + // Retorno + return ids; + + } + + + + /// + /// Construir el modelo. + /// + /// Consulta base. + /// Filtros. + private static IQueryable BuildModel(IQueryable query, Services.Models.QueryIdentityFilter filters) + { + + var final = from id in query + select new IdentityModel + { + Status = id.Status, + CreationTime = filters.IncludeDates ? id.CreationTime : default, + EffectiveTime = filters.IncludeDates ? id.EffectiveTime : default, + ExpirationTime = filters.IncludeDates ? id.ExpirationTime : default, + Id = id.Id, + Unique = id.Unique + }; + + return final; + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/DirectoryMembers.cs b/LIN.Cloud.Identity/Data/DirectoryMembers.cs new file mode 100644 index 0000000..a96b74d --- /dev/null +++ b/LIN.Cloud.Identity/Data/DirectoryMembers.cs @@ -0,0 +1,473 @@ +using LIN.Types.Cloud.Identity.Abstracts; + +namespace LIN.Cloud.Identity.Data; + + +public static class DirectoryMembers +{ + + + + #region Abstracciones + + + + /// + /// Obtener los directorios (Grupos de organización) donde una identidad pertenece. + /// + /// Id de la identidad + public static async Task> Read(int id, int organization) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Read(id, organization, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Valida si una identidad es miembro de una organización. + /// + /// Identidad + /// Id de la organización + public static async Task> IamIn(int id, int organization) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await IamIn(id, organization, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + + + + + public static async Task> IamIn(List id, int organization) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await IamIn(id, organization, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Valida si una identidad es miembro de una organización. + /// + /// Identidad + /// Id de la organización + public static async Task> IamInByDir(int id, int directory) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await IamInByDir(id, directory, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// + /// + /// Id del directorio + public static async Task> ReadMembers(int id) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await ReadMembers(id, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + public static async Task>> ReadMembersByOrg(int id) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await ReadMembersByOrg(id, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + + #endregion + + + + + + + /// + /// Obtener los directorios (Grupos de organización) donde una identidad pertenece. + /// + /// Identidad + /// Organización de contexto. + /// Contexto. + public static async Task> Read(int id, int organization, DataContext context) + { + + try + { + + + var members = await (from gm in context.GroupMembers + where gm.IdentityId == id + join o in context.Organizations + on gm.GroupId equals o.DirectoryId + where o.Id == organization + select new GroupMember + { + Type = gm.Type, + Group = gm.Group, + GroupId = gm.GroupId, + IdentityId = gm.IdentityId, + }).ToListAsync(); + + + // Si la cuenta no existe. + if (members == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = members + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + + + + + + + /// + /// Valida si una identidad es miembro de una organización. + /// + /// Identidad + /// Id de la organización + /// Contexto + public static async Task> IamIn(int id, int organization, DataContext context) + { + + try + { + + // Consulta. + var query = await (from org in context.Organizations + where org.Id == organization + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + where gm.IdentityId == id + select new + { + gm.Type + }).FirstOrDefaultAsync(); + + + // Si la cuenta no existe. + if (query == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = query.Type + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.NotRows + }; + } + + } + + + + + /// + /// Valida si una lista de identidades son miembro de una organización. + /// + /// Identidades + /// Id de la organización + /// Contexto + public static async Task> IamIn(List ids, int organization, DataContext context) + { + + try + { + + // Consulta. + var query = await (from org in context.Organizations + where org.Id == organization + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + where ids.Contains(gm.IdentityId) + select gm.IdentityId).ToListAsync(); + + + // Si la cuenta no existe. + if (query == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = query + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.NotRows + }; + } + + } + + + + /// + /// Valida si una identidad es miembro de una organización. + /// + /// Identidad + /// Id del directorio + /// Contexto + public static async Task> IamInByDir(int id, int directory, DataContext context) + { + + try + { + + // Consulta. + var query = await (from org in context.Organizations + where org.DirectoryId == directory + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + where gm.IdentityId == id + select new + { + gm.Type + }).FirstOrDefaultAsync(); + + + // Si la cuenta no existe. + if (query == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = query.Type + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.NotRows + }; + } + + } + + + + /// + /// + /// + /// Directorio + /// Contexto + public static async Task> ReadMembers(int id, DataContext context) + { + + try + { + + var members = await (from org in context.Organizations + where org.DirectoryId == id + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + select new GroupMember + { + GroupId = gm.GroupId, + Identity = gm.Identity, + Type = gm.Type, + IdentityId = gm.IdentityId + }).ToListAsync(); + + + + // Si la cuenta no existe. + if (members == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = members + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// + /// + /// Directorio + /// Contexto + public static async Task>> ReadMembersByOrg(int id, DataContext context) + { + + try + { + + var members = await (from org in context.Organizations + where org.Id == id + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + join a in context.Accounts + on gm.IdentityId equals a.IdentityId + select new SessionModel + { + Account = new() + { + Id = a.Id, + Name = a.Name, + Visibility = a.Visibility, + IdentityService = a.IdentityService, + Identity = new() + { + Id = a.Identity.Id, + Unique = a.Identity.Unique + } + }, + Profile = gm + }).ToListAsync(); + + + // Si la cuenta no existe. + if (members == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = members + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/GroupMembers.cs b/LIN.Cloud.Identity/Data/GroupMembers.cs new file mode 100644 index 0000000..cfd985f --- /dev/null +++ b/LIN.Cloud.Identity/Data/GroupMembers.cs @@ -0,0 +1,488 @@ +namespace LIN.Cloud.Identity.Data; + + +public static class GroupMembers +{ + + + + #region Abstracciones + + + + /// + /// Crear nuevo integrante en un grupo. + /// + /// Modelo. + public static async Task Create(GroupMember modelo) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnectionForce(); + + // Función. + var response = await Create(modelo, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Crear nuevos integrantes en un grupo. + /// + /// Modelos. + public static async Task Create(IEnumerable modelos) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Create(modelos, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener los miembros de un grupo. + /// + /// Id del grupo + public static async Task> ReadAll(int id) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await ReadAll(id, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Buscar en los integrantes de un grupo. + /// + /// Patron de búsqueda. + /// Id del grupo. + public static async Task> Search(string pattern, int group) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Search(pattern, group, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + + /// + /// Buscar en los integrantes de un grupo. + /// + /// Patron de búsqueda. + /// Id del grupo. + public static async Task> SearchGroups(string pattern, int group) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await SearchGroups(pattern, group, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Eliminar un integrante de un grupo. + /// + /// Identidad. + /// Id del grupo. + public static async Task Delete(int identity, int group) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Delete(identity, group, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + + /// + /// Obtener los grupos donde una identidad esta de integrante + /// + /// Id del grupo + public static async Task> OnMembers(int organization, int identity) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await OnMembers(organization, identity, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + #endregion + + + + /// + /// Crear nuevo integrante en un grupo. + /// + /// Modelo. + /// Contexto de conexión. + public static async Task Create(GroupMember modelo, DataContext context) + { + try + { + + // Ya existen. + context.Attach(modelo.Group); + context.Attach(modelo.Identity); + + // Guardar la identidad. + await context.GroupMembers.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + + } + + + + /// + /// Crear nuevos integrante en un grupo. + /// + /// Modelos. + /// Contexto de conexión. + public static async Task Create(IEnumerable modelos, DataContext context) + { + try + { + + // Validar existencia. + foreach (var member in modelos) + { + try + { + context.Database.ExecuteSqlRaw(""" + INSERT INTO [dbo].[GROUPS_MEMBERS] + ([IdentityId] + ,[GroupId] + ,[Type]) + VALUES + ({0} + ,{1} + ,{2}) + """, member.Identity.Id, member.Group.Id, (int)GroupMemberTypes.User); + } + + catch (Exception ex) + { + + } + + + } + + return new() + { + Response = Responses.Success + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + + } + + + + /// + /// Obtener los miembros de un grupo. + /// + /// Id del grupo. + /// Contexto de base de datos. + public static async Task> ReadAll(int id, DataContext context) + { + + try + { + + // Consulta. + var members = await (from gm in context.GroupMembers + where gm.GroupId == id + select new GroupMember + { + GroupId = gm.GroupId, + Identity = gm.Identity, + Type = gm.Type, + IdentityId = gm.IdentityId + }).ToListAsync(); + + + // Si la cuenta no existe. + if (members == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = members + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Buscar en los integrantes de un grupo. + /// + /// Patron de búsqueda. + /// Id del grupo. + /// Contexto de base de datos. + public static async Task> Search(string pattern, int group, DataContext context) + { + + try + { + + // Consulta. + var members = await (from g in context.GroupMembers + where g.GroupId == @group + && g.Identity.Unique.ToLower().Contains(pattern.ToLower()) + select g.Identity).ToListAsync(); + + + // Si la cuenta no existe. + if (members == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = members + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Buscar en los integrantes de un grupo. + /// + /// Patron de búsqueda. + /// Id del grupo. + /// Contexto de base de datos. + public static async Task> SearchGroups(string pattern, int group, DataContext context) + { + + try + { + + // Consulta. + var members = await (from g in context.GroupMembers + where g.GroupId == @group + && g.Identity.Unique.ToLower().Contains(pattern.ToLower()) + && g.Identity.Type == IdentityType.Group + select g.Identity).ToListAsync(); + + + // Si la cuenta no existe. + if (members == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = members + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Eliminar un integrante de un grupo. + /// + /// Identidad. + /// Id del grupo. + /// Contexto de base de datos. + public static async Task Delete(int identity, int group, DataContext context) + { + + try + { + + + var response = await (from g in context.GroupMembers + where g.GroupId == @group + && g.IdentityId == identity + select g).ExecuteDeleteAsync(); + + + // Success. + return new() + { + Response = Responses.Success + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + + } + + + + /// + /// Obtener los grupos donde una identidad esta de integrante + /// + /// + /// + /// + public static async Task> OnMembers(int organization, int identity, DataContext context) + { + + try + { + + // Consulta. + var groups = await (from g in context.GroupMembers + where g.Group.OwnerId == organization + && g.IdentityId == identity + select new GroupModel + { + Id = g.Group.Id, + Identity = g.Group.Identity, + Name = g.Group.Name, + OwnerId = g.Group.OwnerId + }).ToListAsync(); + + // Si la cuenta no existe. + if (groups == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = groups + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Groups.cs b/LIN.Cloud.Identity/Data/Groups.cs new file mode 100644 index 0000000..2d630aa --- /dev/null +++ b/LIN.Cloud.Identity/Data/Groups.cs @@ -0,0 +1,475 @@ +namespace LIN.Cloud.Identity.Data; + + +public static class Groups +{ + + + + #region Abstracciones + + + + /// + /// Crear un nuevo grupo. + /// + /// Modelo. + public static async Task> Create(GroupModel modelo) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Create(modelo, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener un grupo según el Id. + /// + /// Id. + public static async Task> Read(int id) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Read(id, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + + /// + /// Obtener un grupo según el Id de la identidad. + /// + /// Id. + public static async Task> ReadByIdentity(int id) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await ReadByIdentity(id, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener los grupos asociados a una organización. + /// + /// Id de la organización. + public static async Task> ReadAll(int organization) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await ReadAll(organization, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener la organización propietaria de un grupo. + /// + /// Id del grupo. + public static async Task> GetOwner(int group) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await GetOwner(group, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener la organización propietaria de un grupo. + /// + /// Id de la identidad del grupo. + public static async Task> GetOwnerByIdentity(int identity) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await GetOwnerByIdentity(identity, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + #endregion + + + + /// + /// Crear nuevo grupo. + /// + /// Modelo. + /// Contexto de conexión. + public static async Task> Create(GroupModel modelo, DataContext context) + { + // Pre. + modelo.Id = 0; + + // Transacción. + using var transaction = context.Database.BeginTransaction(); + + try + { + + // Miembros. + foreach (var e in modelo.Members) + { + e.Group = modelo; + context.Attach(e.Identity); + } + + // Fijar la organización. + modelo.Owner = new() + { + Id = modelo.OwnerId ?? 0 + }; + + // Si no existe. + if (modelo.Owner != null) + context.Attach(modelo.Owner); + + // Guardar la identidad. + await context.Groups.AddAsync(modelo); + + + // Obtener el directorio general. + var dir = (from org in context.Organizations + where org.Id == modelo.OwnerId + select new { org.DirectoryId, org.Directory.Identity.Unique }).FirstOrDefault(); + + // Si no se encontró el directorio. + if (dir == null) + { + transaction.Rollback(); + return new() + { + Response = Responses.NotRows + }; + } + + // Nueva identidad. + modelo.Identity.Unique = $"{modelo.Identity.Unique}@{dir.Unique}"; + + // Guardar. + context.SaveChanges(); + + + var dirModel = new GroupModel + { + Id = dir.DirectoryId + }; + + context.Attach(dirModel); + + context.GroupMembers.Add(new() + { + Group = dirModel, + Identity = modelo.Identity, + Type = GroupMemberTypes.Group + }); + + + context.SaveChanges(); + + transaction.Commit(); + + return new() + { + Response = Responses.Success, + Model = modelo + }; + + } + catch (Exception) + { + transaction.Rollback(); + return new() + { + Response = Responses.Undefined + }; + } + + } + + + + /// + /// Obtener un grupo según el Id. + /// + /// Id. + /// Contexto de base de datos. + public static async Task> Read(int id, DataContext context) + { + + try + { + // Consulta. + var group = await (from g in context.Groups + where g.Id == id + select new GroupModel + { + Id = g.Id, + Identity = g.Identity, + Name = g.Name, + IdentityId = g.IdentityId, + Description = g.Description + }).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (group == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = group + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener un grupo según el Id de la identidad. + /// + /// Identidad. + /// Contexto de base de datos. + public static async Task> ReadByIdentity(int id, DataContext context) + { + + try + { + // Consulta. + var group = await (from g in context.Groups + where g.IdentityId == id + select new GroupModel + { + Id = g.Id, + Identity = g.Identity, + Name = g.Name, + IdentityId = g.IdentityId, + Description = g.Description + }).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (group == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = group + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener los grupos asociados a una organización. + /// + /// Organización. + /// Contexto de base de datos. + public static async Task> ReadAll(int organization, DataContext context) + { + + try + { + + // Consulta. + var groups = await (from g in context.Groups + where g.OwnerId == organization + select new GroupModel + { + Id = g.Id, + Identity = g.Identity, + Name = g.Name, + OwnerId = g.OwnerId + }).ToListAsync(); + + // Si la cuenta no existe. + if (groups == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = groups + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener la organización propietaria de un grupo. + /// + /// Id del grupo. + /// Contexto de base de datos. + public static async Task> GetOwner(int id, DataContext context) + { + + try + { + + // Consulta. + var ownerId = await (from g in context.Groups + where g.Id == id + select g.OwnerId).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (ownerId == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = ownerId == null ? Responses.NotRows : Responses.Success, + Model = ownerId ?? 0 + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener la organización propietaria de un grupo. + /// + /// Id de la identidad. + /// Contexto de base de datos. + public static async Task> GetOwnerByIdentity(int id, DataContext context) + { + + try + { + + // Consulta. + var ownerId = await (from g in context.Groups + where g.IdentityId == id + select g.OwnerId).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (ownerId == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = ownerId == null ? Responses.NotRows : Responses.Success, + Model = ownerId ?? 0 + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Identities.cs b/LIN.Cloud.Identity/Data/Identities.cs new file mode 100644 index 0000000..7107668 --- /dev/null +++ b/LIN.Cloud.Identity/Data/Identities.cs @@ -0,0 +1,205 @@ +namespace LIN.Cloud.Identity.Data; + + +public static class Identities +{ + + + + #region Abstracciones + + + + /// + /// Crear nueva identidad. + /// + /// Modelo. + public static async Task> Create(IdentityModel modelo) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Create(modelo, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener una identidad según el Id. + /// + /// Id. + /// Filtros de búsqueda. + public static async Task> Read(int id, Services.Models.QueryIdentityFilter filters) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Read(id, filters, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener una identidad según el Unique. + /// + /// Unique. + /// Filtros de búsqueda. + public static async Task> Read(string unique, Services.Models.QueryIdentityFilter filters) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Read(unique, filters, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + #endregion + + + + /// + /// Crear nueva identidad. + /// + /// Modelo. + /// Contexto de conexión. + public static async Task> Create(IdentityModel modelo, DataContext context) + { + // Pre. + modelo.Id = 0; + + try + { + + foreach (var e in modelo.Roles) + e.Identity = modelo; + + // Guardar la identidad. + await context.Identities.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + Model = modelo + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener una identidad según el Id. + /// + /// Id de la identidad. + /// Filtros de búsqueda. + /// Contexto de base de datos. + public static async Task> Read(int id, QueryIdentityFilter filters, DataContext context) + { + + try + { + + // Consulta de las cuentas. + var account = await Builders.Identities.GetIds(id, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener una identidad según el Unique. + /// + /// Unique. + /// Filtros de búsqueda. + /// Contexto de base de datos. + public static async Task> Read(string unique, QueryIdentityFilter filters, DataContext context) + { + + try + { + + // Consulta de las cuentas. + var account = await Builders.Identities.GetIds(unique, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/IdentityRoles.cs b/LIN.Cloud.Identity/Data/IdentityRoles.cs new file mode 100644 index 0000000..3db39c1 --- /dev/null +++ b/LIN.Cloud.Identity/Data/IdentityRoles.cs @@ -0,0 +1,264 @@ +namespace LIN.Cloud.Identity.Data; + + +public static class IdentityRoles +{ + + + + #region Abstracciones + + + + /// + /// Crear nuevo rol. + /// + /// Modelo. + public static async Task Create(IdentityRolesModel modelo) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Create(modelo, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener los roles asociados a una identidad en una organización determinada. + /// + /// Identidad. + /// Organización. + public static async Task> ReadAll(int identity, int organization) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await ReadAll(identity, organization, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Eliminar el rol de una identidad en una organización. + /// + /// Identidad. + /// Rol. + /// Organización. + public static async Task Remove(int identity, Roles rol, int organization) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Remove(identity, rol, organization, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + #endregion + + + + /// + /// Crear nuevo rol en identidad. + /// + /// Modelo. + /// Contexto de conexión. + public static async Task Create(IdentityRolesModel modelo, DataContext context) + { + + try + { + + // Attach. + context.Attach(modelo.Identity); + context.Attach(modelo.Organization); + + + // Guardar la identidad. + await context.IdentityRoles.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener los roles asociados a una identidad en una organización determinada. + /// + /// Identidad. + /// Organización. + /// Contexto de base de datos. + public static async Task> ReadAll(int identity, int organization, DataContext context) + { + + try + { + + List Roles = []; + + await RolesOn(identity, organization, context, [], Roles); + + return new() + { + Models = Roles, + Response = Responses.Success + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + + /// + /// Eliminar el rol de una identidad en una organización. + /// + /// Identidad. + /// Rol. + /// Organización. + /// Contexto de base de datos. + public static async Task Remove(int identity, Roles rol, int organization, DataContext context) + { + + try + { + + // Ejecutar eliminación. + var count = await (from ir in context.IdentityRoles + where ir.IdentityId == identity + && ir.Rol == rol + && ir.OrganizationId == organization + select ir).ExecuteDeleteAsync(); + + return new() + { + Response = Responses.Success + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + + + + + + + + + + + + private static async Task RolesOn(int identity, int organization, DataContext context, List ids, List roles) + { + + var query = from id in context.Identities + where id.Id == identity + select new + { + Id = new { id.Unique, id.ExpirationTime, id.CreationTime }, + In = (from member in context.GroupMembers + where !ids.Contains(member.Group.IdentityId) + && member.IdentityId == identity + select member.Group.IdentityId).ToList(), + + Roles = (from IR in context.IdentityRoles + where IR.IdentityId == identity + where IR.OrganizationId == organization + select IR.Rol).ToList() + }; + + + // Si hay elementos. + if (query.Any()) + { + + // Ejecuta la consulta. + var local = query.ToList(); + + // Obtiene los roles. + var localRoles = local.SelectMany(t => t.Roles); + + // Obtiene las bases. + var bases = local.SelectMany(t => t.In); + + // Agregar a los objetos. + roles.AddRange(localRoles.Select(t => new IdentityRolesModel + { + Identity = new() + { + Id = identity, + Unique = local[0].Id.Unique, + CreationTime = local[0].Id.CreationTime, + ExpirationTime = local[0].Id.ExpirationTime, + }, + Rol = t + })); + + ids.AddRange(bases); + + // Recorrer. + foreach (var @base in bases) + await RolesOn(@base, organization, context, ids, roles); + + } + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs new file mode 100644 index 0000000..5158922 --- /dev/null +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -0,0 +1,341 @@ +namespace LIN.Cloud.Identity.Data; + + +public static class Organizations +{ + + + + #region Abstracciones + + + + /// + /// Crear nueva organización. + /// + /// Modelo. + public static async Task Create(OrganizationModel modelo) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Create(modelo, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener una organización según el Id. + /// + /// Id de la identidad + public static async Task> Read(int id) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Read(id, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener las organizaciones donde un usuario es miembro. + /// + /// Id de la identidad + public static async Task> ReadAll(int id) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await ReadAll(id, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener el dominio de la organización. + /// + /// Id de la organización. + public static async Task> GetDomain(int id) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await GetDomain(id, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + #endregion + + + + /// + /// Crear nueva organización. [Transacción] + /// + /// Modelo. + /// Contexto de conexión. + public static async Task Create(OrganizationModel modelo, DataContext context) + { + + using var transaction = context.Database.BeginTransaction(); + + try + { + + // Metadata. + modelo.Directory.Name = "Directorio General"; + modelo.Directory.Description = "Directorio General de la organización"; + modelo.Directory.Owner = null; + modelo.Directory.OwnerId = null; + modelo.Directory.Identity.Type = IdentityType.Group; + + await context.Organizations.AddAsync(modelo); + + + context.SaveChanges(); + + + // Cuenta de usuario + var account = new AccountModel() + { + Id = 0, + Creation = DateTime.Now, + Visibility = Visibility.Hidden, + Name = "Admin", + Password = $"pwd@{DateTime.Now.Year}", + IdentityService = IdentityService.LIN, + Identity = new IdentityModel() + { + Status = IdentityStatus.Enable, + CreationTime = DateTime.Now, + EffectiveTime = DateTime.Now, + ExpirationTime = DateTime.Now.AddYears(10), + Unique = $"admin@{modelo.Directory.Identity.Unique}" + } + }; + + + var resultAccount = new AccountModel() + { + Password = account.Password, + Identity = new() + { + Unique = account.Identity.Unique, + } + }; + + account = Services.Formats.Account.Process(account); + + await context.Accounts.AddAsync(account); + + context.SaveChanges(); + + // RolesIam. + var rol = new IdentityRolesModel + { + Identity = account.Identity, + Organization = modelo, + Rol = Roles.Administrator + }; + + + await context.IdentityRoles.AddAsync(rol); + + context.SaveChanges(); + + modelo.Directory.Owner = modelo; + modelo.Directory.Members.Add(new() + { + Group = modelo.Directory, + Identity = account.Identity, + Type = GroupMemberTypes.User + }); + + context.SaveChanges(); + + + transaction.Commit(); + + + var responseFinal = new CreateResponse() + { + Response = Responses.Success, + LastID = modelo.Id + }; + + + return responseFinal; + + } + catch (Exception) + { + transaction.Rollback(); + return new() + { + Response = Responses.Undefined + }; + } + + } + + + + /// + /// Obtener una organización según el Id. + /// + /// Id. + /// Contexto de base de datos. + public static async Task> Read(int id, DataContext context) + { + + try + { + + var org = await (from g in context.Organizations + where g.Id == id + select g).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (org == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = org + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener las organizaciones donde una identidad pertenece. + /// + /// Identidad + /// Contexto + public static async Task> ReadAll(int id, DataContext context) + { + + try + { + + // Consulta. + var query = await (from org in context.Organizations + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + where gm.IdentityId == id + select org).ToListAsync(); + + + // Si la cuenta no existe. + if (query == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Models = query + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.NotRows + }; + } + + } + + + + /// + /// Obtener el dominio de una organización. + /// + /// Id de la organización. + /// Contexto de base de datos. + public static async Task> GetDomain(int id, DataContext context) + { + + try + { + + var org = await (from g in context.Organizations + where g.Id == id + select g.Directory.Identity).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (org == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = org + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj new file mode 100644 index 0000000..ca94db6 --- /dev/null +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + enable + false + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs new file mode 100644 index 0000000..d542722 --- /dev/null +++ b/LIN.Cloud.Identity/Program.cs @@ -0,0 +1,65 @@ +using LIN.Cloud.Identity.Services.Realtime; + +var builder = WebApplication.CreateBuilder(args); + +// Servicios de contenedor. +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddSignalR(); + +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowAnyOrigin", + builder => + { + builder.AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + }); +}); + +// Servicios personalizados +string sql = ""; + +#if DEBUG +sql = builder.Configuration["ConnectionStrings:local"] ?? string.Empty; +#elif RELEASE +sql = builder.Configuration["ConnectionStrings:cloud"] ?? string.Empty; +#endif + +builder.Services.AddDataBase(sql); + +// Servicios propios. +builder.Services.AddIP(); + +var app = builder.Build(); + +// Middlewares personalizados. +app.UseIP(); + + + +app.UseCors("AllowAnyOrigin"); + + + + + + + +// Swagger. +app.UseSwagger(); +app.UseSwaggerUI(); + +// Base de datos. +app.UseDataBase(); + +// Hub. +app.MapHub("/realTime/auth/passkey"); + +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run(); diff --git a/LIN.Identity/Properties/launchSettings.json b/LIN.Cloud.Identity/Properties/launchSettings.json similarity index 72% rename from LIN.Identity/Properties/launchSettings.json rename to LIN.Cloud.Identity/Properties/launchSettings.json index f570cb9..89a6453 100644 --- a/LIN.Identity/Properties/launchSettings.json +++ b/LIN.Cloud.Identity/Properties/launchSettings.json @@ -8,7 +8,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "http://localhost:5225" + "applicationUrl": "http://localhost:5166" }, "https": { "commandName": "Project", @@ -18,7 +18,7 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "dotnetRunMessages": true, - "applicationUrl": "https://localhost:7265;http://localhost:5225" + "applicationUrl": "https://localhost:7064;http://localhost:5166" }, "IIS Express": { "commandName": "IISExpress", @@ -31,21 +31,21 @@ "WSL": { "commandName": "WSL2", "launchBrowser": true, - "launchUrl": "https://localhost:7265/swagger", + "launchUrl": "https://localhost:7064/swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "https://localhost:7265;http://localhost:5225" + "ASPNETCORE_URLS": "https://localhost:7064;http://localhost:5166" }, "distributionName": "" } }, - "$schema": "https://json.schemastore.org/launchsettings.json", + "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:6638", - "sslPort": 44316 + "applicationUrl": "http://localhost:11415", + "sslPort": 44359 } } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs new file mode 100644 index 0000000..1e5b8be --- /dev/null +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -0,0 +1,123 @@ +namespace LIN.Cloud.Identity.Services.Auth; + + +public class Authentication +{ + + + /// + /// Usuario. + /// + private string User { get; set; } + + + + /// + /// Usuario. + /// + private string Password { get; set; } + + + + /// + /// Código de la aplicación. + /// + private string AppCode { get; set; } + + + + /// + /// Modelo obtenido. + /// + public AccountModel? Account { get; set; } = null; + + + + + /// + /// Generar nuevo servicio de autenticación. + /// + /// Usuario. + /// Contraseña + /// Código de aplicación + public Authentication(string user, string password, string appCode) + { + this.User = user; + this.Password = password; + this.AppCode = appCode; + } + + + + + + /// + /// Iniciar el proceso. + /// + public async Task Start() + { + + // Obtener la cuenta. + var account = await GetAccount(); + + // Error. + if (!account) + return Responses.NotExistAccount; + + // Validar contraseña. + bool password = ValidatePassword(); + + if(!password) + return Responses.InvalidPassword; + + + return Responses.Success; + } + + + + /// + /// Iniciar el proceso. + /// + private async Task GetAccount() + { + + // Obtener la cuenta. + var account = await Data.Accounts.Read(User, new() + { + FindOn = FindOn.StableAccounts, + IsAdmin = true + }); + + // Establecer. + Account = account.Model; + + // Respuesta. + return account.Response == Responses.Success; + + } + + + + /// + /// Validar la contraseña. + /// + private bool ValidatePassword() + { + + // Validar la cuenta. + if (Account == null) + return false; + + var ss = EncryptClass.Encrypt(Password); + // Validar la contraseña. + if (EncryptClass.Encrypt(Password) != Account.Password) + return false; + + // Correcto. + return true; + + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Services/Jwt.cs b/LIN.Cloud.Identity/Services/Auth/JwtService.cs similarity index 92% rename from LIN.Identity/Services/Jwt.cs rename to LIN.Cloud.Identity/Services/Auth/JwtService.cs index db8ee2b..97e88a6 100644 --- a/LIN.Identity/Services/Jwt.cs +++ b/LIN.Cloud.Identity/Services/Auth/JwtService.cs @@ -1,14 +1,16 @@ -using LIN.Identity.Models; +using Microsoft.Extensions.Configuration; +using LIN.Cloud.Identity.Services.Models; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; -namespace LIN.Identity.Services; +namespace LIN.Cloud.Identity.Services.Auth; -public class Jwt +public class JwtService { + /// /// Llave del token /// @@ -17,7 +19,7 @@ public class Jwt /// - /// Inicia el servicio Jwt + /// Inicia el servicio JwtService /// public static void Open() { @@ -34,8 +36,10 @@ public static void Open() internal static string Generate(AccountModel user, int appID) { - // Configuración + if (JwtKey == string.Empty) + Open(); + // Configuración var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtKey)); // Credenciales @@ -44,9 +48,8 @@ internal static string Generate(AccountModel user, int appID) // Reclamaciones var claims = new[] { - new Claim(ClaimTypes.PrimarySid, user.ID.ToString()), + new Claim(ClaimTypes.PrimarySid, user.Id.ToString()), new Claim(ClaimTypes.NameIdentifier, user.Identity.Unique), - new Claim(ClaimTypes.Role, ((int)user.Rol).ToString()), new Claim(ClaimTypes.GroupSid, (user.Identity.Id).ToString() ?? ""), new Claim(ClaimTypes.Authentication, appID.ToString()) }; @@ -72,7 +75,6 @@ internal static JwtModel Validate(string token) try { - // Comprobación if (string.IsNullOrWhiteSpace(token)) return new() @@ -138,6 +140,5 @@ internal static JwtModel Validate(string token) } - } \ No newline at end of file diff --git a/LIN.Identity/Services/Configuration.cs b/LIN.Cloud.Identity/Services/Configuration.cs similarity index 93% rename from LIN.Identity/Services/Configuration.cs rename to LIN.Cloud.Identity/Services/Configuration.cs index 8bac1fb..eb4aeab 100644 --- a/LIN.Identity/Services/Configuration.cs +++ b/LIN.Cloud.Identity/Services/Configuration.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Services; +namespace LIN.Cloud.Identity.Services; public class Configuration diff --git a/LIN.Cloud.Identity/Services/Database/DataContext.cs b/LIN.Cloud.Identity/Services/Database/DataContext.cs new file mode 100644 index 0000000..f300e95 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Database/DataContext.cs @@ -0,0 +1,179 @@ +namespace LIN.Cloud.Identity.Services.Database; + + +public class DataContext(DbContextOptions options) : DbContext(options) +{ + + + /// + /// Tabla de identidades. + /// + public DbSet Identities { get; set; } + + + /// + /// Tabla de cuentas. + /// + public DbSet Accounts { get; set; } + + + /// + /// Organizaciones. + /// + public DbSet Organizations { get; set; } + + + + /// + /// Grupos. + /// + public DbSet Groups { get; set; } + + + /// + /// Integrantes de un grupo. + /// + public DbSet GroupMembers { get; set; } + + + /// + /// RolesIam de grupos. + /// + public DbSet IdentityRoles { get; set; } + + + + + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + + optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + base.OnConfiguring(optionsBuilder); + + + } + + + + /// + /// Generación del modelo de base de datos. + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + + // Modelo: Identity. + { + modelBuilder.Entity() + .HasIndex(t => t.Unique) + .IsUnique(); + } + + // Modelo: Account. + { + modelBuilder.Entity() + .HasIndex(t => t.IdentityId) + .IsUnique(); + } + + + + // Modelo: Account. + { + + modelBuilder.Entity() + .HasOne(o => o.Directory) + .WithOne() + .HasForeignKey(o => o.DirectoryId) + .OnDelete(DeleteBehavior.NoAction); + + + modelBuilder.Entity() + .HasOne(g => g.Owner) + .WithMany(o => o.OwnedGroups) + .HasForeignKey(g => g.OwnerId) + .OnDelete(DeleteBehavior.NoAction); + + + } + + // Modelo: GroupModel. + { + + modelBuilder.Entity() + .HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t => t.IdentityId) + .OnDelete(DeleteBehavior.NoAction); + + + } + + + // Modelo: GroupMemberModel. + { + + + modelBuilder.Entity() + + .HasKey(t => new + { + t.IdentityId, + t.GroupId + }); + + modelBuilder.Entity() + .HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(y => y.IdentityId) + .OnDelete(DeleteBehavior.NoAction); + + modelBuilder.Entity() + .HasOne(t => t.Group) + .WithMany(t => t.Members) + .HasForeignKey(y => y.GroupId); + + + + } + + + // Modelo: IdentityRolesModel. + { + modelBuilder.Entity() + .HasOne(t => t.Identity) + .WithMany(t => t.Roles) + .HasForeignKey(y => y.IdentityId); + + modelBuilder.Entity() + .HasOne(t => t.Organization) + .WithMany() + .HasForeignKey(y => y.OrganizationId); + + modelBuilder.Entity() + .HasKey(t => new + { + t.Rol, + t.IdentityId, + t.OrganizationId + }); + + } + + + + + // Nombres de las tablas. + modelBuilder.Entity().ToTable("IDENTITIES"); + modelBuilder.Entity().ToTable("ACCOUNTS"); + modelBuilder.Entity().ToTable("GROUPS"); + modelBuilder.Entity().ToTable("IDENTITY_ROLES"); + modelBuilder.Entity().ToTable("GROUPS_MEMBERS"); + modelBuilder.Entity().ToTable("ORGANIZATIONS"); + + // Base. + base.OnModelCreating(modelBuilder); + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Database/DataService.cs b/LIN.Cloud.Identity/Services/Database/DataService.cs new file mode 100644 index 0000000..0205783 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Database/DataService.cs @@ -0,0 +1,126 @@ +namespace LIN.Cloud.Identity.Services.Database; + + +public static class DataService +{ + + + /// + /// Cache de conexiones. + /// + private static List Cache { get; set; } = []; + + + /// + /// Cantidad de instancias de base de datos. + /// + public static int MaxInstances { get; set; } = 10; + + + /// + /// Cadena de conexión SQL Server. + /// + public static string ConnectionString { get; private set; } = string.Empty; + + + + /// + /// Agregar base de datos al cache. + /// + /// Base de datos. + public static void AddToCache(DataBase db) + { + if (Cache.Count >= MaxInstances) + return; + Cache.Add(db); + } + + + + /// + /// Obtener una conexión a base de datos. + /// + public static (DataBase context, string contextKey) GetConnection() + { + + // Obtener base de datos del cache. + var cache = Cache.FirstOrDefault(db => !db.OnUseAction); + + // Validaciones. + if (cache != null && cache.Key == string.Empty) + { + // Bloquear el objecto. + lock (cache) + { + cache.SetOnUse(); + string key = KeyGen.Generate(10, "db."); + cache.Key = key; + return (cache, key); + } + } + + + // Generar estancia forzosa. + var newDB = new DataBase() + { + Key = KeyGen.Generate(10, "db.") + }; + + newDB.SetOnUse(); + AddToCache(newDB); + return (newDB, newDB.Key); + + } + + + + /// + /// Obtener una conexión a base de datos. + /// + public static (DataBase context, string contextKey) GetConnectionForce() + { + return (new(), ""); + + } + + + + /// + /// Habilitar el servicio de base de datos. + /// + public static IServiceCollection AddDataBase(this IServiceCollection context, string sql) + { + ConnectionString = sql; + context.AddDbContext(options => + { + options.UseSqlServer(sql); +#if DEBUG + options.EnableSensitiveDataLogging(); +#endif + }); + + return context; + } + + + + /// + /// Habilitar el servicio de base de datos. + /// + public static IApplicationBuilder UseDataBase(this IApplicationBuilder context) + { + try + { + var (connection, key) = GetConnection(); + bool created = connection.Database.EnsureCreated(); + connection.Close(key); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + return context; + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Database/Database.cs b/LIN.Cloud.Identity/Services/Database/Database.cs new file mode 100644 index 0000000..18444cf --- /dev/null +++ b/LIN.Cloud.Identity/Services/Database/Database.cs @@ -0,0 +1,99 @@ +using Microsoft.EntityFrameworkCore; + +namespace LIN.Cloud.Identity.Services.Database; + + +public sealed class DataBase : DataContext +{ + + + /// + /// Obtiene o establece si la DataBase esta en uso. + /// + private volatile bool OnUse = false; + + + + /// + /// Obtiene si la DataBase esta en uso y la pone en uso. + /// + public bool OnUseAction + { + get + { + lock (this) + { + if (!OnUse) + { + OnUse = true; + return false; + } + return true; + } + } + } + + + + /// + /// LLave para cerrar la conexión. + /// + public string Key = string.Empty; + + + + /// + /// Nueva DataBase. + /// + public DataBase() : base(new DbContextOptionsBuilder().UseSqlServer(DataService.ConnectionString).Options) + { + } + + + + /// + /// Destructor. + /// + ~DataBase() + { + Dispose(); + } + + + + + /// + /// Establece que la conexión esta en uso. + /// + public void SetOnUse() + { + lock (this) + { + OnUse = true; + } + } + + + + /// + /// Cerrar la conexión con la base de datos. + /// + /// Llave + public void Close(string key) + { + lock (this) + { + if (Key != key) + return; + + foreach (var entry in ChangeTracker.Entries()) + entry.State = EntityState.Detached; + + ChangeTracker.Clear(); + Key = string.Empty; + OnUse = false; + } + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Services/Filters/TokenAuthAttribute.cs b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs similarity index 80% rename from LIN.Identity/Services/Filters/TokenAuthAttribute.cs rename to LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs index 683c7eb..ac6943c 100644 --- a/LIN.Identity/Services/Filters/TokenAuthAttribute.cs +++ b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs @@ -1,8 +1,7 @@ -using Microsoft.AspNetCore.Mvc.Filters; +namespace LIN.Cloud.Identity.Services.Filters; -namespace LIN.Identity.Services.Filters; -public class TokenAuthAttribute : ActionFilterAttribute +public class IdentityTokenAttribute : ActionFilterAttribute { @@ -22,7 +21,7 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context bool can = httpContext.Request.Headers.TryGetValue("token", out Microsoft.Extensions.Primitives.StringValues value); // Información del token. - var tokenInfo = Jwt.Validate(value.ToString()); + var tokenInfo = JwtService.Validate(value.ToString()); // Error de autenticación. if (!can || !tokenInfo.IsAuthenticated) @@ -37,7 +36,7 @@ await httpContext.Response.WriteAsJsonAsync(new ResponseBase() } // Agrega la información del token. - context.HttpContext.Items.Add("token", tokenInfo); + context.HttpContext.Items.Add(value.ToString(), tokenInfo); await base.OnActionExecutionAsync(context, next); } diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs new file mode 100644 index 0000000..90d85b3 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -0,0 +1,80 @@ +using System.Text.RegularExpressions; + +namespace LIN.Cloud.Identity.Services.Formats; + + +public class Account +{ + + + + /// + /// Procesar el modelo. + /// + /// Modelo + public static (bool pass, string message) Validate(AccountModel baseAccount) + { + + + if (baseAccount.Name == null || baseAccount.Name.Trim().Length <= 0) + return (false, "La cuenta debe de tener un nombre valido."); + + + if (baseAccount.Identity == null || baseAccount.Identity.Unique == null|| baseAccount.Identity.Unique.Trim().Length <= 0) + return (false, "La cuenta debe de tener una identidad unica valida."); + + + + if (!ValidarCadena(baseAccount.Identity.Unique)) + return (false, "La identidad de la cuenta no puede contener simbolos NO alfanumericos."); + + + return (true, ""); + } + + + + static bool ValidarCadena(string cadena) + { + // Patrón de expresión regular para permitir solo letras o números + string patron = "^[a-zA-Z0-9]*$"; + + // Comprobar la coincidencia con el patrón + return Regex.IsMatch(cadena, patron); + } + + + + /// + /// Procesar el modelo. + /// + /// Modelo + public static AccountModel Process(AccountModel baseAccount) + { + return new AccountModel() + { + Id = 0, + IdentityService = IdentityService.LIN, + Creation = DateTime.Now, + Name = baseAccount.Name.Trim(), + Profile = baseAccount.Profile, + Password = EncryptClass.Encrypt(baseAccount.Password), + Visibility = baseAccount.Visibility, + IdentityId = 0, + Identity = new() + { + Id = 0, + Status = IdentityStatus.Enable, + Type = IdentityType.Account, + CreationTime = DateTime.Now, + EffectiveTime = baseAccount.Identity.EffectiveTime == default ? DateTime.Now : baseAccount.Identity.EffectiveTime, + ExpirationTime = baseAccount.Identity.ExpirationTime == default ? DateTime.Now.AddYears(5) : baseAccount.Identity.ExpirationTime, + Roles = [], + Unique = baseAccount.Identity.Unique.Trim() + } + }; + + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Formats/Identities.cs b/LIN.Cloud.Identity/Services/Formats/Identities.cs new file mode 100644 index 0000000..60fcf00 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Formats/Identities.cs @@ -0,0 +1,25 @@ +namespace LIN.Cloud.Identity.Services.Formats; + + +public class Identities +{ + + + /// + /// Procesar el modelo. + /// + /// Modelo + public static void Process(IdentityModel id) + { + + id.Id = 0; + id.ExpirationTime = DateTime.Now.AddYears(10); + id.EffectiveTime = DateTime.Now; + id.CreationTime = DateTime.Now; + id.Status = IdentityStatus.Enable; + id.Unique = id.Unique.Trim(); + + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs new file mode 100644 index 0000000..af611ae --- /dev/null +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -0,0 +1,161 @@ +namespace LIN.Cloud.Identity.Services.Iam; + + +public class RolesIam +{ + + + + + public static async Task<(List identities, List roles)> RolesOnByDir(int identity, int directory) + { + + var (context, contextKey) = DataService.GetConnection(); + + var query = await (from org in context.Organizations + where org.DirectoryId == directory + select org.Id).FirstOrDefaultAsync(); + + + if (query <= 0) + return ([], []); + + + + List identities = [identity]; + List roles = []; + + await RolesOn(identity, query, context, identities, roles); + + context.Close(contextKey); + return (identities, roles); + } + + + + + public static async Task<(List identities, List roles)> RolesOn(int identity, int organization) + { + List identities = [identity]; + List roles = []; + + var (context, contextKey) = DataService.GetConnection(); + + await RolesOn(identity, organization, context, identities, roles); + + context.Close(contextKey); + return (identities, roles); + } + + + private static async Task RolesOn(int identity, int organization, DataContext context, List ids, List roles) + { + + + var query = from id in context.Identities + where id.Id == identity + select new + { + In = (from member in context.GroupMembers + where !ids.Contains(member.Group.IdentityId) + && member.IdentityId == identity + select member.Group.IdentityId).ToList(), + + Roles = (from IR in context.IdentityRoles + where IR.IdentityId == identity + && IR.OrganizationId == organization + select IR.Rol).ToList() + }; + + + // Si hay elementos. + if (query.Any()) + { + + // Ejecuta la consulta. + var local = query.ToList(); + + // Obtiene los roles. + var localRoles = local.SelectMany(t => t.Roles); + + // Obtiene las bases. + var bases = local.SelectMany(t => t.In); + + // Agregar a los objetos. + roles.AddRange(localRoles); + ids.AddRange(bases); + + + + // Recorrer. + foreach (var @base in bases) + await RolesOn(@base, organization, context, ids, roles); + + } + + } + + + + + + + + + +} + + + + +public static class ValidateRoles +{ + + + + + + + public static bool ValidateRead(IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager, + Roles.AccountOperator, + Roles.Regular, + Roles.Viewer, + Roles.SecurityViewer + ]; + + + var sets = availed.Intersect(roles); + + return sets.Any(); + + } + + + public static bool ValidateAlterMembers(IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager, + Roles.AccountOperator + ]; + + + var sets = availed.Intersect(roles); + + return sets.Any(); + + } + + + + + + + +} \ No newline at end of file diff --git a/LIN.Identity/Services/Filters/IPMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs similarity index 66% rename from LIN.Identity/Services/Filters/IPMiddleware.cs rename to LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs index 785b009..a5bb829 100644 --- a/LIN.Identity/Services/Filters/IPMiddleware.cs +++ b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs @@ -1,12 +1,14 @@ -namespace LIN.Identity.Services.Filters; +namespace LIN.Cloud.Identity.Services.Middlewares; public class IPMiddleware : IMiddleware { /// - /// Middleware de IP. + /// Invocación del Middleware. /// + /// Contexto HTTP. + /// Pipeline public async Task InvokeAsync(HttpContext context, RequestDelegate next) { // Obtener la IP. @@ -24,19 +26,11 @@ await context.Response.WriteAsJsonAsync(new ResponseBase return; } + // Item de IP. context.Items.Add("IP", ip); await next(context); } -} - - -public static class IPMiddlewareExtensions -{ - - public static IApplicationBuilder UseIP(this IApplicationBuilder builder) => builder.UseMiddleware(); - - public static IServiceCollection AddIP(this IServiceCollection builder) => builder.AddSingleton(); } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Middlewares/MiddlewareExtensions.cs b/LIN.Cloud.Identity/Services/Middlewares/MiddlewareExtensions.cs new file mode 100644 index 0000000..5a896d0 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Middlewares/MiddlewareExtensions.cs @@ -0,0 +1,38 @@ +namespace LIN.Cloud.Identity.Services.Middlewares; + + +public static class MiddlewareExtensions +{ + + /// + /// Agregar servicio. + /// + public static IServiceCollection AddIP(this IServiceCollection builder) => builder.AddSingleton(); + + + /// + /// Activar el servicio. + /// + public static IApplicationBuilder UseIP(this IApplicationBuilder builder) => builder.UseMiddleware(); + + + /// + /// Agregar servicio. + /// + public static IServiceCollection AddQuota(this IServiceCollection builder) => builder.AddSingleton(); + + + /// + /// Activar el servicio. + /// + public static IApplicationBuilder UseQuota(this IApplicationBuilder builder, int max) + { + if (max <= 0) + max = 1; + + QuotaMiddleware.MaxQuote = max; + return builder.UseMiddleware(); + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs new file mode 100644 index 0000000..d666cc8 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs @@ -0,0 +1,54 @@ +namespace LIN.Cloud.Identity.Services.Middlewares; + + +public class QuotaMiddleware : IMiddleware +{ + + + /// + /// Solicites máximas permitidas al momento. + /// + public static int MaxQuote { get; set; } + + + /// + /// Solicitudes que se están procesando actualmente. + /// + private volatile static int ActualQuote = 0; + + + + + /// + /// Invocación del Middleware. + /// + /// Contexto HTTP. + /// Pipeline + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + + //// Comprobar el limite de cuota. + //if (ActualQuote >= MaxQuote) + //{ + // context.Response.StatusCode = 503; + // await context.Response.WriteAsJsonAsync(new ResponseBase + // { + // Message = $"Actualmente estamos al limite de solicitudes.", + // Response = Responses.UnavailableService + // }); + // return; + //} + + //// Incrementar. + //ActualQuote++; + + // Ejecución del flujo (Pipeline). + await next(context); + + // Decrementar. + ActualQuote--; + + } + + +} \ No newline at end of file diff --git a/LIN.Identity/Models/JwtModel.cs b/LIN.Cloud.Identity/Services/Models/JwtModel.cs similarity index 93% rename from LIN.Identity/Models/JwtModel.cs rename to LIN.Cloud.Identity/Services/Models/JwtModel.cs index cdc28f3..8104c6f 100644 --- a/LIN.Identity/Models/JwtModel.cs +++ b/LIN.Cloud.Identity/Services/Models/JwtModel.cs @@ -1,4 +1,4 @@ -namespace LIN.Identity.Models; +namespace LIN.Cloud.Identity.Services.Models; public class JwtModel diff --git a/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs b/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs new file mode 100644 index 0000000..74a7a8e --- /dev/null +++ b/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs @@ -0,0 +1,19 @@ +namespace LIN.Cloud.Identity.Services.Models; + + +public class QueryAccountFilter +{ + public int AccountContext { get; set; } + public int IdentityContext { get; set; } + public List OrganizationsDirectories { get; set; } = []; + public bool IsAdmin { get; set; } + public FindOn FindOn { get; set; } + +} + + +public enum FindOn +{ + StableAccounts, + AllAccounts +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs b/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs new file mode 100644 index 0000000..cb28aa3 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs @@ -0,0 +1,16 @@ +namespace LIN.Cloud.Identity.Services.Models; + + +public class QueryIdentityFilter +{ + public FindOn FindOn { get; set; } + public bool IncludeDates { get; set; } + +} + + +public enum FindOnIdentities +{ + Stable, + All +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs new file mode 100644 index 0000000..028e1e4 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -0,0 +1,236 @@ +namespace LIN.Cloud.Identity.Services.Realtime; + + +public partial class PassKeyHub : Hub +{ + + + /// + /// Lista de intentos Passkey. + /// String: Usuario. + /// PasskeyModels: Lista de intentos. + /// + public readonly static Dictionary> Attempts = []; + + + + /// + /// Canal de intentos. + /// + public static readonly string AttemptsChannel = "#attempts"; + + + /// + /// Canal de respuestas. + /// + public static readonly string ResponseChannel = "#responses"; + + + + + + public override Task OnDisconnectedAsync(Exception? exception) + { + + var e = Attempts.Values.Where(T => T.Where(T => T.HubKey == Context.ConnectionId).Any()).FirstOrDefault() ?? new(); + + + _ = e.Where(T => + { + if (T.HubKey == Context.ConnectionId && T.Status == PassKeyStatus.Undefined) + T.Status = PassKeyStatus.Failed; + + return false; + }); + + + return base.OnDisconnectedAsync(exception); + } + + + + + + + + + + + //=========== Dispositivos ===========// + + + /// + /// Envía la solicitud a los admins. + /// + public async Task SendRequest(PassKeyModel modelo) + { + + var pass = new PassKeyModel() + { + Expiración = modelo.Expiración, + Hora = modelo.Hora, + Status = modelo.Status, + User = modelo.User, + HubKey = modelo.HubKey + }; + + await Clients.Group(BuildGroupName(modelo.User)).SendAsync(AttemptsChannel, pass); + } + + + + + /// + /// Recibe una respuesta de passkey + /// + public async Task ReceiveRequest(PassKeyModel modelo) + { + try + { + + // Validación del token recibido + var info = JwtService.Validate(modelo.Token); + + // No es valido el token + if (!info.IsAuthenticated || modelo.Status != PassKeyStatus.Success) + { + // Modelo de falla + PassKeyModel badPass = new() + { + Status = modelo.Status, + User = modelo.User + }; + + // comunica la respuesta + await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync(ResponseChannel, badPass); + return; + } + + // Obtiene el attempt + List attempts = Attempts[modelo.User.ToLower()].Where(A => A.HubKey == modelo.HubKey).ToList(); + + // Elemento + var attempt = attempts.Where(A => A.HubKey == modelo.HubKey).FirstOrDefault(); + + // Validación del intento + if (attempt == null) + return; + + // Eliminar el attempt de la lista + attempts.Remove(attempt); + + // Cambiar el estado del intento + attempt.Status = modelo.Status; + + // Si el tiempo de expiración ya paso + if (DateTime.Now > modelo.Expiración) + { + attempt.Status = PassKeyStatus.Expired; + attempt.Token = string.Empty; + } + + //// Validación de la organización + //if (orgID > 0) + //{ + // // Obtiene la organización + // var organization = await Data.Organizations.Organizations.Read(orgID); + + // // Si tiene lista blanca + // //if (organization.Model.HaveWhiteList) + // //{ + // // //// Validación de la app + // // //var applicationOnOrg = await Data.Organizations.Applications.AppOnOrg(attempt.Application.Key, orgID); + + // // //// Si la app no existe o no esta activa + // // //if (applicationOnOrg.Response != Responses.Success) + // // //{ + // // // // Modelo de falla + // // // PassKeyModel badPass = new() + // // // { + // // // Status = PassKeyStatus.BlockedByOrg, + // // // User = modelo.User + // // // }; + + // // // // comunica la respuesta + // // // await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", badPass); + // // // return; + + // // //} + // //} + + + //} + + //// Aplicación + //var app = await Data.Applications.Read(attempt.Application.Key); + + //// Si la app no existe + //if (app.Response != Responses.Success) + //{ + // // Modelo de falla + // PassKeyModel badPass = new() + // { + // Status = PassKeyStatus.Failed, + // User = modelo.User + // }; + + // // comunica la respuesta + // await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", badPass); + // return; + //} + + //// Guarda el acceso. + //LoginLogModel loginLog = new() + //{ + // AccountID = userID, + // Application = new() + // { + // ID = app.Model.ID + // }, + // Date = DateTime.Now, + // Type = LoginTypes.Passkey, + // ID = 0 + //}; + + //_ = Data.Logins.Create(loginLog); + + + //// Nuevo token + var newToken = JwtService.Generate(new AccountModel() + { + Id = info.AccountId, + Identity = new() + { + Id = info.IdentityId, + Unique = info.Unique + }, + IdentityId = info.IdentityId + }, 0); + + // nuevo pass + var pass = new PassKeyModel() + { + Expiración = modelo.Expiración, + Status = modelo.Status, + User = modelo.User, + Token = newToken, + Hora = modelo.Hora, + HubKey = "", + Key = "" + }; + + // Respuesta al cliente + await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync(ResponseChannel, pass); + + } + catch + { + } + + } + + + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs new file mode 100644 index 0000000..47bc464 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -0,0 +1,76 @@ +namespace LIN.Cloud.Identity.Services.Realtime; + + +public partial class PassKeyHub +{ + + + /// + /// Agregar un dispositivo administrador. + /// + /// Token de acceso. + public async Task JoinAdmin(string token) + { + + // Obtener información del token. + var tokenInformation = JwtService.Validate(token); + + // Validar. + if (!tokenInformation.IsAuthenticated) + return; + + // Grupo de la cuenta. + await Groups.AddToGroupAsync(Context.ConnectionId, BuildGroupName(tokenInformation.Unique)); + + } + + + + + /// + /// Nuevo intento de inicio. + /// + /// Modelo. + public async Task JoinIntent(PassKeyModel attempt) + { + + //// Aplicación + //var application = await Data.Applications.Read(attempt.Application.Key); + + //// Si la app no existe o no esta activa + //if (application.Response != Responses.Success) + // return; + + //// Preparar el modelo + //attempt.Application ??= new(); + //attempt.Application.Name = application.Model.Name; + //attempt.Application.Badge = application.Model.Badge; + //attempt.Application.Key = application.Model.Key; + //attempt.Application.ID = application.Model.ID; + + // Vencimiento + var expiración = DateTime.Now.AddMinutes(2); + + // Caducidad el modelo + attempt.HubKey = Context.ConnectionId; + attempt.Status = PassKeyStatus.Undefined; + attempt.Hora = DateTime.Now; + attempt.Expiración = expiración; + + // Agrega el modelo + if (!Attempts.ContainsKey(attempt.User.ToLower())) + Attempts.Add(attempt.User.ToLower(), [attempt]); + + else + Attempts[attempt.User.ToLower()].Add(attempt); + + // Yo + await Groups.AddToGroupAsync(Context.ConnectionId, $"dbo.{Context.ConnectionId}"); + + await SendRequest(attempt); + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubFunctions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubFunctions.cs new file mode 100644 index 0000000..45c1b60 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubFunctions.cs @@ -0,0 +1,15 @@ +namespace LIN.Cloud.Identity.Services.Realtime; + + +public partial class PassKeyHub +{ + + + /// + /// Construir el nombre de un grupo. + /// + /// Usuario. + public string BuildGroupName(string user) => $"gr.{user.ToLower().Trim()}"; + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs new file mode 100644 index 0000000..2491e13 --- /dev/null +++ b/LIN.Cloud.Identity/Usings.cs @@ -0,0 +1,42 @@ +// Sistema. +global using LIN.Cloud.Identity; +global using LIN.Cloud.Identity.Services; +global using LIN.Cloud.Identity.Services.Auth; +global using LIN.Cloud.Identity.Services.Models; +global using Microsoft.AspNetCore.SignalR; +global using LIN.Cloud.Identity.Services.Iam; + + + + +// Framework. +global using System.Text; +global using System.IO; +global using System.Globalization; +global using Microsoft.AspNetCore.Mvc.Filters; +global using Microsoft.AspNetCore.Mvc; + +// SQL. +global using Microsoft.EntityFrameworkCore; + +// Tipos locales. +global using LIN.Cloud.Identity.Services.Database; +global using LIN.Cloud.Identity.Services.Middlewares; +global using LIN.Types.Cloud.Identity.Models; +global using LIN.Types.Cloud.Identity.Enumerations; +global using LIN.Cloud.Identity.Services.Filters; + +// Tipos Extras. +global using Microsoft.IdentityModel.Tokens; +global using System.IdentityModel.Tokens.Jwt; +global using System.Security.Claims; + +// Tipos Generales +global using LIN.Types.Responses; +global using LIN.Types.Enumerations; +global using Http.ResponsesList; + +// Módulos. +global using LIN.Modules; + + diff --git a/LIN.Identity/appsettings.Development.json b/LIN.Cloud.Identity/appsettings.Development.json similarity index 100% rename from LIN.Identity/appsettings.Development.json rename to LIN.Cloud.Identity/appsettings.Development.json diff --git a/LIN.Cloud.Identity/appsettings.json b/LIN.Cloud.Identity/appsettings.json new file mode 100644 index 0000000..f8c1a57 --- /dev/null +++ b/LIN.Cloud.Identity/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "jwt": { + "key": "drfghjiwuytr567uijnbvgfder56y7uiolkjhgfdertyui2345fgh" + }, + "ConnectionStrings": { + "local": "Data Source=(local);Initial Catalog=Identity;Integrated Security=True;TrustServerCertificate=True", + "cloud": "workstation id=linidentitydb.mssql.somee.com;packet size=4096;user id=linauth_SQLLogin_2;pwd=p4b691lkwx;data source=linidentitydb.mssql.somee.com;persist security info=False;initial catalog=linidentitydb;TrustServerCertificate=True; Encrypt=false;" + } +} \ No newline at end of file diff --git a/LIN.Identity.sln b/LIN.Identity.sln deleted file mode 100644 index 7caf9f5..0000000 --- a/LIN.Identity.sln +++ /dev/null @@ -1,81 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.34003.232 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Identity", "LIN.Identity\LIN.Identity.csproj", "{56E0DD97-DE1B-4F71-BC58-C43BC17CC0A1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types", "..\..\Tipos\LIN.Types\LIN.Types.csproj", "{5154207E-1CF9-4065-A4DE-456D8E369524}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Modules", "..\..\Tipos\LIN.Modules\LIN.Modules.csproj", "{7A5043D3-B280-49D6-8C69-9E02594141E5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http", "..\..\Tipos\Http\Http.csproj", "{13CF4A38-8857-442E-A496-76982F0A1D48}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Access.Logger", "..\..\AccesoAPI\LIN.Access.Logger\LIN.Access.Logger.csproj", "{6CB65EA1-51C6-4450-B174-F0A04C489FB5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types.Identity", "..\..\Tipos\LIN.Types.Identity\LIN.Types.Identity\LIN.Types.Identity.csproj", "{F88DF354-579F-4079-8A74-15F5CC4BEDBA}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {56E0DD97-DE1B-4F71-BC58-C43BC17CC0A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56E0DD97-DE1B-4F71-BC58-C43BC17CC0A1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56E0DD97-DE1B-4F71-BC58-C43BC17CC0A1}.Debug|x86.ActiveCfg = Debug|Any CPU - {56E0DD97-DE1B-4F71-BC58-C43BC17CC0A1}.Debug|x86.Build.0 = Debug|Any CPU - {56E0DD97-DE1B-4F71-BC58-C43BC17CC0A1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56E0DD97-DE1B-4F71-BC58-C43BC17CC0A1}.Release|Any CPU.Build.0 = Release|Any CPU - {56E0DD97-DE1B-4F71-BC58-C43BC17CC0A1}.Release|x86.ActiveCfg = Release|Any CPU - {56E0DD97-DE1B-4F71-BC58-C43BC17CC0A1}.Release|x86.Build.0 = Release|Any CPU - {5154207E-1CF9-4065-A4DE-456D8E369524}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5154207E-1CF9-4065-A4DE-456D8E369524}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5154207E-1CF9-4065-A4DE-456D8E369524}.Debug|x86.ActiveCfg = Debug|Any CPU - {5154207E-1CF9-4065-A4DE-456D8E369524}.Debug|x86.Build.0 = Debug|Any CPU - {5154207E-1CF9-4065-A4DE-456D8E369524}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5154207E-1CF9-4065-A4DE-456D8E369524}.Release|Any CPU.Build.0 = Release|Any CPU - {5154207E-1CF9-4065-A4DE-456D8E369524}.Release|x86.ActiveCfg = Release|Any CPU - {5154207E-1CF9-4065-A4DE-456D8E369524}.Release|x86.Build.0 = Release|Any CPU - {7A5043D3-B280-49D6-8C69-9E02594141E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7A5043D3-B280-49D6-8C69-9E02594141E5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7A5043D3-B280-49D6-8C69-9E02594141E5}.Debug|x86.ActiveCfg = Debug|Any CPU - {7A5043D3-B280-49D6-8C69-9E02594141E5}.Debug|x86.Build.0 = Debug|Any CPU - {7A5043D3-B280-49D6-8C69-9E02594141E5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7A5043D3-B280-49D6-8C69-9E02594141E5}.Release|Any CPU.Build.0 = Release|Any CPU - {7A5043D3-B280-49D6-8C69-9E02594141E5}.Release|x86.ActiveCfg = Release|Any CPU - {7A5043D3-B280-49D6-8C69-9E02594141E5}.Release|x86.Build.0 = Release|Any CPU - {13CF4A38-8857-442E-A496-76982F0A1D48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {13CF4A38-8857-442E-A496-76982F0A1D48}.Debug|Any CPU.Build.0 = Debug|Any CPU - {13CF4A38-8857-442E-A496-76982F0A1D48}.Debug|x86.ActiveCfg = Debug|x86 - {13CF4A38-8857-442E-A496-76982F0A1D48}.Debug|x86.Build.0 = Debug|x86 - {13CF4A38-8857-442E-A496-76982F0A1D48}.Release|Any CPU.ActiveCfg = Release|Any CPU - {13CF4A38-8857-442E-A496-76982F0A1D48}.Release|Any CPU.Build.0 = Release|Any CPU - {13CF4A38-8857-442E-A496-76982F0A1D48}.Release|x86.ActiveCfg = Release|x86 - {13CF4A38-8857-442E-A496-76982F0A1D48}.Release|x86.Build.0 = Release|x86 - {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Debug|x86.ActiveCfg = Debug|Any CPU - {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Debug|x86.Build.0 = Debug|Any CPU - {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Release|Any CPU.Build.0 = Release|Any CPU - {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Release|x86.ActiveCfg = Release|Any CPU - {6CB65EA1-51C6-4450-B174-F0A04C489FB5}.Release|x86.Build.0 = Release|Any CPU - {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Debug|x86.ActiveCfg = Debug|Any CPU - {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Debug|x86.Build.0 = Debug|Any CPU - {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Release|Any CPU.Build.0 = Release|Any CPU - {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Release|x86.ActiveCfg = Release|Any CPU - {F88DF354-579F-4079-8A74-15F5CC4BEDBA}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {15082DA4-9040-4FA7-A6C6-D1446CB629B9} - EndGlobalSection -EndGlobal diff --git a/LIN.Identity/Areas/Accounts/AccountController.cs b/LIN.Identity/Areas/Accounts/AccountController.cs deleted file mode 100644 index e476a39..0000000 --- a/LIN.Identity/Areas/Accounts/AccountController.cs +++ /dev/null @@ -1,311 +0,0 @@ -using Account = LIN.Identity.Validations.Account; - -namespace LIN.Identity.Areas.Accounts; - - -[Route("account")] -public class AccountController : ControllerBase -{ - - /// - /// Crear una cuenta. - /// - /// Modelo de la cuenta. - [HttpPost("create")] - public async Task Create([FromBody] AccountModel? modelo) - { - - // Comprobaciones - if (modelo == null || modelo.Identity == null || modelo.Contraseña.Length < 4 || modelo.Nombre.Length <= 0 || modelo.Identity.Unique.Length <= 0) - return new(Responses.InvalidParam) - { - Message = "Uno o varios parámetros son inválidos." - }; - - // Organización del modelo - modelo = Account.Process(modelo); - - // Creación del usuario - var response = await Data.Accounts.Create(modelo); - - // Evaluación - if (response.Response != Responses.Success) - return new(response.Response) - { - Message = "Hubo un error al crear la cuenta." - }; - - // Obtiene el usuario - var token = Jwt.Generate(response.Model, 0); - - // Retorna el resultado - return new CreateResponse() - { - LastID = response.Model.Identity.Id, - Response = Responses.Success, - Token = token, - Message = "Cuenta creada satisfactoriamente." - }; - - } - - - - /// - /// Obtener una cuenta. - /// - /// Id de la cuenta. - /// Token de acceso. - [HttpGet("read/id")] - [TokenAuth] - public async Task> Read([FromQuery] int id, [FromHeader] string token) - { - - // Id es invalido. - if (id <= 0 || string.IsNullOrWhiteSpace(token)) - return new(Responses.InvalidParam) - { - Message = "Uno o varios parámetros son inválidos." - }; - - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - // Obtiene el usuario. - var response = await Data.Accounts.Read(id, new() - { - ContextAccount = tokenInfo.AccountId, - FindOn = Models.FindOn.StableAccounts, - IncludeOrg = Models.IncludeOrg.IncludeIf, - IsAdmin = false, - OrgLevel = Models.IncludeOrgLevel.Advance - }); - - // Si es erróneo - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = response.Response - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Obtener una cuenta. - /// - /// Identidad. - /// Token de acceso. - [HttpGet("read/user")] - [TokenAuth] - public async Task> Read([FromQuery] string user, [FromHeader] string token) - { - - // Usuario es invalido. - if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(token)) - return new(Responses.InvalidParam) - { - Message = "Uno o varios parámetros son inválidos." - }; - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - - // Obtiene el usuario. - var response = await Data.Accounts.Read(user, new() - { - ContextAccount = tokenInfo.AccountId, - FindOn = Models.FindOn.StableAccounts, - IncludeOrg = Models.IncludeOrg.IncludeIf, - OrgLevel = Models.IncludeOrgLevel.Advance - }); - - // Si es erróneo - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = response.Response, - Model = new() - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Obtiene una lista de diez (10) usuarios que coincidan con un patron - /// - /// Patron - /// Token de acceso - [HttpGet("search")] - [TokenAuth] - public async Task> Search([FromQuery] string pattern) - { - - // Comprobación - if (pattern.Trim().Length <= 0 || string.IsNullOrWhiteSpace(pattern)) - return new(Responses.InvalidParam) - { - Message = "Uno o varios parámetros son inválidos." - }; - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - // Obtiene el usuario - var response = await Data.Accounts.Search(pattern, new() - { - ContextAccount = tokenInfo.AccountId, - FindOn = Models.FindOn.StableAccounts, - IncludeOrg = Models.IncludeOrg.IncludeIf, - OrgLevel = Models.IncludeOrgLevel.Advance - }); - - return response; - } - - - - /// - /// Obtiene una lista cuentas - /// - /// IDs de las cuentas - /// Token de acceso - [HttpPost("findAll")] - [TokenAuth] - public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - // Obtiene el usuario - var response = await Data.Accounts.FindAll(ids, new() - { - ContextAccount = tokenInfo.AccountId, - FindOn = Models.FindOn.StableAccounts, - IncludeOrg = Models.IncludeOrg.Include, - OrgLevel = Models.IncludeOrgLevel.Advance - }); - - return response; - - } - - - - /// - /// (ADMIN) encuentra diez (10) usuarios que coincidan con el patron - /// - /// - /// - [HttpGet("admin/search")] - [TokenAuth] - public async Task> FindAll([FromQuery] string pattern, [FromHeader] string token) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - var rol = AccountRoles.User; - - - if (rol != AccountRoles.Admin) - return new(Responses.Unauthorized); - - // Obtiene el usuario - var response = await Data.Accounts.Search(pattern, new() - { - OrgLevel = Models.IncludeOrgLevel.Advance, - ContextAccount = 0, - FindOn = Models.FindOn.AllAccount, - IncludeOrg = Models.IncludeOrg.Include, - IsAdmin = true - }); - - return response; - - } - - - - /// - /// Actualiza la información de una cuenta. - /// - /// Modelo - /// Token de acceso - [HttpPut("update")] - [TokenAuth] - public async Task Update([FromBody] AccountModel modelo, [FromHeader] string token) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - // Organizar el modelo. - modelo.Identity.Id = tokenInfo.IdentityId; - modelo.Perfil = Image.Zip(modelo.Perfil ?? []); - - if (modelo.Identity.Id <= 0 || modelo.Nombre.Any()) - return new(Responses.InvalidParam); - - return await Data.Accounts.Update(modelo); - - } - - - - /// - /// Actualiza el genero de un usuario - /// - /// Token de acceso - /// Nuevo genero - [HttpPatch("update/gender")] - [TokenAuth] - public async Task Update([FromHeader] string token, [FromHeader] Genders genero) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - // Realizar actualización. - return await Data.Accounts.Update(tokenInfo.AccountId, genero); - - } - - - - /// - /// Actualiza la visibilidad de una cuenta. - /// - /// Token de acceso. - /// Nueva visibilidad. - [HttpPatch("update/visibility")] - [TokenAuth] - public async Task Update([FromHeader] string token, [FromHeader] AccountVisibility visibility) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - // Actualización. - return await Data.Accounts.Update(tokenInfo.AccountId, visibility); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/Accounts/AccountLogsController.cs b/LIN.Identity/Areas/Accounts/AccountLogsController.cs deleted file mode 100644 index cf5c8d7..0000000 --- a/LIN.Identity/Areas/Accounts/AccountLogsController.cs +++ /dev/null @@ -1,32 +0,0 @@ - - -namespace LIN.Identity.Areas.Accounts; - - -[Route("account/logs")] -public class AccountLogsController : ControllerBase -{ - - - /// - /// Obtienes la lista de accesos asociados a una cuenta - /// - /// Token de acceso - [HttpGet] - [TokenAuth] - public async Task> GetAll([FromHeader] string token) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - // Obtiene el usuario. - var result = await Data.Logins.ReadAll(tokenInfo.AccountId); - - // Retorna el resultado. - return result ?? new(); - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs b/LIN.Identity/Areas/Accounts/AccountSecurityController.cs deleted file mode 100644 index a566dde..0000000 --- a/LIN.Identity/Areas/Accounts/AccountSecurityController.cs +++ /dev/null @@ -1,100 +0,0 @@ -namespace LIN.Identity.Areas.Accounts; - - -[Route("account/security")] -public class AccountSecurityController : ControllerBase -{ - - - - /// - /// Elimina una cuenta - /// - /// Token de acceso - [HttpDelete("delete")] - [TokenAuth] - public async Task Delete([FromHeader] string token) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - if (tokenInfo.AccountId <= 0) - return new(Responses.InvalidParam); - - var response = await Data.Accounts.Delete(tokenInfo.AccountId); - return response; - } - - - - /// - /// Actualizar la contraseña. - /// - /// Id de la cuenta actual. - /// Contraseña actual. - /// Nueva contraseña. - [HttpPatch("update/password")] - [TokenAuth] - public async Task UpdatePassword([FromHeader] int account, [FromQuery] string actualPassword, [FromHeader] string newPassword) - { - - // Validación de parámetros. - if (account <= 0 || string.IsNullOrWhiteSpace(actualPassword) || string.IsNullOrWhiteSpace(newPassword)) - return new(Responses.InvalidParam) - { - Message = "Uno o varios parámetros son inválidos." - }; - - // Tamaño invalido. - if (newPassword.Length < 4) - return new(Responses.InvalidParam) - { - Message = "La nueva contraseña debe de tener mas de 4 dígitos y cumplir con las políticas asociadas." - }; - - - // Data actual. - var actualData = await Data.Accounts.Read(account, new() - { - SensibleInfo = true, - ContextAccount = account, - FindOn = Models.FindOn.StableAccounts, - IncludeOrg = Models.IncludeOrg.None, - IsAdmin = true, - OrgLevel = Models.IncludeOrgLevel.Basic - }); - - // Si no existe la cuenta. - if (actualData.Response != Responses.Success) - return new(Responses.NotExistAccount) - { - Message = "No se encontró la cuenta." - }; - - // Encriptar la contraseña actual. - if (EncryptClass.Encrypt(actualPassword) != actualData.Model.Contraseña) - return new(Responses.Unauthorized) - { - Message = "La contraseña actual es diferente a la proporcionada." - }; - - // Validar políticas de contraseña. - var result = await AccountPassword.ValidatePassword(actualData.Model.IdentityId, newPassword); - - // Invalido por políticas - if (!result) - return new(Responses.PoliciesNotComplied) - { - Message = "Invalidado por no cumplir las políticas del directorio." - }; - - // Encriptar la nueva contraseña. - var response = await Data.Accounts.Update(account, EncryptClass.Encrypt(newPassword)); - - return response; - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/Applications/ApplicationController.cs b/LIN.Identity/Areas/Applications/ApplicationController.cs deleted file mode 100644 index 4440181..0000000 --- a/LIN.Identity/Areas/Applications/ApplicationController.cs +++ /dev/null @@ -1,101 +0,0 @@ -namespace LIN.Identity.Areas.Applications; - - -[Route("applications")] -public class ApplicationController : ControllerBase -{ - - - /// - /// Crear nueva aplicación. - /// - /// Modelo. - /// Token de acceso. - [HttpPost] - [TokenAuth] - public async Task Create([FromBody] ApplicationModel applicationModel, [FromHeader] string token) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - // Validaciones. - if (applicationModel == null || applicationModel.ApplicationUid.Trim().Length < 4 || applicationModel.Name.Trim().Length < 4) - return new CreateResponse() - { - Response = Responses.InvalidParam, - Message = "Parámetros inválidos." - }; - - // Preparar el modelo - applicationModel.ApplicationUid = applicationModel.ApplicationUid.Trim().ToLower(); - applicationModel.Name = applicationModel.Name.Trim().ToLower(); - applicationModel.AccountID = tokenInfo.AccountId; - - // Crear la aplicación. - return await Data.Applications.Create(applicationModel); - - } - - - - /// - /// Obtener las aplicaciones asociadas - /// - /// Token de acceso - [HttpGet] - [TokenAuth] - public async Task> GetAll([FromHeader] string token) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - - // Obtiene la data. - var data = await Data.Applications.ReadAll(tokenInfo.AccountId); - - return data; - - } - - - - /// - /// Crear acceso permitido a una app. - /// - /// Token de acceso. - /// ID de la aplicación. - /// ID del integrante. - [HttpPut] - [TokenAuth] - public async Task> InsertAllow([FromHeader] string token, [FromHeader] int appId, [FromHeader] int accountId) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - // Respuesta de Iam. - var iam = await Services.Iam.Applications.ValidateAccess(tokenInfo.AccountId, appId); - - // Validación de Iam - if (iam.Model != IamLevels.Privileged) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "No tienes autorización para modificar este recurso." - }; - - - // Enviar la actualización - var data = await Data.Applications.AllowTo(appId, accountId); - - return data; - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/Directories/DirectoryController.cs b/LIN.Identity/Areas/Directories/DirectoryController.cs deleted file mode 100644 index 4bafde6..0000000 --- a/LIN.Identity/Areas/Directories/DirectoryController.cs +++ /dev/null @@ -1,136 +0,0 @@ -using LIN.Identity.Data.Areas.Directories; - -namespace LIN.Identity.Areas.Directories; - - -[Route("directory")] -public class DirectoryController : ControllerBase -{ - - - /// - /// Obtener un directorio. - /// - /// ID del directorio. - /// Identidad de contexto. - /// Token de acceso. - [HttpGet("read/id")] - [TokenAuth] - public async Task> Read([FromQuery] int id, [FromQuery] int findIdentity, [FromHeader] string token) - { - - // Id es invalido. - if (id <= 0) - return new(Responses.InvalidParam); - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - - - // Acceso IAM. - var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); - - // Si no hay acceso. - if (Roles.View(roles)) - return new ReadOneResponse() - { - Response = Responses.Unauthorized, - Message = "No tienes permisos para acceder a este recurso." - }; - - // Obtiene el directorio. - var response = await Data.Areas.Directories.Directories.Read(id, findIdentity); - - // Si es erróneo - if (response.Response != Responses.Success) - return new ReadOneResponse() - { - Response = response.Response - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Obtener los directorios asociados. - /// - /// Token de acceso. - [HttpGet("read/all")] - [TokenAuth] - public async Task> ReadAll([FromHeader] string token) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - // Si el token no es valido. - - - // Obtiene el usuario. - var response = await Data.Areas.Directories.Directories.ReadAll(tokenInfo.IdentityId); - - // Si es erróneo - if (response.Response != Responses.Success) - return new() - { - Response = response.Response - }; - - // Retorna el resultado - return response; - - } - - - - /// - /// Obtener los integrantes asociados a un directorio. - /// - /// Token de acceso. - /// ID del directorio. - [HttpGet("read/members")] - [TokenAuth] - public async Task> ReadAll([FromHeader] string token, [FromQuery] int directory) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - - - // Acceso IAM. - var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); - - // Si no hay acceso. - if (Roles.ViewMembers(roles)) - return new() - { - Response = Responses.Unauthorized, - Message = "No tienes permisos para visualizar los integrantes de este recurso." - }; - - // Obtiene el usuario. - var response = await DirectoryMembers.ReadMembers(directory); - - // Si es erróneo - if (response.Response != Responses.Success) - return new() - { - Response = response.Response - }; - - // Retorna el resultado - return response; - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs b/LIN.Identity/Areas/Directories/DirectoryMemberController.cs deleted file mode 100644 index 098f840..0000000 --- a/LIN.Identity/Areas/Directories/DirectoryMemberController.cs +++ /dev/null @@ -1,53 +0,0 @@ -using LIN.Identity.Data.Areas.Directories; - -namespace LIN.Identity.Areas.Directories; - - -[Route("directory/members")] -public class DirectoryMembersController : ControllerBase -{ - - - [HttpPost] - [TokenAuth] - public async Task Create([FromHeader] string token, [FromBody] DirectoryMember model) - { - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - // Acceso IAM. - var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); - - // Si no hay acceso. - if (Roles.AlterMembers(roles)) - return new() - { - Response = Responses.Unauthorized, - Message = "No tienes permisos para administrar los integrantes de este recurso." - }; - - - - var guestIdentity = await Data.Identities.Read(model.Identity.Id); - - if (guestIdentity.Response != Responses.Success) - return new() - { - Message = "Error", - Response = Responses.NotRows - }; - - - // Obtiene el usuario. - var response = await DirectoryMembers.Create(model); - - - // Retorna el resultado - return response; - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/Organizations/MemberController.cs b/LIN.Identity/Areas/Organizations/MemberController.cs deleted file mode 100644 index da25ceb..0000000 --- a/LIN.Identity/Areas/Organizations/MemberController.cs +++ /dev/null @@ -1,154 +0,0 @@ -using LIN.Identity.Data.Areas.Organizations; -using Account = LIN.Identity.Validations.Account; - -namespace LIN.Identity.Areas.Organizations; - - -[Route("orgs/members")] -public class MemberController : ControllerBase -{ - - - /// - /// Crea un nuevo miembro en una organización. - /// - /// Modelo de la cuenta - /// Token de acceso de un administrador - /// Rol asignado - [HttpPost] - [TokenAuth] - public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] Types.Identity.Enumerations.Roles rol) - { - - // Validar el modelo. - if (modelo == null || modelo.Identity == null || string.IsNullOrWhiteSpace(modelo.Identity.Unique) || string.IsNullOrWhiteSpace(modelo.Nombre)) - return new() - { - Response = Responses.InvalidParam, - Message = "Uno o varios parámetros inválidos." - }; - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - - // Ajustar el modelo. - modelo.Visibilidad = AccountVisibility.Hidden; - modelo.Contraseña = $"ChangePwd@{DateTime.Now.Year}"; - modelo = Account.Process(modelo); - - // Obtiene el usuario. - var userContext = await Data.Accounts.ReadBasic(tokenInfo.AccountId); - - // Error al encontrar el usuario - if (userContext.Response != Responses.Success) - return new CreateResponse - { - Message = "No se encontró un usuario valido.", - Response = Responses.Unauthorized - }; - - // Encontrar el directorio de la organización. - var orgBase = await Data.Areas.Organizations.Organizations.FindBaseDirectory(userContext.Model.IdentityId); - - // Si no se encontró el directorio. - if (orgBase.Response != Responses.Success) - return new() - { - Response = Responses.NotRows, - Message = "No se encontró una organización permitida a este usuario." - }; - - // Permisos para alterar los integrantes. - var iam = Validations.Roles.AlterMembers(orgBase.Model.Rol); - - // No tienes permisos. - if (!iam) - return new() - { - Response = Responses.Unauthorized, - Message = "No tienes permisos para modificar los integrantes de esta organización." - }; - - - - - - - - - - - // Validar acceso en el directorio con IAM. - //var iam = await Services.Iam.Directories.ValidateAccess(userContext.Model.IdentityId, orgBase.Model.DirectoryId); - - - //DirectoryRoles[] roles = [DirectoryRoles.System, DirectoryRoles.SuperManager, DirectoryRoles.Manager, DirectoryRoles.AccountsOperator, DirectoryRoles.Operator]; - - - //if (!roles.Contains(iam.Model)) - // return new CreateResponse - // { - // Message = $"No tienes permisos suficientes para crear nuevas cuentas en el directorio general de tu organización.", - // Response = Responses.Unauthorized - // }; - - // Conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - // Creación del usuario - var response = await Members.Create(modelo, orgBase.Model.DirectoryId, rol, context); - - // Evaluación - if (response.Response != Responses.Success) - return new(response.Response); - - // Cierra la conexión - context.CloseActions(connectionKey); - - // Retorna el resultado - return new CreateResponse() - { - LastID = response.Model.ID, - Response = Responses.Success, - Message = "Success" - }; - - } - - - - /// - /// Obtiene la lista de integrantes asociados a una organización. - /// - /// Token de acceso - [HttpGet] - [TokenAuth] - public async Task> ReadAll([FromHeader] string token) - { - - // Información del token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); ; - - - - // Obtiene los miembros. - var members = await Members.ReadAll(tokenInfo.OrganizationId); - - // Error al obtener los integrantes. - if (members.Response != Responses.Success) - return new ReadAllResponse - { - Message = "No se encontró la organización.", - Response = Responses.Unauthorized - }; - - // Retorna el resultado - return members; - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Areas/Policies/PolicyController.cs b/LIN.Identity/Areas/Policies/PolicyController.cs deleted file mode 100644 index 8cbffa6..0000000 --- a/LIN.Identity/Areas/Policies/PolicyController.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace LIN.Identity.Areas.Organizations; - - -[Route("policies")] -public class PolicyController : ControllerBase -{ - - - /// - /// Crear política. - /// - /// Modelo. - /// Token de acceso. - [HttpPost] - [TokenAuth] - public async Task Create([FromBody] PolicyModel policy, [FromHeader] string token) - { - - // Validar parámetros. - if (policy == null || string.IsNullOrEmpty(token) || policy.DirectoryId <= 0) - return new CreateResponse - { - Message = "Parámetros inválidos.", - Response = Responses.InvalidParam - }; - - // Token. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - // Acceso IAM. - // var iam = await Services.Iam.Directories.ValidateAccess(identity, policy.DirectoryId); - - // SI no tiene permisos de modificación. - //if (iam.Model != IamLevels.Privileged) - // return new CreateResponse - // { - // Message = "No tienes permiso para modificar este directorio.", - // Response = Responses.Unauthorized - // }; - - - return await Data.Policies.Create(new() - { - Creation = DateTime.Now, - Directory = new() - { - ID = policy.DirectoryId, - }, - Id = 0, - Type = policy.Type, - ValueJson = policy.ValueJson, - }); - - } - - - - /// - /// Valida el acceso a un permiso de una identidad. - /// - /// ID de la identidad - /// ID de la política de permisos - [HttpGet("access")] - public async Task> ValidatePermissions([FromQuery] int identity, [FromQuery] int policy) - { - - // Validar parámetros. - if (identity <= 0 || policy <= 0) - return new() - { - Message = "Parámetros inválidos.", - Response = Responses.Unauthorized - }; - - // Respuesta. - return await Data.Policies.ValidatePermission(identity, policy); - - } - - - - /// - /// Obtiene las políticas asociadas a un directorio. - /// - /// Token de acceso. - /// Id del directorio. - [HttpGet("read/all")] - [TokenAuth] - public async Task> ReadAll([FromHeader] string token, [FromQuery] int directory) - { - - // Validar parámetros. - if (directory <= 0) - return new() - { - Message = "Parámetros inválidos.", - Response = Responses.InvalidParam - }; - - // Validar JSON. - JwtModel tokenInfo = HttpContext.Items["token"] as JwtModel ?? new(); - - - - // Acceso IAM. - var (_, _, roles) = await Data.Queries.Directories.Get(tokenInfo.IdentityId); - - // Si no hay acceso. - if (Roles.ViewPolicy(roles)) - return new() - { - Response = Responses.Unauthorized, - Message = "No tienes permisos para visualizar las políticas de este recurso." - }; - - // Respuesta. - return await Data.Policies.ReadAll(directory); - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Conexion.cs b/LIN.Identity/Conexion.cs deleted file mode 100644 index 0bebd91..0000000 --- a/LIN.Identity/Conexion.cs +++ /dev/null @@ -1,225 +0,0 @@ -namespace LIN.Identity; - - -/// -/// Conexión con la base de datos -/// -public sealed class Conexión -{ - - - //===== Estáticas =====// - - /// - /// String de Conexión - /// - private static string _connection = string.Empty; - - - /// - /// Contador de conexiones abiertas - /// - private static volatile int _counter = 0; - - - /// - /// Cantidad de conexiones que se pueden almacenar en cache - /// - private static int _cantidad = 1; - - - - //===== Propiedades =====// - - - /// - /// Obtiene o establece si la Conexión esta en uso - /// - private volatile bool OnUse = false; - - - - /// - /// Obtiene si la Conexión esta en uso y la pone en uso - /// - private bool OnUseAction - { - - get - { - lock (this) - { - if (!OnUse) - { - OnUse = true; - return false; - } - - return true; - - } - - } - - } - - - - /// - /// Obtiene el numero de Conexión - /// - public readonly int ConnectionNumber; - - - - /// - /// Cache de conexiones - /// - private static List CacheConnections { get; set; } = new(); - - - - - - - /// - /// Obtiene la base de datos - /// - public Data.Context DataBase { get; private set; } - - - - /// - /// Nueva Conexión - /// - private Conexión() - { - - DbContextOptionsBuilder optionsBuilder = new(); - optionsBuilder.UseSqlServer(_connection); - - DataBase = new(optionsBuilder.Options); - - _counter++; - ConnectionNumber = _counter; - - - if (CacheConnections.Count <= _cantidad) - CacheConnections.Add(this); - - } - - - - /// - /// Destructor - /// - ~Conexión() - { - this?.DataBase?.Dispose(); - } - - - - - /// - /// Establece que la conexión esta en uso - /// - public void SetOnUse() - { - lock (this) - { - OnUse = true; - } - - } - - - - private string _myKey = string.Empty; - - public void CloseActions(string key) - { - lock (this) - { - if (_myKey != key) - return; - - DataBase.ChangeTracker.Clear(); - _myKey = string.Empty; - OnUse = false; - } - } - - - - /// - /// Inicia las conexiones del cache - /// - public static async Task StartConnections() - { - - _cantidad = 5; -#if AZURE - _cantidad = 30; -#elif SOMEE - _cantidad = 10; -#elif DEBUG - _cantidad = 50; -#endif - - await Task.Run(() => - { - for (var i = 0; i < _cantidad; i++) - { - _ = new Conexión(); - } - }); - - } - - - - /// - /// Establece el string de Conexión - /// - /// string de Conexión - public static void SetStringConnection(string connectionString) - { - _connection = connectionString; - } - - - - /// - /// Obtiene una Conexión a la base de datos - /// - public static (Conexión context, string contextKey) GetOneConnection() - { - - // Obtiene una Conexión de la pool - var con = CacheConnections.FirstOrDefault(T => !T.OnUseAction); - - if (con != null && con._myKey == string.Empty) - { - lock (con) - { - con.SetOnUse(); - var key = KeyGen.Generate(10, "con."); - con._myKey = key; - return (con, key); - } - } - - // Retorna la Conexión - var conexión = new Conexión - { - _myKey = KeyGen.Generate(10, "con.") - }; - conexión.SetOnUse(); - return (conexión, conexión._myKey); - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Applications.cs b/LIN.Identity/Data/Applications.cs deleted file mode 100644 index e5ddc9c..0000000 --- a/LIN.Identity/Data/Applications.cs +++ /dev/null @@ -1,322 +0,0 @@ -namespace LIN.Identity.Data; - - -public class Applications -{ - - - #region Abstracciones - - - /// - /// Crea una app - /// - /// Modelo - public static async Task Create(ApplicationModel data) - { - var (context, contextKey) = Conexión.GetOneConnection(); - var response = await Create(data, context); - context.CloseActions(contextKey); - return response; - } - - - - /// - /// Obtiene la lista de apps asociados a una cuenta - /// - /// ID de la cuenta - public static async Task> ReadAll(int id) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await ReadAll(id, context); - context.CloseActions(contextKey); - return res; - } - - - - - /// - /// Agregar una identidad al directorio de una app. - /// - /// Id de la app. - /// Id de la identidad. - public static async Task> AllowTo(int appId, int accountId) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await AllowTo(appId, accountId, context); - context.CloseActions(contextKey); - return res; - } - - - - - - - /// - /// Obtiene una app - /// - /// ID de la app - public static async Task> Read(int id) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await Read(id, context); - context.CloseActions(contextKey); - return res; - } - - - /// - /// Obtiene una app - /// - /// Key de la app - public static async Task> Read(string key) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await Read(key, context); - context.CloseActions(contextKey); - return res; - } - - - #endregion - - - - /// - /// Crear aplicación. - /// - /// Modelo - /// Contexto de conexión - public static async Task Create(ApplicationModel data, Conexión context) - { - - // ID. - data.ID = 0; - - // Ejecución - try - { - - // Guardar la información. - await context.DataBase.Applications.AddAsync(data); - - // Llevar info a la BD. - context.DataBase.SaveChanges(); - - return new(Responses.Success, data.ID); - } - catch (Exception ex) - { - - if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) - return new(Responses.Undefined); - - } - - return new(); - } - - - - /// - /// Obtiene la lista de apps asociados a una cuenta. - /// - /// ID de la cuenta - /// Contexto de conexión - public static async Task> ReadAll(int id, Conexión context) - { - - // Ejecución - try - { - - // Query - var emails = await (from E in context.DataBase.Applications - where E.AccountID == id - select E).ToListAsync(); - - return new(Responses.Success, emails); - } - catch - { - } - - return new(); - } - - - - /// - /// Obtiene una app. - /// - /// ID de la app - /// Contexto de conexión - public static async Task> Read(int id, Conexión context) - { - - // Ejecución - try - { - - // Query - var email = await (from E in context.DataBase.Applications - where E.ID == id - select E).FirstOrDefaultAsync(); - - // Email no existe - if (email == null) - return new(Responses.NotRows); - - // Correcto. - return new(Responses.Success, email); - } - catch (Exception) - { - } - - return new(); - } - - - - /// - /// Obtiene una app - /// - /// Key de la app - /// Contexto de conexión - public static async Task> Read(string key, Conexión context) - { - - // Ejecución - try - { - - // Query - var email = await (from E in context.DataBase.Applications - where E.Key == key - select E).FirstOrDefaultAsync(); - - // Email no existe - if (email == null) - return new(Responses.NotRows); - - - return new(Responses.Success, email); - } - catch (Exception) - { - } - - return new(); - } - - - - /// - /// Obtiene una app - /// - /// UId de la app - /// Contexto de conexión - public static async Task> ReadByAppUid(string uid, Conexión context) - { - - // Ejecución - try - { - - // Query - var app = await (from E in context.DataBase.Applications - where E.ApplicationUid == uid - select E).FirstOrDefaultAsync(); - - // Email no existe - if (app == null) - return new(Responses.NotRows); - - return new(Responses.Success, app); - } - catch (Exception) - { - } - - return new(); - } - - - - /// - /// Permitir acceso a una identidad al directorio de una app. - /// - /// Id de la app. - /// Id de la identidad. - /// Cuenta de conexión. - public static async Task> AllowTo(int appId, int identityId, Conexión context) - { - - // Ejecución - try - { - - - var application = await (from app in context.DataBase.Applications - where app.ID == appId - select new ApplicationModel() - { - ID = app.ID, - DirectoryId = app.DirectoryId - }).FirstOrDefaultAsync(); - - if (application == null) - { - return new() - { - Response = Responses.NotRows - }; - } - - - if (application.DirectoryId <= 0) - { - return new() - { - Response = Responses.NotFoundDirectory - }; - } - - DirectoryMember member = new() - { - Identity = new IdentityModel() - { - Id = identityId - }, - Directory = new() - { - ID = application.DirectoryId, - } - }; - context.DataBase.Attach(member.Identity); - context.DataBase.Attach(member.Directory); - - context.DataBase.DirectoryMembers.Add(member); - - context.DataBase.SaveChanges(); - - return new(Responses.Success, true); - } - catch - { - } - - return new(); - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Areas/Accounts/AccountsGet.cs b/LIN.Identity/Data/Areas/Accounts/AccountsGet.cs deleted file mode 100644 index 57e68c3..0000000 --- a/LIN.Identity/Data/Areas/Accounts/AccountsGet.cs +++ /dev/null @@ -1,400 +0,0 @@ -namespace LIN.Identity.Data; - - -internal static partial class Accounts -{ - - - #region Abstracciones - - - public static async Task> Read(int id, Models.Account filters) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await Read(id, filters, context); - context.CloseActions(connectionKey); - return res; - - } - - - - - public static async Task> ReadByIdentity(int id, Models.Account filters) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadByIdentity(id, filters, context); - context.CloseActions(connectionKey); - return res; - - } - - - - - - - /// - /// Obtiene una cuenta - /// - /// ID de la cuenta - /// Incluir la organización - public static async Task> Read(string user, Models.Account filters) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await Read(user, filters, context); - context.CloseActions(connectionKey); - return res; - - } - - - - /// - /// Obtiene la información básica de un usuario - /// - /// ID del usuario - public static async Task> ReadBasic(int id) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadBasic(id, context); - context.CloseActions(connectionKey); - return res; - - } - - - - /// - /// Obtiene la información básica de un usuario - /// - /// Usuario único - public static async Task> ReadBasic(string user) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadBasic(user, context); - context.CloseActions(connectionKey); - return res; - - } - - - - - /// - /// Obtiene una lista de diez (10) usuarios que coincidan con un patron - /// - /// Patron de búsqueda - /// Mi ID - /// ID de la org de contexto - public static async Task> Search(string pattern, Models.Account filters) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await Search(pattern, filters, context); - context.CloseActions(connectionKey); - return res; - } - - - - - - - /// - /// Obtiene una lista de usuarios por medio del ID - /// - /// Lista de IDs - /// ID del usuario contexto - /// ID de organización - public static async Task> FindAll(List ids, Models.Account filters) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await FindAll(ids, filters, context); - context.CloseActions(connectionKey); - return res; - } - - - #endregion - - - - - /// - /// Obtiene un usuario - /// - /// ID del usuario - /// Filtros de búsqueda - /// Contexto de base de datos - public static async Task> Read(int id, Models.Account filters, Conexión context) - { - - // Ejecución - try - { - - var query = Queries.Identities.GetAccounts(id, filters, context); - - // Obtiene el usuario - var result = await query.FirstOrDefaultAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotExistAccount); - - return new(Responses.Success, result); - } - catch - { - } - - return new(); - } - - - - /// - /// Obtiene un usuario - /// - /// ID de la identidad - /// Filtros de búsqueda - /// Contexto de base de datos - public static async Task> ReadByIdentity(int id, Models.Account filters, Conexión context) - { - - // Ejecución - try - { - - var query = Queries.Identities.GetAccountsByIdentity(id, filters, context); - - // Obtiene el usuario - var result = await query.FirstOrDefaultAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotExistAccount); - - return new(Responses.Success, result); - } - catch - { - } - - return new(); - } - - - - /// - /// Obtiene un usuario - /// - /// Usuario único - /// Filtros de búsqueda - /// Contexto de base de datos - public static async Task> Read(string user, Models.Account filters, Conexión context) - { - - // Ejecución - try - { - - var query = Queries.Identities.GetAccounts(user, filters, context); - - // Obtiene el usuario - var result = await query.FirstOrDefaultAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotExistAccount); - - return new(Responses.Success, result); - } - catch - { - } - - return new(); - } - - - - /// - /// Buscar usuarios por patron de búsqueda. - /// - /// Patron de búsqueda - /// ID del usuario contexto - /// ID de la organización de contexto - /// Es administrador - /// Contexto de base de datos - public static async Task> Search(string pattern, Models.Account filters, Conexión context) - { - - // Ejecución - try - { - - List accountModels = await Queries.Identities.Search(pattern, filters, context).Take(10).ToListAsync(); - - // Si no existe el modelo - if (accountModels == null) - return new(Responses.NotRows); - - return new(Responses.Success, accountModels); - } - catch - { - } - - return new(); - } - - - - /// - /// Obtiene los usuarios con IDs coincidentes - /// - /// Lista de IDs - /// ID del usuario contexto - /// ID de la organización de contexto - /// Contexto de base de datos - public static async Task> FindAll(List ids, Models.Account filters, Conexión context) - { - - // Ejecución - try - { - - var query = Queries.Identities.GetAccounts(ids, filters, context); - - // Ejecuta - var result = await query.ToListAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotRows); - - return new(Responses.Success, result); - } - catch - { - } - - return new(); - } - - - - /// - /// Obtiene la información básica de un usuario - /// - /// ID del usuario - /// Contexto de base de datos - public static async Task> ReadBasic(int id, Conexión context) - { - - // Ejecución - try - { - - var query = from account in Queries.Identities.GetValidAccounts(context) - where account.ID == id - select new AccountModel - { - ID = account.ID, - Identity = new() - { - Id = account.Identity.Id, - Type = account.Identity.Type, - Unique = account.Identity.Unique - }, - Contraseña = account.Contraseña, - Estado = account.Estado, - Nombre = account.Nombre - }; - - // Obtiene el usuario - var result = await query.FirstOrDefaultAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotExistAccount); - - return new(Responses.Success, result); - } - catch - { - } - - return new(); - } - - - - /// - /// Obtiene la información básica de un usuario - /// - /// Usuario único - /// Contexto de base de datos - public static async Task> ReadBasic(string user, Conexión context) - { - - // Ejecución - try - { - - var query = from account in Queries.Identities.GetValidAccounts(context) - where account.Identity.Unique == user - select new AccountModel - { - ID = account.ID, - Identity = new() - { - Id = account.Identity.Id, - Type = account.Identity.Type, - Unique = account.Identity.Unique - }, - Contraseña = account.Contraseña, - Estado = account.Estado, - Nombre = account.Nombre - }; - - // Obtiene el usuario - var result = await query.FirstOrDefaultAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotExistAccount); - - return new(Responses.Success, result); - } - catch - { - } - - return new(); - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Areas/Accounts/AccountsPost.cs b/LIN.Identity/Data/Areas/Accounts/AccountsPost.cs deleted file mode 100644 index a52783f..0000000 --- a/LIN.Identity/Data/Areas/Accounts/AccountsPost.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace LIN.Identity.Data; - - -internal static partial class Accounts -{ - - - #region Abstracciones - - - /// - /// Crea una nueva cuenta - /// - /// Modelo de la nueva cuenta - public static async Task> Create(AccountModel data) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await Create(data, context); - context.CloseActions(connectionKey); - return res; - - } - - - #endregion - - - - /// - /// Crea una nueva cuenta - /// - /// Modelo de la cuenta - /// Contexto de base de datos - public static async Task> Create(AccountModel data, Conexión context) - { - - // Identidad. - data.Identity.Id = 0; - data.ID = 0; - - // Ejecución - try - { - var res = await context.DataBase.Accounts.AddAsync(data); - context.DataBase.SaveChanges(); - - return new(Responses.Success, data); - } - catch (Exception ex) - { - if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) - return new(Responses.ExistAccount); - - } - - return new(); - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs b/LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs deleted file mode 100644 index db6bb4b..0000000 --- a/LIN.Identity/Data/Areas/Accounts/AccountsUpdate.cs +++ /dev/null @@ -1,311 +0,0 @@ -namespace LIN.Identity.Data; - - -internal static partial class Accounts -{ - - - #region Abstracciones - - - /// - /// Elimina una cuenta - /// - /// ID de la cuenta - public static async Task Delete(int id) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - var res = await Delete(id, context); - context.CloseActions(connectionKey); - return res; - } - - - - /// - /// Actualiza la información de una cuenta - /// - /// Modelo nuevo de la cuenta - public static async Task Update(AccountModel modelo) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - var res = await Update(modelo, context); - context.CloseActions(connectionKey); - return res; - } - - - - - - /// - /// Actualiza el estado de un usuario - /// - /// ID del usuario - /// Nuevo estado - public static async Task Update(int id, AccountStatus status) - { - - var (context, key) = Conexión.GetOneConnection(); - - var res = await Update(id, status, context); - context.CloseActions(key); - return res; - - } - - - - /// - /// Actualiza el genero de un usuario - /// - /// ID del usuario - /// Nuevo genero - public static async Task Update(int id, Genders gender) - { - - var (context, key) = Conexión.GetOneConnection(); - - var res = await Update(id, gender, context); - context.CloseActions(key); - return res; - - } - - - - /// - /// Actualiza la visibilidad de un usuario - /// - /// ID del usuario - /// Nueva visibilidad - public static async Task Update(int id, AccountVisibility visibility) - { - - var (context, key) = Conexión.GetOneConnection(); - - var res = await Update(id, visibility, context); - context.CloseActions(key); - return res; - - } - - - - public static async Task Update(int id, string password) - { - - var (context, key) = Conexión.GetOneConnection(); - - var res = await Update(id, password, context); - context.CloseActions(key); - return res; - - } - - - #endregion - - - /// - /// Elimina una cuenta. - /// - /// ID de la cuenta - /// Contexto de conexión - public static async Task Delete(int id, Conexión context) - { - - // Ejecución - try - { - - // Obtiene el usuario - var user = await context.DataBase.Accounts.FindAsync(id); - - // Si no existe - if (user == null) - return new(Responses.Success); - - // Cambia el estado - user.Estado = AccountStatus.Deleted; - context.DataBase.SaveChanges(); - - // Retorna - return new(Responses.Success); - } - catch - { - } - - return new(); - } - - - - /// - /// Actualiza la información de una cuenta. - /// ** No actualiza datos sensibles. - /// - /// Modelo nuevo de la cuenta - /// Contexto de conexión - public static async Task Update(AccountModel modelo, Conexión context) - { - - // Ejecución - try - { - var user = await context.DataBase.Accounts.FindAsync(modelo.ID); - - // Si el usuario no se encontró - if (user == null || user.Estado != AccountStatus.Normal) - { - return new(Responses.NotExistAccount); - } - - // Nuevos datos - user.Perfil = modelo.Perfil; - user.Nombre = modelo.Nombre; - - context.DataBase.SaveChanges(); - return new(Responses.Success); - } - catch - { - } - - return new(); - } - - - - - - - /// - /// Actualiza el estado - /// - /// ID - /// Nuevo estado - /// Contexto de conexión con la BD - public static async Task Update(int user, AccountStatus status, Conexión context) - { - - // Encontrar el usuario - var usuario = await (from U in context.DataBase.Accounts - where U.ID == user - select U).FirstOrDefaultAsync(); - - // Si el usuario no existe - if (usuario == null) - { - return new(Responses.NotExistAccount); - } - - // Cambiar Contraseña - usuario.Estado = status; - - context.DataBase.SaveChanges(); - return new(Responses.Success); - - } - - - - /// - /// Actualiza el genero - /// - /// ID - /// Nuevo genero - /// Contexto de conexión con la BD - public static async Task Update(int user, Genders genero, Conexión context) - { - - // Encontrar el usuario - var usuario = await (from U in context.DataBase.Accounts - where U.ID == user - select U).FirstOrDefaultAsync(); - - // Si el usuario no existe - if (usuario == null) - { - return new(Responses.NotExistAccount); - } - - // Cambiar Contraseña - // usuario.Genero = genero; - - context.DataBase.SaveChanges(); - return new(Responses.Success); - - } - - - - /// - /// Actualiza la visibilidad - /// - /// ID - /// Nueva visibilidad - /// Contexto de conexión con la BD - public static async Task Update(int user, AccountVisibility visibility, Conexión context) - { - - // Encontrar el usuario - var usuario = await (from U in context.DataBase.Accounts - where U.ID == user - select U).FirstOrDefaultAsync(); - - // Si el usuario no existe - if (usuario == null) - { - return new(Responses.NotExistAccount); - } - - // Cambiar visibilidad - usuario.Visibilidad = visibility; - - context.DataBase.SaveChanges(); - return new(Responses.Success); - - } - - - - - /// - /// Actualiza la contraseña - /// - /// ID - /// Nueva contraseña - /// Contexto de conexión con la BD - public static async Task Update(int user, string password, Conexión context) - { - - // Encontrar el usuario - var usuario = await (from U in context.DataBase.Accounts - where U.ID == user - select U).FirstOrDefaultAsync(); - - // Si el usuario no existe - if (usuario == null) - { - return new(Responses.NotExistAccount); - } - - // Cambiar visibilidad - usuario.Contraseña = password; - - context.DataBase.SaveChanges(); - return new(Responses.Success); - - } - - - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Areas/Directories/Directories.cs b/LIN.Identity/Data/Areas/Directories/Directories.cs deleted file mode 100644 index ebccc14..0000000 --- a/LIN.Identity/Data/Areas/Directories/Directories.cs +++ /dev/null @@ -1,239 +0,0 @@ -namespace LIN.Identity.Data.Areas.Directories; - - -public class Directories -{ - - - #region Abstracciones - - - /// - /// Nuevo directorio vacío. - /// - /// Modelo. - public static async Task Create(DirectoryModel directory) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await Create(directory, context); - context.CloseActions(connectionKey); - return res; - - } - - - - /// - /// Obtiene los directorios donde una identidad es integrante. - /// - /// Id de la identidad. - public static async Task> ReadAll(int identityId) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadAll(identityId, context); - context.CloseActions(connectionKey); - return res; - - } - - - - /// - /// Obtener un directorio. - /// - /// Id del directorio. - /// Identidad del contexto (No necesaria).. - public static async Task> Read(int id, int identityContext = 0) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await Read(id, identityContext, context); - context.CloseActions(connectionKey); - return res; - - } - - - - public static async Task> ReadByIdentity(int id) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadByIdentity(id, context); - context.CloseActions(connectionKey); - return res; - - } - - - #endregion - - - - /// - /// Crear un directorio vacío. - /// - /// Modelo del directorio. - /// Contexto de conexión. - public static async Task Create(DirectoryModel model, Conexión context) - { - - // Modelo. - model.ID = 0; - model.Members = []; - - // Ejecución - try - { - - // Agregar el elemento. - await context.DataBase.Directories.AddAsync(model); - - // Guardar los cambios. - context.DataBase.SaveChanges(); - - // Respuesta. - return new() - { - Response = Responses.Success, - LastID = model.ID - }; - - } - catch (Exception) - { - } - - return new(); - } - - - - /// - /// Obtiene los directorios donde una identidad es integrante. - /// - /// Id de la identidad. - /// Contexto de conexión. - public static async Task> ReadAll(int identityId, Conexión context) - { - - // Ejecución - try - { - - var (_, identities, _) = await Queries.Directories.Get(identityId); - - // Directorios. - var directories = await (from directoryMember in context.DataBase.DirectoryMembers - where identities.Contains(directoryMember.IdentityId) - select new DirectoryMember - { - DirectoryId = directoryMember.DirectoryId, - Directory = new() - { - ID = directoryMember.Directory.ID, - Creación = directoryMember.Directory.Creación, - Nombre = directoryMember.Directory.Nombre, - IdentityId = directoryMember.Directory.IdentityId, - Identity = new() - { - Id = directoryMember.Directory.Identity.Id, - Unique = directoryMember.Directory.Identity.Unique, - Type = directoryMember.Directory.Identity.Type - } - }, - Identity = new() - { - Id = directoryMember.Identity.Id, - Unique = directoryMember.Identity.Unique, - Type = directoryMember.Identity.Type - }, - IdentityId = directoryMember.IdentityId - }).ToListAsync(); - - return new(Responses.Success, directories); - - } - catch (Exception) - { - } - - return new(); - } - - - - /// - /// Obtener un directorio. - /// - /// Id del directorio. - /// Identidad de contexto (No necesaria). - /// Contexto de conexión. - public static async Task> Read(int id, int identityContext, Conexión context) - { - - // Ejecución - try - { - - // Consulta. - var query = Queries.Identities.GetDirectory(id, identityContext, context); - - // Obtiene el usuario - var result = await query.FirstOrDefaultAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotExistAccount); - - return new(Responses.Success, result); - } - catch (Exception) - { - } - - return new(); - } - - - - - public static async Task> ReadByIdentity(int id, Conexión context) - { - - // Ejecución - try - { - - // Consulta. - var query = Queries.Identities.GetDirectoryByIdentity(id, context); - - // Obtiene el usuario - var result = await query.FirstOrDefaultAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotExistAccount); - - return new(Responses.Success, result); - } - catch (Exception) - { - } - - return new(); - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Areas/Directories/DirectoryMembers.cs b/LIN.Identity/Data/Areas/Directories/DirectoryMembers.cs deleted file mode 100644 index 403b701..0000000 --- a/LIN.Identity/Data/Areas/Directories/DirectoryMembers.cs +++ /dev/null @@ -1,131 +0,0 @@ -namespace LIN.Identity.Data.Areas.Directories; - - -public class DirectoryMembers -{ - - - #region Abstracciones - - - /// - /// Agregar un miembro a un directorio. - /// - /// Modelo. - public static async Task Create(DirectoryMember directory) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await Create(directory, context); - context.CloseActions(connectionKey); - return res; - - } - - - - - /// - /// Obtiene los integrantes de un directorio. - /// - /// Id del directorio. - public static async Task> ReadMembers(int identityId) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadMembers(identityId, context); - context.CloseActions(connectionKey); - return res; - - } - - - - #endregion - - - - /// - /// Agregar un miembro a un directorio. - /// - /// Modelo. - /// Contexto de conexión. - public static async Task Create(DirectoryMember model, Conexión context) - { - - // Ejecución - try - { - - // Ya existen los registros. - context.DataBase.Attach(model.Identity); - context.DataBase.Attach(model.Directory); - - // Agregar el elemento. - await context.DataBase.DirectoryMembers.AddAsync(model); - - // Guardar los cambios. - context.DataBase.SaveChanges(); - - // Respuesta. - return new() - { - Response = Responses.Success - }; - - } - catch (Exception) - { - } - - return new(); - } - - - - - - - /// - /// Obtiene los integrantes de un directorio. - /// - /// Id del directorio. - /// Contexto de conexión. - public static async Task> ReadMembers(int directoryId, Conexión context) - { - - // Ejecución - try - { - - // Directorios. - var directories = await (from directoryMember in context.DataBase.DirectoryMembers - where directoryMember.DirectoryId == directoryId - select new DirectoryMember - { - DirectoryId = directoryMember.DirectoryId, - Identity = new() - { - Id = directoryMember.Identity.Id, - Unique = directoryMember.Identity.Unique, - Type = directoryMember.Identity.Type - }, - IdentityId = directoryMember.IdentityId - }).ToListAsync(); - - return new(Responses.Success, directories); - - } - catch (Exception) - { - } - - return new(); - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Areas/Organizations/Members.cs b/LIN.Identity/Data/Areas/Organizations/Members.cs deleted file mode 100644 index f150399..0000000 --- a/LIN.Identity/Data/Areas/Organizations/Members.cs +++ /dev/null @@ -1,158 +0,0 @@ -namespace LIN.Identity.Data.Areas.Organizations; - - -public class Members -{ - - - #region Abstracciones - - - - - /// - /// Obtiene la lista de integrantes de una organización - /// - /// ID de la organización - public static async Task> ReadAll(int id) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await ReadAll(id, context); - context.CloseActions(contextKey); - return res; - } - - - - - public static async Task> Create(AccountModel data, int dir, Types.Identity.Enumerations.Roles rol) - { - - var (context, contextKey) = Conexión.GetOneConnection(); - var res = await Create(data, dir, rol, context); - context.CloseActions(contextKey); - return res; - } - - - #endregion - - - - /// - /// Crea una cuenta en una organización - /// - /// Modelo - /// Rol dentro de la organización - /// Contexto de conexión - public static async Task> Create(AccountModel data, int directory, Types.Identity.Enumerations.Roles rol, Conexión context) - { - - data.ID = 0; - - // Ejecución - using (var transaction = context.DataBase.Database.BeginTransaction()) - { - try - { - - // Guardar la cuenta. - context.DataBase.Accounts.Add(data); - context.DataBase.SaveChanges(); - - // Obtiene la organización. - int directoryId = await (from org in context.DataBase.Organizations - where org.DirectoryId == directory - select org.DirectoryId).FirstOrDefaultAsync(); - - // No existe la organización. - if (directoryId <= 0) - { - transaction.Rollback(); - return new(Responses.NotRows); - } - - // Modelo del integrante. - var member = new DirectoryMember() - { - Directory = new() - { - ID = directoryId - }, - Identity = data.Identity - }; - - // El directorio ya existe. - context.DataBase.Attach(member.Directory); - - // Guardar cambios. - context.DataBase.DirectoryMembers.Add(member); - context.DataBase.SaveChanges(); - - // Enviar la transacción. - transaction.Commit(); - - return new(Responses.Success, data); - } - catch (Exception ex) - { - transaction.Rollback(); - if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) - return new(Responses.ExistAccount); - - } - } - - return new(); - } - - - - /// - /// Obtiene la lista de integrantes de una organización. - /// - /// ID de la organización - /// Contexto de conexión - public static async Task> ReadAll(int id, Conexión context) - { - - // Ejecución - try - { - - // Organización - var members = from org in context.DataBase.Organizations - where org.ID == id - join m in context.DataBase.DirectoryMembers - on org.DirectoryId equals m.DirectoryId - select org.Directory; - - - var accounts = await (from account in context.DataBase.Accounts - join m in members - on account.IdentityId equals m.IdentityId - select new AccountModel - { - Creación = account.Creación, - ID = account.ID, - Nombre = account.Nombre, - Identity = new() - { - Id = account.Identity.Id, - Type = IdentityTypes.Account, - Unique = account.Identity.Unique - } - }).ToListAsync(); - - return new(Responses.Success, accounts); - } - catch - { - } - - return new(); - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Areas/Organizations/Organizations.cs b/LIN.Identity/Data/Areas/Organizations/Organizations.cs deleted file mode 100644 index 211ecd5..0000000 --- a/LIN.Identity/Data/Areas/Organizations/Organizations.cs +++ /dev/null @@ -1,270 +0,0 @@ -using Account = LIN.Identity.Validations.Account; - -namespace LIN.Identity.Data.Areas.Organizations; - - -public class Organizations -{ - - - #region Abstracciones - - - /// - /// Crea una organización. - /// - /// Modelo - public static async Task> Create(OrganizationModel data) - { - var (context, contextKey) = Conexión.GetOneConnection(); - var response = await Create(data, context); - context.CloseActions(contextKey); - return response; - } - - - - /// - /// Obtiene una organización. - /// - /// ID de la organización - public static async Task> Read(int id) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await Read(id, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Encontrar el directorio de una organización. - /// - /// Id del integrante directo. - public static async Task> FindBaseDirectory(int identity) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await FindBaseDirectory(identity, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Encontrar una organización según la identidad de un integrante directo. - /// - /// Id del integrante. - public static async Task> FindOrganization(int identity) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await FindBaseDirectory(identity, context); - context.CloseActions(contextKey); - return res; - } - - - - - #endregion - - - - /// - /// Crear una organización. - /// - /// Modelo - /// Contexto de conexión - public static async Task> Create(OrganizationModel data, Conexión context) - { - - // Modelo. - data.ID = 0; - - // Ejecución - using (var transaction = context.DataBase.Database.BeginTransaction()) - { - try - { - - // Guardar datos. - await context.DataBase.Organizations.AddAsync(data); - - // Guardar en BD. - context.DataBase.SaveChanges(); - - // Cuenta de administración. - AccountModel account = new() - { - Contraseña = "root123", - Creación = DateTime.Now, - Estado = AccountStatus.Normal, - Gender = Genders.Undefined, - ID = 0, - Identity = new() - { - Id = 0, - Type = IdentityTypes.Account, - Unique = $"root@{data.Directory.Identity.Unique}" - }, - Insignia = AccountBadges.None, - Nombre = $"Root user {data.Directory.Identity.Unique}", - Perfil = [], - Rol = AccountRoles.User, - Visibilidad = AccountVisibility.Hidden, - IdentityId = 0 - }; - - // Procesar la cuenta. - account = Account.Process(account); - - // Guardar la cuenta. - context.DataBase.Accounts.Add(account); - - // Agregar el miembro. - data.Directory.Members.Add(new() - { - Directory = data.Directory, - Identity = account.Identity, - Rol = Types.Identity.Enumerations.Roles.SuperManager - }); - - // Guardar. - context.DataBase.SaveChanges(); ; - - // Commit cambios. - transaction.Commit(); - - return new(Responses.Success, data); - } - catch (Exception ex) - { - transaction.Rollback(); - if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) - return new(Responses.Undefined); - - } - } - return new(); - } - - - - /// - /// Obtiene una organización. - /// - /// ID de la organización - /// Contexto de conexión - public static async Task> Read(int id, Conexión context) - { - - // Ejecución - try - { - - // Query - var org = await (from E in context.DataBase.Organizations - where E.ID == id - select new OrganizationModel - { - Directory = null!, - DirectoryId = id, - ID = E.ID, - IsPublic = E.IsPublic, - Name = E.Name, - }).FirstOrDefaultAsync(); - - // Email no existe - if (org == null) - return new(Responses.NotRows); - - return new(Responses.Success, org); - } - catch - { - } - - return new(); - } - - - - /// - /// Encontrar el directorio de una organización. - /// - /// Id del integrante directo. - /// Contexto de conexión. - public static async Task> FindBaseDirectory(int identity, Conexión context) - { - - // Ejecución - try - { - - // Consulta el directorio base. - var query = await (from org in context.DataBase.Organizations - join dir in context.DataBase.Directories - on org.DirectoryId equals dir.ID - join mem in context.DataBase.DirectoryMembers - on dir.ID equals mem.DirectoryId - where mem.Rol != Types.Identity.Enumerations.Roles.Guest - && mem.Rol != Types.Identity.Enumerations.Roles.RoyalGuest - where identity == dir.IdentityId - select mem).FirstOrDefaultAsync(); - - // Email no existe - if (query == null) - return new(Responses.NotRows); - - return new(Responses.Success, query); - } - catch - { - } - - return new(); - } - - - - /// - /// Encontrar una organización según la identidad de un integrante directo. - /// - /// Id del integrante. - /// Contexto de conexión. - public static async Task> FindOrganization(int identity, Conexión context) - { - - // Ejecución - try - { - - // Consulta el directorio base. - var query = await (from org in context.DataBase.Organizations - join dir in context.DataBase.Directories - on org.DirectoryId equals dir.ID - join mem in context.DataBase.DirectoryMembers - on dir.ID equals mem.DirectoryId - where identity == dir.IdentityId - select org).FirstOrDefaultAsync(); - - // Email no existe - if (query == null) - return new(Responses.NotRows); - - return new(Responses.Success, query); - } - catch - { - } - - return new(); - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Context.cs b/LIN.Identity/Data/Context.cs deleted file mode 100644 index 2f5df37..0000000 --- a/LIN.Identity/Data/Context.cs +++ /dev/null @@ -1,162 +0,0 @@ -namespace LIN.Identity.Data; - - -/// -/// Nuevo contexto a la base de datos. -/// -public class Context(DbContextOptions options) : DbContext(options) -{ - - - /// - /// Identidades. - /// - public DbSet Identities { get; set; } - - - /// - /// Cuentas de usuario. - /// - public DbSet Accounts { get; set; } - - - /// - /// Organizaciones. - /// - public DbSet Organizations { get; set; } - - - /// - /// Directorios. - /// - public DbSet Directories { get; set; } - - - /// - /// Integrantes de Directorios. - /// - public DbSet DirectoryMembers { get; set; } - - - /// - /// Tabla de aplicaciones - /// - public DbSet Applications { get; set; } - - - /// - /// Tabla de registros de login - /// - public DbSet LoginLogs { get; set; } - - - /// - /// Tabla de correos - /// - public DbSet Emails { get; set; } - - - /// - /// Políticas. - /// - public DbSet Policies { get; set; } - - - - /// - /// Naming DB - /// - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - - // Indices y identidad. - modelBuilder.Entity() - .HasIndex(e => e.Unique) - .IsUnique(); - - // Indices y identidad. - modelBuilder.Entity() - .HasIndex(e => e.Key) - .IsUnique(); - - // Indices y identidad. - modelBuilder.Entity() - .HasIndex(e => e.ApplicationUid) - .IsUnique(); - - // Indices y identidad. - modelBuilder.Entity() - .HasIndex(e => e.Email) - .IsUnique(); - - - modelBuilder.Entity() - .HasOne(p => p.Directory) - .WithMany() - .HasForeignKey(p => p.DirectoryId); - - - modelBuilder.Entity() - .HasOne(p => p.Directory) - .WithMany() - .HasForeignKey(p => p.DirectoryId); - - - modelBuilder.Entity() - .HasOne(p => p.Application) - .WithMany() - .HasForeignKey(p => p.ApplicationID) - .OnDelete(DeleteBehavior.NoAction); - - - modelBuilder.Entity() - .HasOne(p => p.Account) - .WithMany() - .HasForeignKey(p => p.AccountID); - - - modelBuilder.Entity() - .HasOne(dm => dm.Directory) - .WithMany(d => d.Members) - .HasForeignKey(dm => dm.DirectoryId) - .OnDelete(DeleteBehavior.Restrict); - - - modelBuilder.Entity() - .HasOne(p => p.Identity) - .WithMany(d => d.DirectoryMembers) - .HasForeignKey(p => p.IdentityId) - .OnDelete(DeleteBehavior.Restrict); - - - modelBuilder.Entity() - .HasOne(p => p.Directory) - .WithMany(d => d.Policies) - .HasForeignKey(p => p.DirectoryId) - .OnDelete(DeleteBehavior.Restrict); - - - modelBuilder.Entity() - .HasKey(t => new - { - t.IdentityId, - t.DirectoryId - }); - - - - // Nombre de la identidades. - modelBuilder.Entity().ToTable("IDENTITIES"); - modelBuilder.Entity().ToTable("ORGANIZATIONS"); - modelBuilder.Entity().ToTable("ACCOUNTS"); - modelBuilder.Entity().ToTable("APPLICATIONS"); - modelBuilder.Entity().ToTable("EMAILS"); - modelBuilder.Entity().ToTable("DIRECTORIES"); - modelBuilder.Entity().ToTable("DIRECTORY_MEMBERS"); - modelBuilder.Entity().ToTable("LOGS"); - modelBuilder.Entity().ToTable("POLICIES"); - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Identities.cs b/LIN.Identity/Data/Identities.cs deleted file mode 100644 index 0335c2a..0000000 --- a/LIN.Identity/Data/Identities.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace LIN.Identity.Data; - - -public class Identities -{ - - - #region Abstracciones - - - - - public static async Task> Read(int identity) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await Read(identity, context); - context.CloseActions(contextKey); - return res; - } - - - - - #endregion - - - - - public static async Task> Read(int id, Conexión context) - { - - // Ejecución - try - { - - var ids = await (from identity in context.DataBase.Identities - where identity.Id == id - select identity).FirstOrDefaultAsync(); - - - if (ids == null) - return new(Responses.NotRows); - - return new(Responses.Success, ids); - - } - catch - { - } - - return new(); - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Logins.cs b/LIN.Identity/Data/Logins.cs deleted file mode 100644 index 23f5258..0000000 --- a/LIN.Identity/Data/Logins.cs +++ /dev/null @@ -1,121 +0,0 @@ -namespace LIN.Identity.Data; - - -public class Logins -{ - - - - #region Abstracciones - - - /// - /// Crea un registro de Login - /// - /// Modelo del login - public static async Task Create(LoginLogModel data) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - var res = await Create(data, context); - context.CloseActions(connectionKey); - return res; - } - - - - /// - /// Obtiene la lista de registros login de una cuenta - /// - /// ID de la cuenta - public static async Task> ReadAll(int id) - { - - // Obtiene la conexión - var (context, connectionKey) = Conexión.GetOneConnection(); - - var res = await ReadAll(id, context); - context.CloseActions(connectionKey); - return res; - - } - - - #endregion - - - - /// - /// Crea un registro de Login - /// - /// Modelo del login - /// Contexto de conexión - public static async Task Create(LoginLogModel data, Conexión context) - { - // ID en 0 - data.ID = 0; - - // Ejecución - try - { - - // Ya existe la app. - context.DataBase.Attach(data.Application); - - var res = context.DataBase.LoginLogs.Add(data); - await context.DataBase.SaveChangesAsync(); - return new(Responses.Success, data.ID); - } - catch (Exception) - { - } - - return new(); - } - - - - /// - /// Obtiene la lista de registros de acceso de una cuenta - /// - /// ID de la cuenta - /// Contexto de conexión - public static async Task> ReadAll(int id, Conexión context) - { - - // Ejecución - try - { - - // Consulta. - var logins = from L in context.DataBase.LoginLogs - where L.AccountID == id - orderby L.Date descending - select new LoginLogModel - { - ID = L.ID, - Type = L.Type, - Date = L.Date, - Application = new() - { - ID = L.Application.ID, - Name = L.Application.Name, - Badge = L.Application.Badge - } - }; - - // Resultado. - var result = await logins.Take(50).ToListAsync(); - - return new(Responses.Success, result); - } - catch (Exception) - { - } - return new(); - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Mails.cs b/LIN.Identity/Data/Mails.cs deleted file mode 100644 index 10a6dab..0000000 --- a/LIN.Identity/Data/Mails.cs +++ /dev/null @@ -1,315 +0,0 @@ -namespace LIN.Identity.Data; - - -public class Mails -{ - - - #region Abstracciones - - - /// - /// Crea un nuevo email - /// - /// Modelo - public static async Task Create(EmailModel data) - { - var (context, contextKey) = Conexión.GetOneConnection(); - var response = await Create(data, context); - context.CloseActions(contextKey); - return response; - } - - - - /// - /// Obtiene la lista de emails asociados a una cuenta - /// - /// ID de la cuenta - public static async Task> ReadAll(int id) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await ReadAll(id, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Obtiene un email - /// - /// ID del email - public static async Task> Read(int id) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await Read(id, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Obtiene la lista de emails verificados asociados a una cuenta - /// - /// ID de la cuenta - public static async Task> ReadVerifiedEmails(int id) - { - var (context, contextKey) = Conexión.GetOneConnection(); - var res = await ReadVerifiedEmails(id, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Establece el email default de una cuenta - /// - /// ID de la cuenta - /// ID de el email - public static async Task SetDefaultEmail(int id, int emailId) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await SetDefaultEmail(id, emailId, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Actualiza el estado de un email - /// - /// ID del email - /// Nuevo estado - public static async Task UpdateState(int id, EmailStatus state) - { - var (context, contextKey) = Conexión.GetOneConnection(); - var res = await UpdateState(id, state, context); - context.CloseActions(contextKey); - return res; - } - - - #endregion - - - - /// - /// Crea un nuevo mail. - /// - /// Modelo - /// Contexto de conexión - public static async Task Create(EmailModel data, Conexión context) - { - - data.ID = 0; - - // Ejecución - try - { - - var res = await context.DataBase.Emails.AddAsync(data); - context.DataBase.SaveChanges(); - - return new(Responses.Success, data.ID); - } - catch (Exception ex) - { - if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) - return new(Responses.Undefined); - } - - return new(); - } - - - - /// - /// Obtiene la lista de emails asociados a una cuenta - /// - /// ID de la cuenta - /// Contexto de conexión - public static async Task> ReadAll(int id, Conexión context) - { - - // Ejecución - try - { - - // Query - var emails = await (from email in context.DataBase.Emails - where email.UserID == id - where email.Status != EmailStatus.Delete - select email).ToListAsync(); - - return new(Responses.Success, emails); - } - catch (Exception) - { - } - - return new(); - } - - - - /// - /// Obtiene un email - /// - /// ID de el email - /// Contexto de conexión - public static async Task> Read(int id, Conexión context) - { - - // Ejecución - try - { - - // Query - var email = await (from mail in context.DataBase.Emails - where mail.ID == id - select mail).FirstOrDefaultAsync(); - - // Email no existe - if (email == null) - return new(Responses.NotRows); - - return new(Responses.Success, email); - } - catch (Exception) - { - } - - return new(); - } - - - - /// - /// Obtiene la lista de emails verificados asociados a una cuenta - /// - /// ID de la cuenta - /// Contexto de conexión - public static async Task> ReadVerifiedEmails(int id, Conexión context) - { - - // Ejecución - try - { - - // Query - var emails = await (from E in context.DataBase.Emails - where E.UserID == id && E.Status == EmailStatus.Verified - select E).ToListAsync(); - - return new(Responses.Success, emails); - } - catch (Exception ex) - { - _ = Logger.Log(ex, 1); - } - - return new(); - } - - - - /// - /// Establece el email default de una cuenta - /// - /// ID de la cuenta - /// ID de el email - /// Contexto de conexión - public static async Task SetDefaultEmail(int id, int emailID, Conexión context) - { - - // Ejecución (Transacción) - using (var transaction = context.DataBase.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted)) - { - try - { - - // Consulta. - var mails = await (from E in context.DataBase.Emails - where E.UserID == id && E.Status == EmailStatus.Verified - where E.ID == id || E.IsDefault - select E).ToListAsync(); - - // Obtiene los emails default y los establece normal. - foreach (var item in mails.Where(t => t.IsDefault)) - item.IsDefault = false; - - // Elemento actual. - var element = mails.Where(T => T.ID == emailID && T.Status == EmailStatus.Verified).FirstOrDefault(); - - // No existe. - if (element == null) - { - transaction.Rollback(); - return new(Responses.NotRows); - } - - // Establece como default. - element.IsDefault = true; - - context.DataBase.SaveChanges(); - transaction.Commit(); - - return new(Responses.Success); - } - catch (Exception) - { - transaction.Rollback(); - } - } - - return new(); - } - - - - /// - /// Actualiza el estado de un email - /// - /// ID de el email - /// Nuevo estado - /// Contexto de conexión - public static async Task UpdateState(int id, EmailStatus state, Conexión context) - { - - // Ejecución - try - { - - // Query - var email = await (from E in context.DataBase.Emails - where E.ID == id - select E).FirstOrDefaultAsync(); - - // Email no existe - if (email == null) - { - return new(Responses.NotRows); - } - - email.Status = state; - context.DataBase.SaveChanges(); - - return new(Responses.Success); - } - catch (Exception) - { - } - - return new(); - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Policies.cs b/LIN.Identity/Data/Policies.cs deleted file mode 100644 index 89d500c..0000000 --- a/LIN.Identity/Data/Policies.cs +++ /dev/null @@ -1,188 +0,0 @@ -namespace LIN.Identity.Data; - - -public class Policies -{ - - - #region Abstracciones - - - - /// - /// Crear nueva política. - /// - /// Modelo. - public static async Task Create(PolicyModel data) - { - var (context, contextKey) = Conexión.GetOneConnection(); - var response = await Create(data, context); - context.CloseActions(contextKey); - return response; - } - - - - /// - /// Obtener las políticas asociadas a un directorio - /// - /// Id del directorio. - public static async Task> ReadAll(int directory) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await ReadAll(directory, context); - context.CloseActions(contextKey); - return res; - } - - - - /// - /// Valida el acceso a un permiso de una identidad a una política. - /// - /// ID de la identidad - /// ID de la política - public static async Task> ValidatePermission(int identity, int policy) - { - var (context, contextKey) = Conexión.GetOneConnection(); - - var res = await ValidatePermission(identity, policy, context); - context.CloseActions(contextKey); - return res; - } - - - - #endregion - - - - /// - /// Crear política. - /// - /// Modelo - /// Contexto de conexión. - public static async Task Create(PolicyModel data, Conexión context) - { - - // ID. - data.Id = 0; - - // Ejecución - try - { - - // Existe el directorio. - context.DataBase.Attach(data.Directory); - - // Guardar la información. - await context.DataBase.Policies.AddAsync(data); - - // Llevar info a la BD. - context.DataBase.SaveChanges(); - - return new(Responses.Success, data.Id); - } - catch (Exception ex) - { - - if ((ex.InnerException?.Message.Contains("Violation of UNIQUE KEY constraint") ?? false) || (ex.InnerException?.Message.Contains("duplicate key") ?? false)) - return new(Responses.Undefined); - - } - - return new(); - } - - - - /// - /// Obtiene las políticas asociadas a un directorio. - /// - /// ID del directorio - /// Contexto de conexión - public static async Task> ReadAll(int id, Conexión context) - { - - // Ejecución - try - { - - var policies = await (from policy in context.DataBase.Policies - where policy.DirectoryId == id - select new PolicyModel - { - Id = policy.Id, - Creation = policy.Creation, - DirectoryId = policy.DirectoryId, - Type = policy.Type, - ValueJson = policy.ValueJson - }).ToListAsync(); - - return new(Responses.Success, policies); - - } - catch - { - } - - return new(); - } - - - - /// - /// Valida el acceso a un permiso de una identidad. - /// - /// ID de la identidad - /// ID de la política - /// Contexto de conexión - public static async Task> ValidatePermission(int identity, int policyId, Conexión context) - { - - // Ejecución - try - { - - var (directories, _, _) = await Queries.Directories.Get(identity); - - - // Consulta. - var policy = await (from p in context.DataBase.Policies - where p.Type == PolicyTypes.Permission - where p.Id == policyId - select p).FirstOrDefaultAsync(); - - // Si no se encontró la política. - if (policy == null) - return new() - { - Response = Responses.NotRows, - Model = false, - Message = "Sin definición." - }; - - - // No tiene acceso. - if (!directories.Contains(policy.DirectoryId)) - return new() - { - Response = Responses.PoliciesNotComplied, - Model = false, - Message = $$"""No tienes permiso para el recurso {{{policyId}}}.""" - }; - - - return new(Responses.Success, true); - - } - catch - { - } - - return new(); - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Queries/Directories.cs b/LIN.Identity/Data/Queries/Directories.cs deleted file mode 100644 index 98a58bf..0000000 --- a/LIN.Identity/Data/Queries/Directories.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace LIN.Identity.Data.Queries; - - -public class Directories -{ - - - /// - /// Obtiene las identidades y directorios. - /// - /// Identidad base - public static async Task<(List directories, List identities, List roles)> Get(int identity) - { - List identities = [identity]; - List directories = []; - List roles = []; - - var (context, contextKey) = Conexión.GetOneConnection(); - - await Get(identity, context, identities, directories, roles); - - context.CloseActions(contextKey); - return (directories, identities, roles); - } - - - - /// - /// Obtiene las identidades y directorios. - /// - /// Identidad base - /// Contexto - /// Lista de identidades. - /// Directorios - /// Roles - private static async Task Get(int identityBase, Conexión context, List identities, List directories, List roles) - { - // Consulta. - var query = from member in context.DataBase.DirectoryMembers - where member.IdentityId == identityBase - where member.Rol != Roles.Guest - && member.Rol != Roles.RoyalGuest - && !identities.Contains(member.Directory.IdentityId) - select new - { - Identity = member.Directory.Identity.Id, - Directory = member.Directory.ID, - Roles = member.Rol - }; - - // Si hay elementos. - if (query.Any()) - { - var local = query.ToList(); - identities.AddRange(local.Select(t => t.Identity)); - directories.AddRange(local.Select(t => t.Directory)); - roles.AddRange(local.Select(t => t.Roles)); - - foreach (var id in local) - await Get(id.Identity, context, identities, directories, roles); - } - - } - - - -} \ No newline at end of file diff --git a/LIN.Identity/Data/Queries/Identities.cs b/LIN.Identity/Data/Queries/Identities.cs deleted file mode 100644 index 0a7b15a..0000000 --- a/LIN.Identity/Data/Queries/Identities.cs +++ /dev/null @@ -1,306 +0,0 @@ -namespace LIN.Identity.Data.Queries; - - -public class Identities -{ - - - - /// - /// Consulta sobre todas lac cuentas. - /// - /// Contexto DB - public static IQueryable GetAccounts(Conexión context) - { - // Query general - IQueryable accounts = from account in context.DataBase.Accounts - select account; - - // Retorno - return accounts; - - } - - - - /// - /// Consulta sobre las cuentas validas. - /// - /// Contexto DB - public static IQueryable GetValidAccounts(Conexión context) - { - - // Query general - IQueryable accounts = from account in GetAccounts(context) - where account.Estado == AccountStatus.Normal - select account; - - // Retorno - return accounts; - - } - - - - - - - - - - public static IQueryable GetAccounts(int id, Models.Account filters, Conexión context) - { - - // Query general - IQueryable accounts; - - if (filters.FindOn == Models.FindOn.StableAccounts) - accounts = from account in GetValidAccounts(context) - where account.ID == id - select account; - else - accounts = from account in GetAccounts(context) - where account.ID == id - select account; - - // Armar el modelo - accounts = BuildModel(accounts, filters); - - // Retorno - return accounts; - - } - - - public static IQueryable GetAccountsByIdentity(int id, Models.Account filters, Conexión context) - { - - // Query general - IQueryable accounts; - - if (filters.FindOn == Models.FindOn.StableAccounts) - accounts = from account in GetValidAccounts(context) - where account.Identity.Id == id - select account; - else - accounts = from account in GetAccounts(context) - where account.Identity.Id == id - select account; - - // Armar el modelo - accounts = BuildModel(accounts, filters); - - // Retorno - return accounts; - - } - - - public static IQueryable GetAccounts(string user, Models.Account filters, Conexión context) - { - - // Query general - IQueryable accounts; - - if (filters.FindOn == Models.FindOn.StableAccounts) - accounts = from account in GetValidAccounts(context) - where account.Identity.Unique == user - select account; - else - accounts = from account in GetAccounts(context) - where account.Identity.Unique == user - select account; - - // Armar el modelo - accounts = BuildModel(accounts, filters); - - // Retorno - return accounts; - - } - - - public static IQueryable GetAccounts(IEnumerable ids, Models.Account filters, Conexión context) - { - - // Query general - IQueryable accounts = from account in GetValidAccounts(context) - where ids.Contains(account.ID) - select account; - - // Armar el modelo - accounts = BuildModel(accounts, filters); - - // Retorno - return accounts; - - } - - - public static IQueryable Search(string pattern, Models.Account filters, Conexión context) - { - - // Query general. - IQueryable accounts = from account in GetValidAccounts(context) - where account.Identity.Unique.Contains(pattern) - select account; - - // Armar el modelo. - accounts = BuildModel(accounts, filters); - - // Retorno - return accounts; - - } - - - - - public static IQueryable GetDirectory(int id, int identityContext, Conexión context) - { - - // Query general. - IQueryable accounts; - - - var directory = from dm in context.DataBase.DirectoryMembers - where dm.Directory.ID == id - select dm; - - if (identityContext > 0) - directory = directory.Where(t => t.IdentityId == identityContext); - - // Armar el modelo - accounts = BuildModel(directory); - - // Retorno - return accounts; - - } - - - public static IQueryable GetDirectoryByIdentity(int id, Conexión context) - { - - // Query general - IQueryable accounts; - - - var directory = from dm in context.DataBase.DirectoryMembers - where dm.Directory.Identity.Id == id - select dm; - - // Armar el modelo - accounts = BuildModel(directory); - - // Retorno - return accounts; - - } - - - - - - - - - /// - /// Construir la consulta - /// - /// Query base - /// Filtros - private static IQueryable BuildModel(IQueryable query, Models.Account filters) - { - - byte[] profile = - { - }; - try - { - profile = File.ReadAllBytes("wwwroot/user.png"); - } - catch { } - - var queryFinal = from account in query - select new AccountModel - { - ID = account.ID, - Rol = account.Rol, - IdentityId = account.Identity.Id, - Insignia = account.Insignia, - Estado = account.Estado, - Contraseña = filters.SensibleInfo ? account.Contraseña : "", - Visibilidad = account.Visibilidad, - Identity = new() - { - Id = account.Identity.Id, - Unique = account.Identity.Unique, - Type = account.Identity.Type, - }, - - // Nombre. - Nombre = account.Visibilidad == AccountVisibility.Visible - || account.ID == filters.ContextAccount - || filters.IsAdmin - ? account.Nombre - : "Usuario privado", - - // Cumpleaños. - Birthday = account.Visibilidad == AccountVisibility.Visible - || account.ID == filters.ContextAccount - || filters.IsAdmin - ? account.Birthday - : default, - - // Creación. - Creación = account.Visibilidad == AccountVisibility.Visible - || account.ID == filters.ContextAccount - || filters.IsAdmin - ? account.Creación - : default, - - // Perfil. - Perfil = account.Visibilidad == AccountVisibility.Visible - || account.ID == filters.ContextAccount - || filters.IsAdmin - ? account.Perfil - : profile, - - }; - - return queryFinal; - - } - - - - /// - /// Construir la consulta - /// - /// Query base - private static IQueryable BuildModel(IQueryable query) - { - - return from d in query - select new DirectoryMember - { - IdentityId = d.IdentityId, - Directory = new() - { - Nombre = d.Directory.Nombre, - Creación = d.Directory.Creación, - ID = d.Directory.ID, - Identity = new() - { - Id = d.Directory.Identity.Id, - Type = d.Directory.Identity.Type, - Unique = d.Directory.Identity.Unique - }, - IdentityId = d.Directory.Identity.Id - } - }; - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Hubs/PasskeyHub.cs b/LIN.Identity/Hubs/PasskeyHub.cs deleted file mode 100644 index 33947db..0000000 --- a/LIN.Identity/Hubs/PasskeyHub.cs +++ /dev/null @@ -1,300 +0,0 @@ -using LIN.Identity.Data.Areas.Organizations; - -namespace LIN.Identity.Hubs; - - -public class PassKeyHub : Hub -{ - - - /// - /// Lista de intentos Passkey. - /// - public readonly static Dictionary> Attempts = []; - - - - /// - /// Nuevo intento passkey. - /// - /// Intento passkey - public async Task JoinIntent(PassKeyModel attempt) - { - - // Aplicación - var application = await Data.Applications.Read(attempt.Application.Key); - - // Si la app no existe o no esta activa - if (application.Response != Responses.Success) - return; - - // Preparar el modelo - attempt.Application ??= new(); - attempt.Application.Name = application.Model.Name; - attempt.Application.Badge = application.Model.Badge; - attempt.Application.Key = application.Model.Key; - attempt.Application.ID = application.Model.ID; - - // Vencimiento - var expiración = DateTime.Now.AddMinutes(2); - - // Caducidad el modelo - attempt.HubKey = Context.ConnectionId; - attempt.Status = PassKeyStatus.Undefined; - attempt.Hora = DateTime.Now; - attempt.Expiración = expiración; - - // Agrega el modelo - if (!Attempts.ContainsKey(attempt.User.ToLower())) - Attempts.Add(attempt.User.ToLower(), [attempt]); - else - Attempts[attempt.User.ToLower()].Add(attempt); - - // Yo - await Groups.AddToGroupAsync(Context.ConnectionId, $"dbo.{Context.ConnectionId}"); - - await SendRequest(attempt); - - } - - - - - - - - - - - - - - - - - - - - - - - - - public override Task OnDisconnectedAsync(Exception? exception) - { - - var e = Attempts.Values.Where(T => T.Where(T => T.HubKey == Context.ConnectionId).Any()).FirstOrDefault() ?? new(); - - - _ = e.Where(T => - { - if (T.HubKey == Context.ConnectionId && T.Status == PassKeyStatus.Undefined) - T.Status = PassKeyStatus.Failed; - - return false; - }); - - - return base.OnDisconnectedAsync(exception); - } - - - /// - /// Un dispositivo envía el PassKey intent - /// - public async Task JoinAdmin(string usuario) - { - - // Grupo de la cuenta - await Groups.AddToGroupAsync(Context.ConnectionId, usuario.ToLower()); - - } - - - - - - - - //=========== Dispositivos ===========// - - - /// - /// Envía la solicitud a los admins - /// - public async Task SendRequest(PassKeyModel modelo) - { - - var pass = new PassKeyModel() - { - Expiración = modelo.Expiración, - Hora = modelo.Hora, - Status = modelo.Status, - User = modelo.User, - HubKey = modelo.HubKey, - Application = new() - { - Name = modelo.Application.Name, - Badge = modelo.Application.Badge - } - }; - - await Clients.Group(modelo.User.ToLower()).SendAsync("newintent", pass); - } - - - - - /// - /// Recibe una respuesta de passkey - /// - public async Task ReceiveRequest(PassKeyModel modelo) - { - try - { - - // Validación del token recibido. - var tokenInfo = Jwt.Validate(modelo.Token); - - // No es valido el token - if (!tokenInfo.IsAuthenticated || modelo.Status != PassKeyStatus.Success) - { - // Modelo de falla - PassKeyModel badPass = new() - { - Status = modelo.Status, - User = modelo.User - }; - - // comunica la respuesta - await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", badPass); - return; - } - - // Obtiene el attempt - List attempts = Attempts[modelo.User.ToLower()].Where(A => A.HubKey == modelo.HubKey).ToList(); - - // Elemento - var attempt = attempts.Where(A => A.HubKey == modelo.HubKey).FirstOrDefault(); - - // Validación del intento - if (attempt == null) - return; - - // Eliminar el attempt de la lista - attempts.Remove(attempt); - - // Cambiar el estado del intento - attempt.Status = modelo.Status; - - // Si el tiempo de expiración ya paso - if (DateTime.Now > modelo.Expiración) - { - attempt.Status = PassKeyStatus.Expired; - attempt.Token = string.Empty; - } - - // Validación de la organización - if (tokenInfo.OrganizationId > 0) - { - // Obtiene la organización - var organization = await Organizations.Read(tokenInfo.OrganizationId); - - // Si tiene lista blanca - //if (organization.Model.HaveWhiteList) - //{ - // //// Validación de la app - // //var applicationOnOrg = await Data.Organizations.Applications.AppOnOrg(attempt.Application.Key, orgID); - - // //// Si la app no existe o no esta activa - // //if (applicationOnOrg.Response != Responses.Success) - // //{ - // // // Modelo de falla - // // PassKeyModel badPass = new() - // // { - // // Status = PassKeyStatus.BlockedByOrg, - // // User = modelo.User - // // }; - - // // // comunica la respuesta - // // await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", badPass); - // // return; - - // //} - //} - - - } - - // Aplicación - var app = await Data.Applications.Read(attempt.Application.Key); - - // Si la app no existe - if (app.Response != Responses.Success) - { - // Modelo de falla - PassKeyModel badPass = new() - { - Status = PassKeyStatus.Failed, - User = modelo.User - }; - - // comunica la respuesta - await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", badPass); - return; - } - - // Guarda el acceso. - LoginLogModel loginLog = new() - { - AccountID = tokenInfo.AccountId, - Application = new() - { - ID = app.Model.ID - }, - Date = DateTime.Now, - Type = LoginTypes.Passkey, - ID = 0 - }; - - _ = Data.Logins.Create(loginLog); - - - // Nuevo token - var newToken = Jwt.Generate(new() - { - ID = tokenInfo.AccountId, - Identity = new() - { - Unique = tokenInfo.Unique - } - }, app.Model.ID); - - // nuevo pass - var pass = new PassKeyModel() - { - Expiración = modelo.Expiración, - Status = modelo.Status, - User = modelo.User, - Token = newToken, - Hora = modelo.Hora, - Application = new(), - HubKey = "", - Key = "" - }; - - // Respuesta al cliente - await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", pass); - - } - catch - { - } - - } - - - - - -} \ No newline at end of file diff --git a/LIN.Identity/LIN.Identity.csproj b/LIN.Identity/LIN.Identity.csproj deleted file mode 100644 index 42928b2..0000000 --- a/LIN.Identity/LIN.Identity.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - - net8.0 - enable - enable - c0e756b3-bd08-49b8-93e6-c3c6ce24d1fb - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - diff --git a/LIN.Identity/Models/Account.cs b/LIN.Identity/Models/Account.cs deleted file mode 100644 index d685d29..0000000 --- a/LIN.Identity/Models/Account.cs +++ /dev/null @@ -1,66 +0,0 @@ -namespace LIN.Identity.Models; - - -public class Account -{ - - public int ContextAccount { get; set; } - public bool IsAdmin { get; set; } - public bool SensibleInfo { get; set; } - public IncludeOrg IncludeOrg { get; set; } = IncludeOrg.None; - public FindOn FindOn { get; set; } = FindOn.AllAccount; - public IncludeOrgLevel OrgLevel { get; set; } = IncludeOrgLevel.Basic; - -} - - - -public enum IncludeOrg -{ - - /// - /// No incluir la organización - /// - None, - - /// - /// Incluir la organización - /// - Include, - - /// - /// Incluir la organización según el contexto - /// - IncludeIf - -} - -public enum IncludeOrgLevel -{ - - /// - /// Incluir el rol - /// - Basic, - - /// - /// Incluir la organización y su nombre - /// - Advance - -} - -public enum FindOn -{ - - /// - /// En todas las cuentas - /// - AllAccount, - - /// - /// En las cuentas estables - /// - StableAccounts - -} \ No newline at end of file diff --git a/LIN.Identity/Program.cs b/LIN.Identity/Program.cs deleted file mode 100644 index 09c9f4f..0000000 --- a/LIN.Identity/Program.cs +++ /dev/null @@ -1,91 +0,0 @@ -using LIN.Identity.Data; - - -try -{ - - // Nombre de la app en Error Logger. - Logger.AppName = "LIN.IDENTITY.V3"; - - var builder = WebApplication.CreateBuilder(args); - - - builder.Services.AddIP(); - - // Add services to the container. - builder.Services.AddSignalR(); - - builder.Services.AddCors(options => - { - options.AddPolicy("AllowAnyOrigin", - builder => - { - builder.AllowAnyOrigin() - .AllowAnyHeader() - .AllowAnyMethod(); - }); - }); - - - - var sqlConnection = builder.Configuration["ConnectionStrings:local"] ?? string.Empty; - - - // Servicio de BD - builder.Services.AddDbContext(options => - { - options.UseSqlServer(sqlConnection); - }); - - - - builder.Services.AddControllers(); - builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); - - var app = builder.Build(); - - try - { - // Si la base de datos no existe - using var scope = app.Services.CreateScope(); - var dataContext = scope.ServiceProvider.GetRequiredService(); - var res = dataContext.Database.EnsureCreated(); - } - catch (Exception ex) - { - _ = LIN.Access.Logger.Logger.Log(ex, 3); - } - - - app.UseIP(); - app.UseCors("AllowAnyOrigin"); - - app.MapHub("/realTime/auth/passkey"); - - app.UseSwagger(); - app.UseSwaggerUI(); - - Conexión.SetStringConnection(sqlConnection); - - app.UseStaticFiles(); - app.UseHttpsRedirection(); - Jwt.Open(); - EmailWorker.StarService(); - - - - app.MapGet("/", () => "Hello World!"); - - app.UseAuthorization(); - - app.MapControllers(); - - app.Run(); - -} -catch (Exception ex) -{ - Logger.AppName = "LIN.Identity.V3"; - await Logger.Log(ex, 3); -} diff --git a/LIN.Identity/Services/EmailWorker.cs b/LIN.Identity/Services/EmailWorker.cs deleted file mode 100644 index 4864056..0000000 --- a/LIN.Identity/Services/EmailWorker.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System.Diagnostics; - -namespace LIN.Identity.Services; - - -public class EmailWorker -{ - - - /// - /// Email de salida - /// - private static string Password { get; set; } = string.Empty; - - - - /// - /// Inicia el servicio - /// - public static void StarService() - { - Password = Configuration.GetConfiguration("resend:key"); - } - - - - /// - /// Enviar un correo - /// - /// Destinatario - /// url - /// Info del mail - public static async Task SendVerification(string to, string url, string mail) - { - - // Obtiene la plantilla - var body = File.ReadAllText("wwwroot/Plantillas/Plantilla.html"); - - // Remplaza - body = body.Replace("@@Titulo", "Verificación de correo electrónico"); - body = body.Replace("@@Subtitulo", $"{mail}"); - body = body.Replace("@@Url", url); - body = body.Replace("@@Mensaje", "Hemos recibido tu solicitud para agregar una dirección de correo electrónico adicional a tu cuenta. Para completar este proceso, da click en el siguiente botón"); - body = body.Replace("@@ButtonMessage", "Verificar"); - - // Envía el email - return await SendMail(to, "Verifica el email", body); - - } - - - - /// - /// Enviar un correo - /// - /// Destinatario - /// Nombre del usuario - /// URL - public static async Task SendPassword(string to, string nombre, string url) - { - - // Obtiene la plantilla - var body = File.ReadAllText("wwwroot/Plantillas/Plantilla.html"); - - // Remplaza - body = body.Replace("@@Titulo", "Reestablecer contraseña"); - body = body.Replace("@@Subtitulo", $"Hola, {nombre}"); - body = body.Replace("@@Url", url); - body = body.Replace("@@Mensaje", "Recibimos tu solicitud para restablecer la contraseña de tu cuenta LIN. Para completar este proceso, simplemente haz clic en el siguiente botón"); - body = body.Replace("@@ButtonMessage", "Cambiar contraseña"); - - // Envía el email - return await SendMail(to, "Cambiar contraseña", body); - - } - - - - /// - /// Enviar un correo - /// - /// Destinatario - /// Asunto - /// Cuerpo del correo - public static async Task SendMail(string to, string asunto, string body) - { - try - { - - using var client = new HttpClient(); - var url = "https://api.resend.com/emails"; - var accessToken = Password; // Reemplaza con tu token de acceso - - var requestData = new - { - from = "onboarding@resend.dev", - to = new[] - { - to - }, - subject = asunto - }; - - - var json = System.Text.Json.JsonSerializer.Serialize(requestData); - - using var content = new StringContent(json, Encoding.UTF8, "application/json"); - client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken); - - var response = await client.PostAsync(url, content); - - if (response.IsSuccessStatusCode) - { - return true; - } - else - { - _ = Logger.Log("Error al enviar un correo: ", response.StatusCode.ToString(), 3); - } - return true; - } - catch (Exception ex) - { - _ = Logger.Log(ex, 2); - } - return false; - } - - - - - - - - - -} \ No newline at end of file diff --git a/LIN.Identity/Services/Iam/Applications.cs b/LIN.Identity/Services/Iam/Applications.cs deleted file mode 100644 index aceebca..0000000 --- a/LIN.Identity/Services/Iam/Applications.cs +++ /dev/null @@ -1,100 +0,0 @@ -using LIN.Identity.Data.Queries; - -namespace LIN.Identity.Services.Iam; - - -public static class Applications -{ - - - /// - /// Validar acceso a un recurso de aplicación. - /// - /// ID de la cuenta - /// ID de la aplicación - public static async Task> ValidateAccess(int account, int app) - { - - // Obtiene el recurso. - var resource = await Data.Applications.Read(app); - - // Si no existe el recurso. - if (resource == null) - return new() - { - Message = "No se encontró el recurso.", - Response = Responses.NotRows, - Model = IamLevels.NotAccess - }; - - // App publica. - if (resource.Model.DirectoryId <= 0) - return new() - { - Response = Responses.Success, - Model = IamLevels.Visualizer - }; - - - - - - var (context, contextKey) = Conexión.GetOneConnection(); - - - var identity = (from a in context.DataBase.Accounts - where a.ID == account - select a.IdentityId).FirstOrDefault(); - - - var (directories, _,_) = await Directories.Get(identity); - - - // Tiene acceso. - if (directories.Contains(resource.Model.DirectoryId)) - { - return new() - { - Model = IamLevels.Visualizer, - Response = Responses.Success - }; - } - - return new() - { - Model = IamLevels.NotAccess, - Response = Responses.Success - }; - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -} \ No newline at end of file diff --git a/LIN.Identity/Services/Image.cs b/LIN.Identity/Services/Image.cs deleted file mode 100644 index 257539f..0000000 --- a/LIN.Identity/Services/Image.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Drawing; -using System.Drawing.Imaging; -using System.Runtime.Versioning; - -namespace LIN.Identity.Services; - - -public class Image -{ - - - - /// - /// Comprime una imagen - /// - /// Original image - /// Ancho - /// Alto - /// Maximo - public static byte[] Zip(byte[] originalImage, int width = 50, int height = 50, int max = 1900) - { - if (OperatingSystem.IsWindows()) - return ZipOnWindows(originalImage, width, height, max); - else - return ZipOthers(originalImage, width, height, max); - } - - - - /// - /// Comprime una imagen (En Windows) - /// - /// Original image - /// Ancho - /// Alto - /// Maximo - [SupportedOSPlatform("windows")] - public static byte[] ZipOnWindows(byte[] originalImage, int width = 50, int height = 50, int max = 1900) - { - try - { - - // Si la imagen ya esta comprimida o pesa muy poco - if (originalImage.Length <= max) - return originalImage; - - // Cargar la imagen a memoria - MemoryStream memoryStream = new(originalImage); - - // Crear la imagen - var image = System.Drawing.Image.FromStream(memoryStream); - - // Imagen redimensionada - Bitmap nuevaImagen = new(width, height); - - // Crea un objeto Graphics para dibujar la imagen original en el Bitmap redimensionado - using (var graphics = Graphics.FromImage(nuevaImagen)) - { - // Dibuja la imagen original en el nuevo Bitmap con las dimensiones deseadas - graphics.DrawImage(image, 0, 0, 50, 50); - } - - - byte[] imagenBytes; - using (MemoryStream stream = new()) - { - nuevaImagen.Save(stream, ImageFormat.Jpeg); - imagenBytes = stream.ToArray(); - } - - nuevaImagen.Dispose(); - image.Dispose(); - - return imagenBytes; - - } - catch - { - } - return []; - } - - - - /// - /// Comprime una imagen (Plataformas .NET) - /// - /// Original image - /// Ancho - /// Alto - public static byte[] ZipOthers(byte[] originalImage, int width = 100, int height = 100, int max = 1900) - { - try - { - - MemoryStream memoryStream = new(originalImage); - - // Cargar imagen - using Aspose.Imaging.Image pic = Aspose.Imaging.Image.Load(memoryStream); - - // Cambiar el tamaño de la imagen y guardar la imagen redimensionada - - pic.ResizeWidthProportionally(100); - - byte[] imagenBytes; - using MemoryStream stream = new(); - pic.Save(stream); - imagenBytes = stream.ToArray(); - - return imagenBytes; - - } - catch - { - } - return []; - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Services/Login/LoginNormal.cs b/LIN.Identity/Services/Login/LoginNormal.cs deleted file mode 100644 index 29c03fc..0000000 --- a/LIN.Identity/Services/Login/LoginNormal.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace LIN.Identity.Services.Login; - - -/// -/// Nuevo login -/// -/// Datos de la cuenta -/// Llave -/// Contraseña -/// Tipo de inicio -public class LoginNormal(AccountModel? account, string? application, string password, LoginTypes loginType = LoginTypes.Credentials) : LoginService(account, application, password, loginType) -{ - - - - /// - /// Iniciar sesión - /// - public override async Task Login() - { - - // Validar credenciales y estado - var validateAccount = Validate(); - - // Retorna el error - if (validateAccount.Response != Responses.Success) - return validateAccount; - - - // Valida la aplicación - var validateApp = await ValidateApp(); - - // Retorna el error - if (validateApp.Response != Responses.Success) - return validateApp; - - - // Genera el login - GenerateLogin(); - - return new(Responses.Success); - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Services/Login/LoginService.cs b/LIN.Identity/Services/Login/LoginService.cs deleted file mode 100644 index b33575a..0000000 --- a/LIN.Identity/Services/Login/LoginService.cs +++ /dev/null @@ -1,164 +0,0 @@ -namespace LIN.Identity.Services.Login; - - -public abstract class LoginService -{ - - - /// - /// Llave de la aplicación - /// - private protected string ApplicationKey { get; set; } - - - - /// - /// Tipo del login - /// - private protected LoginTypes LoginType { get; set; } - - - - /// - /// Contraseña - /// - private protected string Password { get; set; } - - - - /// - /// Modelo de la aplicación obtenida - /// - private protected ApplicationModel Application { get; set; } - - - - /// - /// Datos de la cuenta - /// - private protected AccountModel Account { get; set; } - - - - /// - /// Nuevo login - /// - /// Datos de la cuenta - /// Llave - /// Contraseña - /// Tipo de inicio - protected LoginService(AccountModel? account, string? application, string? password, LoginTypes loginType = LoginTypes.Credentials) - { - ApplicationKey = application ?? string.Empty; - Account = account ?? new(); - Application = new(); - Password = password ?? string.Empty; - LoginType = loginType; - } - - - - - /// - /// Valida los datos obtenidos de la cuenta - /// - public ResponseBase Validate() - { - - // Si la cuenta no esta activa - if (Account.Estado != AccountStatus.Normal) - return new() - { - Response = Responses.NotExistAccount, - Message = "Esta cuenta fue eliminada o desactivada." - }; - - - // Valida la contraseña - var ee = EncryptClass.Encrypt(Password); - if (Account.Contraseña != ee) - return new() - { - Response = Responses.InvalidPassword, - Message = "La contraseña es incorrecta." - }; - - // Correcto - return new(Responses.Success); - - } - - - - - /// - /// Valida los datos de la aplicación - /// - public async Task ValidateApp() - { - // Obtiene la App. - //var app = await Data.Applications.Read(ApplicationKey); - - //// Verifica si la app existe. - //if (app.Response != Responses.Success) - // return new ReadOneResponse - // { - // Message = "La aplicación no esta autorizada para iniciar sesión en LIN Identity", - // Response = Responses.Unauthorized - // }; - - //// Si es una app privada. - //if (!app.Model.AllowAnyAccount) - //{ - // var allow = await Data.Applications.IsAllow(app.Model.ID, Account.ID, Conexión.GetOneConnection().context); - - // if (allow.Response != Responses.Success) - // return new ReadOneResponse - // { - // Message = $"No tienes permiso para acceder a la aplicación '{app.Model.Name}'", - // Response = Responses.Unauthorized - // }; - //} - - - // Establece la aplicación - // Application = app.Model; - - // Correcto - return new(Responses.Success); - - } - - - - /// - /// Genera el login - /// - public async void GenerateLogin() - { - - - //var app = await Data.Applications.Read(ApplicationKey); - - //// Crea registro del login - //_ = Data.Logins.Create(new() - //{ - // Date = DateTime.Now, - // AccountID = Account.ID, - // Type = LoginType, - // Application = new() - // { - // ID = app.Model.ID - // } - //}); - } - - - - /// - /// Iniciar sesión - /// - public abstract Task Login(); - - -} \ No newline at end of file diff --git a/LIN.Identity/Usings.cs b/LIN.Identity/Usings.cs deleted file mode 100644 index 455a5f9..0000000 --- a/LIN.Identity/Usings.cs +++ /dev/null @@ -1,19 +0,0 @@ -global using Http.ResponsesList; -global using LIN.Identity; -global using LIN.Identity.Hubs; -global using LIN.Identity.Services; -global using LIN.Modules; -global using LIN.Types.Identity.Enumerations; -global using LIN.Types.Identity.Models; -global using LIN.Types.Enumerations; -global using LIN.Types.Responses; -global using Microsoft.AspNetCore.Mvc; -global using Microsoft.AspNetCore.SignalR; -global using Microsoft.EntityFrameworkCore; -global using Microsoft.IdentityModel.Tokens; -global using System.Text; -global using LIN.Access.Logger; -global using LIN.Identity.Validations; - -global using LIN.Identity.Models; -global using LIN.Identity.Services.Filters; \ No newline at end of file diff --git a/LIN.Identity/Validations/Account.cs b/LIN.Identity/Validations/Account.cs deleted file mode 100644 index c85338b..0000000 --- a/LIN.Identity/Validations/Account.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace LIN.Identity.Validations; - - -public class Account -{ - - - /// - /// Procesa la información de un Account - /// - /// Modelo - public static AccountModel Process(AccountModel modelo) - { - - var model = new AccountModel - { - ID = 0, - Nombre = modelo.Nombre, - Identity = new() - { - Id = 0, - Type = IdentityTypes.Account, - Unique = modelo.Identity.Unique, - DirectoryMembers = [] - }, - IdentityId = 0, - Gender = modelo.Gender, - Visibilidad = modelo.Visibilidad, - Contraseña = modelo.Contraseña = EncryptClass.Encrypt(modelo.Contraseña), - Creación = modelo.Creación = DateTime.Now, - Estado = modelo.Estado = AccountStatus.Normal, - Birthday = modelo.Birthday, - Insignia = modelo.Insignia = AccountBadges.None, - Rol = modelo.Rol = AccountRoles.User, - Perfil = modelo.Perfil = modelo.Perfil?.Length == 0 - ? File.ReadAllBytes("wwwroot/profile.png") - : modelo.Perfil - }; - - model.Perfil = Image.Zip(model.Perfil ?? []); - - return model; - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Validations/AccountPassword.cs b/LIN.Identity/Validations/AccountPassword.cs deleted file mode 100644 index 07dcb76..0000000 --- a/LIN.Identity/Validations/AccountPassword.cs +++ /dev/null @@ -1,68 +0,0 @@ -using LIN.Identity.Data.Queries; - -namespace LIN.Identity.Validations; - - -public class AccountPassword -{ - - - /// - /// Validar la contraseña con las políticas de directorio. - /// - /// Id de la cuenta. - /// Contraseña a validar. - public static async Task ValidatePassword(int identity, string password) - { - - // Contexto. - var (context, contextKey) = Conexión.GetOneConnection(); - - // Directorios. - var (directories, _, _) = await Directories.Get(identity); - - // Política. - var policy = await (from p in context.DataBase.Policies - where p.Type == PolicyTypes.PasswordLength - where directories.Contains(p.DirectoryId) - orderby p.Creation - select new PolicyModel - { - Id = p.Id, - ValueJson = p.ValueJson, - Type = p.Type - }).LastOrDefaultAsync(); - - - // Política no existe. - if (policy == null) - return true; - - try - { - - // Valor. - dynamic? policyValue = Newtonsoft.Json.JsonConvert.DeserializeObject(policy.ValueJson); - - // Convertir el valor. - var can = int.TryParse(((policyValue?.length) as object)?.ToString(), out int length); - - // No se pudo pasear. - if (!can) - _ = Logger.Log($"Hubo un error al obtener el valor de Policy con id {policy.Id}", policy.ValueJson ?? "NULL", 3); - - // Validar. - if (password.Length < length) - return false; - - } - catch (Exception) - { - } - - return true; - - } - - -} \ No newline at end of file diff --git a/LIN.Identity/Validations/Roles.cs b/LIN.Identity/Validations/Roles.cs deleted file mode 100644 index 0cd4f46..0000000 --- a/LIN.Identity/Validations/Roles.cs +++ /dev/null @@ -1,284 +0,0 @@ -namespace LIN.Identity.Validations; - -public static class Roles -{ - - /// - /// Confirmar si un rol tiene permisos de ver el directorio. - /// - /// Rol a confirmar. - public static bool View(Types.Identity.Enumerations.Roles rol) - { - - Types.Identity.Enumerations.Roles[] roles = - [ - Types.Identity.Enumerations.Roles.System, - Types.Identity.Enumerations.Roles.SuperManager, - Types.Identity.Enumerations.Roles.Manager, - Types.Identity.Enumerations.Roles.Operator, - Types.Identity.Enumerations.Roles.AccountsOperator, - Types.Identity.Enumerations.Roles.Regular, - Types.Identity.Enumerations.Roles.Guest, - Types.Identity.Enumerations.Roles.RoyalGuest - ]; - - return roles.Contains(rol); - - } - - - /// - /// Confirmar si un rol tiene permisos de ver los integrantes. - /// - /// Rol a confirmar. - public static bool ViewMembers(Types.Identity.Enumerations.Roles rol) - { - - Types.Identity.Enumerations.Roles[] roles = - [ - Types.Identity.Enumerations.Roles.System, - Types.Identity.Enumerations.Roles.SuperManager, - Types.Identity.Enumerations.Roles.Manager, - Types.Identity.Enumerations.Roles.Operator, - Types.Identity.Enumerations.Roles.AccountsOperator, - Types.Identity.Enumerations.Roles.Regular, - Types.Identity.Enumerations.Roles.Guest, - Types.Identity.Enumerations.Roles.RoyalGuest - ]; - - return roles.Contains(rol); - - } - - - /// - /// Confirmar si un rol tiene permisos para alterar los integrantes. - /// - /// Rol a confirmar. - public static bool AlterMembers(Types.Identity.Enumerations.Roles rol) - { - - Types.Identity.Enumerations.Roles[] roles = - [ - Types.Identity.Enumerations.Roles.System, - Types.Identity.Enumerations.Roles.SuperManager, - Types.Identity.Enumerations.Roles.Manager, - Types.Identity.Enumerations.Roles.Operator, - Types.Identity.Enumerations.Roles.AccountsOperator, - Types.Identity.Enumerations.Roles.RoyalGuest - ]; - - return roles.Contains(rol); - - } - - - /// - /// Confirmar si un rol tiene permisos de saltarse las directivas. - /// - /// Rol a confirmar. - public static bool UsePolicy(Types.Identity.Enumerations.Roles rol) - { - - Types.Identity.Enumerations.Roles[] roles = - [ - Types.Identity.Enumerations.Roles.System, - Types.Identity.Enumerations.Roles.Guest, - Types.Identity.Enumerations.Roles.RoyalGuest - ]; - - return roles.Contains(rol); - - } - - - /// - /// Confirmar si un rol tiene permisos de crear directivas. - /// - /// Rol a confirmar. - public static bool CreatePolicy(Types.Identity.Enumerations.Roles rol) - { - - Types.Identity.Enumerations.Roles[] roles = - [ - Types.Identity.Enumerations.Roles.System, - Types.Identity.Enumerations.Roles.SuperManager, - Types.Identity.Enumerations.Roles.Manager, - Types.Identity.Enumerations.Roles.Operator, - Types.Identity.Enumerations.Roles.RoyalGuest - ]; - - return roles.Contains(rol); - - } - - - - /// - /// Confirmar si un rol tiene permisos de alterar datos como nombres de la organización etc... - /// - /// Rol a confirmar. - public static bool DataAlter(Types.Identity.Enumerations.Roles rol) - { - - Types.Identity.Enumerations.Roles[] roles = - [ - Types.Identity.Enumerations.Roles.System, - Types.Identity.Enumerations.Roles.SuperManager, - Types.Identity.Enumerations.Roles.Manager, - ]; - - return roles.Contains(rol); - - } - - - /// - /// Confirmar si un rol tiene permisos de saltarse las directivas. - /// - /// Rol a confirmar. - public static bool ViewPolicy(Types.Identity.Enumerations.Roles rol) - { - - Types.Identity.Enumerations.Roles[] roles = - [ - Types.Identity.Enumerations.Roles.System, - Types.Identity.Enumerations.Roles.SuperManager, - Types.Identity.Enumerations.Roles.Manager, - Types.Identity.Enumerations.Roles.Operator, - Types.Identity.Enumerations.Roles.AccountsOperator, - Types.Identity.Enumerations.Roles.Regular, - Types.Identity.Enumerations.Roles.RoyalGuest - ]; - - return roles.Contains(rol); - - } - - - - - - - - /// - /// Confirmar si un rol tiene permisos de ver el directorio. - /// - /// Rol a confirmar. - public static bool View(IEnumerable roles) - { - - // Recorrer roles. - foreach(var rol in roles) - if (View(rol)) - return true; - - // No tiene permisos. - return false; - } - - - - /// - /// Confirmar si un rol tiene permisos de ver los integrantes. - /// - /// Rol a confirmar. - public static bool ViewMembers(IEnumerable roles) - { - - // Recorrer roles. - foreach (var rol in roles) - if (ViewMembers(rol)) - return true; - - // No tiene permisos. - return false; - } - - - - /// - /// Confirmar si un rol tiene permisos para alterar los integrantes. - /// - /// Rol a confirmar. - public static bool AlterMembers(IEnumerable roles) - { - // Recorrer roles. - foreach (var rol in roles) - if (AlterMembers(rol)) - return true; - - // No tiene permisos. - return false; - - } - - - /// - /// Confirmar si un rol tiene permisos de saltarse las directivas. - /// - /// Rol a confirmar. - public static bool UsePolicy(IEnumerable roles) - { - - // Recorrer roles. - foreach (var rol in roles) - if (UsePolicy(rol)) - return true; - - // No tiene permisos. - return false; - - } - - - /// - /// Confirmar si un rol tiene permisos de crear directivas. - /// - /// Rol a confirmar. - public static bool CreatePolicy(IEnumerable roles) - { - - // Recorrer roles. - foreach (var rol in roles) - if (CreatePolicy(rol)) - return true; - - // No tiene permisos. - return false; - - } - - - - /// - /// Confirmar si un rol tiene permisos de alterar datos como nombres de la organización etc... - /// - /// Rol a confirmar. - public static bool DataAlter(IEnumerable roles) - { - // Recorrer roles. - foreach (var rol in roles) - if (DataAlter(rol)) - return true; - - // No tiene permisos. - return false; - - } - - - - public static bool ViewPolicy(IEnumerable roles) - { - // Recorrer roles. - foreach (var rol in roles) - if (ViewPolicy(rol)) - return true; - - // No tiene permisos. - return false; - - } - -} \ No newline at end of file diff --git a/LIN.Identity/wwwroot/Plantillas/Email.html b/LIN.Identity/wwwroot/Plantillas/Email.html deleted file mode 100644 index 94366e9..0000000 --- a/LIN.Identity/wwwroot/Plantillas/Email.html +++ /dev/null @@ -1,151 +0,0 @@ -
-
- -
- -
- - - - - - -
-
- - - - - - -
- - - - - - -
- - - - -
-
-
-
-
- -
-
- - - - - - -
-
- - - - - - - - - - -
-
- -

- Hola @Nombre,

-

- Estas intentanto agregar este correo a tu cuenta LIN. -

- -
-
- - - - - - -
- Confirmar -
-
-
-
-
-
- - -
- - - - - - -
-
- - - - - - - - - -
-
- Enviado por LIN Services • - Pagina - principal - -
-
-
- Medellin, Colombia -
-
-
-
-
-
- -
-
\ No newline at end of file diff --git a/LIN.Identity/wwwroot/Plantillas/Password.html b/LIN.Identity/wwwroot/Plantillas/Password.html deleted file mode 100644 index bc2e999..0000000 --- a/LIN.Identity/wwwroot/Plantillas/Password.html +++ /dev/null @@ -1,153 +0,0 @@ -
-
- -
- -
- - - - - - -
-
- - - - - - -
- - - - - - -
- - - - -
-
-
-
-
- -
-
- - - - - - -
-
- - - - - - - - - - -
-
- -

- Hola @Nombre,

-

- Puede restablecer su contraseña de LIN haciendo clic en - el botón de abajo. Si no has solicitado una nueva - contraseña, ignora este correo electrónico. -

- -
-
- - - - - - -
- Cambiar la contraseña -
-
-
-
-
-
- - -
- - - - - - -
-
- - - - - - - - - -
-
- Enviado por LIN Services • - Pagina - principal - -
-
-
- Medellin, Colombia -
-
-
-
-
-
- -
-
\ No newline at end of file diff --git a/LIN.Identity/wwwroot/Plantillas/Plantilla.html b/LIN.Identity/wwwroot/Plantillas/Plantilla.html deleted file mode 100644 index 19d54cb..0000000 --- a/LIN.Identity/wwwroot/Plantillas/Plantilla.html +++ /dev/null @@ -1,68 +0,0 @@ -
-
-
- - - - - - - - - - -
- - - - - - - - - -
-
-
-
@@Titulo
- - - - @@Subtitulo - - - - - -
-
-
- @@Mensaje - -
-
- También puedes ver toda la informacion de tu cuenta en
- - Tu Cuenta -
-
- -
-
-
-
-
\ No newline at end of file diff --git a/LIN.Identity/wwwroot/profile.png b/LIN.Identity/wwwroot/profile.png deleted file mode 100644 index 50f642e190263c2617a1d99a7fe2118414fb1204..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4178 zcmYk8dpMNq*T>iMJaZm%Fmev#n6pu06y_mm)Fe8nHffZy>!%VaI&d3`%DbCnw`q5n z+UzX~{VFAPp3X?6HlTcHGvw#1qayi3yzqcwnl-sx*^LyMTzx}uk zOL()MmaqFVE#|guWAu@G+kaj0qU`Rr5oP&_)7#c9=h`0^9K-UwhDOFl->$Bxd_DBY z7KTRJ4BHf`(@)G4DAO117?vfc_-`|&>beJ43VYr0aQ{EoG1fwy&$PF`T`6z$Q2)LQ zy=7Z)(%beu`}Kb?J>Ty$RSUh~#Vem&;Ar;1tIe($cIq9S3|txa>8ao=RJWlTHm z39BnKAvguDb*EFj%-$bpaC4McL-ED7eFfFTW$VG{4xUCV3PHuk#(&Xo=PE-&_Ox+A z_k8PSsi~p!A|;_d_|Rzsq127-q{;h6p0d=fL&^H$bFbrV$m?S#{JB!d+mddH+v&z! z&!$R|NRJ5KS6Tj;8U^Bxc95G0Q$?QkdZaZ$X5;F7bz>l`T?pAGut+owqXFBDhECf72C(-? zz6EK`B`KcRHYu6jaE~6oX;x1k?pO)FSD?v-tZ+wfIqGt*<_g}^k2_X?Z!PRwfu6f$ zev3rWwe(Lbu%uM1z>F5a4D7bEn=?{|Zhuu^QXOqefJe~%C}j>W|Ep2^#;l(0?7lL*Ckub(MaDR4 zW)nR-*?ldLb`l+SCO`awhMmcDM@4^6BF9)IOXaTc9SxU}L?(AsMIif8QsNR*Zh&wG>*X?$~xIMkee0 z$u!$5l2^B)vaIsy*?VMe0hpwUKi<9eMrP{g9#SKG$l~O#EPLjO#7ifB;&vIN-+vh{%s1sO zi~KYGU$jw5V}0@%_aUq z{a`mp5J4F29!&k02#(O!*g`gQ_$*>@Vp~`ci``w{lJL-h)S69NN-(SNSOe62l}?=@ zpMv;eA|`fG`vA0R?j=Ih8Lp@mH!*^9cq{>23%G!PhN&}+C;T;TsP#UbJVVBCQ9a{q zD)g(D^MP{ElDr(~> zpO88pdcn~?p`74@u(X{yiO6PWa?@c!p@~?*avi}EJUz)m;j)iJd+aJCwiYf$%gO(N z#7syO?8Q{sln7ID{{arBWbVa+vf(CvdA?YRhqrISo;wIFu)?SRfF)KnW&<_+$+(iA z$?;kUif~A+Bwk@?v5J{gt4s%_^EIMog+vPB@u@THD4Q$ihpdF+VsSEG z1qwAROO}k`;%?`g85W~FQo+-@<0dfA4qn=Xr{ls!gmAW2k6b7S^0F4SWx@Ph3({3c zDTFZmNAP{1`CHM_!us@3Eh|Qe4aP8e`0 zfJ%8fkPPi2{20RHSRE$L@J9f_8{n9a))+O%!z)wD6d|AHu8JG=6qfL~Nc zBs3~GT;Go%A(w6#$O6$~LIU1N>yZ?q^uhgA>xA5MnZZbLyZ_g$*$Gr|7)$9*Hr3vV zHAl6=p^V$owS@PoAnh(fm(f>JD5P#`3Kkr^wt;kYppoV%DKK3?+W<{;%#!sC&2#b> z(v>#1i9nFg6=Y+mkcttad8X4@Or_0DTnX}Ax)u_GFMt?v_1$3TPDBCQHQXF9D#N1v zU@1>KZvL@c0tRgBTpD#2%fnw{icl4!x^RxE78G`lz(z|6F`dL*-#PgMxI}t^yZLj2Nu=uQM}zVxZHL`5@NWVDxN7(a% zRo#APkIpafvE%cDTyTiosv*Escd2i`@<4iAi78yMX?JS|xfUFZBGH3dwJChMah)b^ ziQj*|?WDgc5w6Wxl3`AnL*G~W5l&ar3_?dJ*K46S0AvSOu&n? zXVl}K=(=`OiSEwj&jXj4)>pS&G!M+;Z+)=VYL3JR`OrFd4-O`bM$j|^7LmVp*fIp~ zc8}z!v#xHB+9lZdVwbS&c z?v5|ZT|->1=YZZ7?fVy_U0u3c)&jx1*HOvhzs+Fp$@BFW>gKNhqD$z#h=*E09rtC$ zk{G8EonB{ll@YYl2Vz=Rx{SeEVH-|PD9r2hQTPRzf23xC)VS`i?H%p>ON=x(P;+m; z`+3-Wik0z?0zVujUekq*>m;;^d5yR)dE+!Ts7f(%ea`U5O3giUkjOktHaY+P)IamX z^(i3gsJ1G}DrUoP+}q{*Qlu$DZ(Lg*By%243vSgI!0D8?Id#9nHm?(X@$mk---F}vp>-V_9)Q(4jhD9HI0anwazYudPV&TTnMi>jN_D z2y)ft@8`Tu6?4&Hw&tsqRc4g|dYp{g9$h(fzFHLqGaRbUgcQ9W6rRB}vAA)Yd?@)c zy%ikK2oM$_bOQO=2?!~4fd)GhO@;np4s2(19a!Hw;_FKL7pB;4g7)xeb76ZCwLSrA zV>zXSy1f&&bdaYQ8nrD-x5=G^6(`YJ7SL*JP_P{TWE6OcLq-sr*EIUQanV?k&Ki7O z>&a+S(%}v!>y7cDnM3J1Djwj8&wcc;9`#Wb%TUlwH}YeMa2?1?+{g}2j~N^(r*22W*Q@rL ziN(>0=-426gsVfFs9`(Ti{Ld;s0UiHiGBf>Z^&>^v@w$B!(K1C*Hh&wu138kbH2C9 zI-^`w1r0_C8CjU!1Z;>|Qi<(@ z-}8e8y;!ykD01aW`PcI?Do_*_d~{qpebEc91{WV%gXPTE#9@Q+^utpjVTUUcp~Z*k zW9x8V1Jt?3u_C)U=p4<7kk3xj{=)4|fPkgig&ef>Gd-N9ZaN*9Q5JrmrCm*rTnPQG z#-t(X+(^ixCGqsZYTUpVju#k{rlj-l%=E3-j+#=HhRd3;swFXy0!2rKsrxgJT96CR z^g#P;whWySdD#P*8L$RM#xC-pX3oxyW%^_{N*L|ch zwH_iEGiH%Nldd|*;q>Q#%Xd#geunfFGJ}LK*LJBG${N9UE?+!Sd++u5N)=O?Y}Ma} zrC@d$DJ6IxUUf;Y&S5RIJFBi5lIGNSdF{66*_mQJYFS|Gn2u%?v7KOQ?H}31)ox8p zJZAH5=+0*SlD+w^)5i0BuQmViBF$Bv`kv;Y#B_DEO!|nFio9usXuFiiUn!r)0W-RcC=$6@ju& z+&db}hM^!GKMJ+#ewHTD^9jjuR`(Rr6FMTRviwmAg;_%XkyyeCQvcmJ&hY^69S`a0 z;L12P%@0$0KN`CQ?J1;lhA&i5hTowOPa2tr5B6p<0qI` zjW`9*l^jptx;8V%mau)bBzrw%ca|OXVi>PrBc^d5o{ibJ!MFGcFO_6D=DJOW&j;am zT-(xV9mW-_pOFH%5;vDfSN9okH-5!0cpvK<_|q}&EN-hKEya-_Zfh89hx+J$q{b%e zFKQUqLqXcBzCLt@4et`tgVl=bdSgHGGm4h1smmD@*GL)B#Z!YA3`?DC z-idovH?RFLBpqe`E_hi_572KCc_+H~N;;C>6s^Msd|tADx{dQPZjxH-Jka-e0yp9k zoFNseRZ_ipQYyol|6_3f6P8H*Vs2Omc_#5d*fyfg$LT`p>!^b(rQTtgq76HUFYz`$ eluGx>kADF7Fa?64W*OuF0000 Date: Wed, 13 Mar 2024 16:07:20 -0500 Subject: [PATCH 069/178] Mejoras --- .../Areas/Authentication/AuthenticationController.cs | 4 ++++ LIN.Cloud.Identity/Data/Builders/Account.cs | 4 +++- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 4 ++-- LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index b642761..1c1c330 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -107,4 +107,8 @@ public async Task> LoginWithToken([FromHeader] } + + + + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Builders/Account.cs b/LIN.Cloud.Identity/Data/Builders/Account.cs index 3fd9177..be55542 100644 --- a/LIN.Cloud.Identity/Data/Builders/Account.cs +++ b/LIN.Cloud.Identity/Data/Builders/Account.cs @@ -186,6 +186,8 @@ where ids.Contains(account.Id) } + private static readonly byte[] selector = []; + @@ -236,7 +238,7 @@ private static IQueryable BuildModel(IQueryable quer }, Password = account.Password, Visibility = account.Visibility, - Profile = profile, + Profile = filters.IncludePhoto ? profile : selector, IdentityId = account.Identity.Id, IdentityService = account.IdentityService }; diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index ca94db6..28773b2 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,8 +9,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs b/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs index 74a7a8e..407d249 100644 --- a/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs +++ b/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs @@ -7,6 +7,7 @@ public class QueryAccountFilter public int IdentityContext { get; set; } public List OrganizationsDirectories { get; set; } = []; public bool IsAdmin { get; set; } + public bool IncludePhoto { get; set; } = true; public FindOn FindOn { get; set; } } From c5327f7aa7a88e403f43e6f5b9a47eecf7484bd9 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 15 Mar 2024 17:30:57 -0500 Subject: [PATCH 070/178] =?UTF-8?q?Peque=C3=B1os=20cambios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Areas/Authentication/AuthenticationController.cs | 3 --- LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 1c1c330..11fc99c 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -108,7 +108,4 @@ public async Task> LoginWithToken([FromHeader] - - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 6996e23..59943cf 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -23,7 +23,7 @@ public HttpReadAllResponse GetAll([FromHeader] string token) // Cuenta var account = (from a in PassKeyHub.Attempts - where a.Key == tokenInfo.Unique.ToLower() + where a.Key.Equals(tokenInfo.Unique, StringComparison.CurrentCultureIgnoreCase) select a).FirstOrDefault().Value ?? new(); // Hora actual From 9cc20eaa365f2373afcdc6743dc85414bc1498b0 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 29 Mar 2024 17:32:18 -0500 Subject: [PATCH 071/178] Mejoras --- .gitignore | 3 +- LIN.Cloud.Identity/Data/Accounts.cs | 282 +----------------- LIN.Cloud.Identity/Data/Accounts.cs.cs | 274 +++++++++++++++++ LIN.Cloud.Identity/Program.cs | 5 - .../Services/Models/JwtModel.cs | 6 - LIN.Cloud.Identity/Usings.cs | 5 - .../appsettings.Development.json | 10 +- LIN.Cloud.Identity/appsettings.json | 16 - 8 files changed, 289 insertions(+), 312 deletions(-) create mode 100644 LIN.Cloud.Identity/Data/Accounts.cs.cs delete mode 100644 LIN.Cloud.Identity/appsettings.json diff --git a/.gitignore b/.gitignore index cea3bae..c8810ec 100644 --- a/.gitignore +++ b/.gitignore @@ -402,4 +402,5 @@ FodyWeavers.xsd *.msp # JetBrains Rider -*.sln.iml \ No newline at end of file +*.sln.iml +/LIN.Cloud.Identity/appsettings.json diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index 4b7fddc..b769a42 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -1,19 +1,15 @@ namespace LIN.Cloud.Identity.Data; -public static class Accounts +public partial class Accounts { - - #region Abstracciones - - - /// - /// Crear nueva cuenta. [Transacción] + /// Crear nueva cuenta. /// /// Modelo de la cuenta. + /// Id de la organización. public static async Task> Create(AccountModel modelo, int organization) { @@ -101,7 +97,6 @@ public static async Task> ReadByIdentity(int id, S /// /// patron de búsqueda /// Filtros - /// Contexto de conexión public static async Task> Search(string pattern, QueryAccountFilter filters) { // Obtener conexión. @@ -121,7 +116,7 @@ public static async Task> Search(string pattern, Q /// Obtiene los usuarios con IDs coincidentes /// /// Lista de IDs - /// Contexto de base de datos + /// Filtros. public static async Task> FindAll(List ids, QueryAccountFilter filters) { // Obtener conexión. @@ -136,273 +131,4 @@ public static async Task> FindAll(List ids, Q } - - #endregion - - - - /// - /// Crear nueva cuenta. [Transacción] - /// - /// Modelo de la cuenta. - /// Contexto de conexión. - public static async Task> Create(AccountModel modelo, DataContext context, int organization = 0) - { - - // Pre. - modelo.Id = 0; - modelo.IdentityId = 0; - - // Transacción. - using var transaction = context.Database.BeginTransaction(); - - try - { - - // Guardar la cuenta. - await context.Accounts.AddAsync(modelo); - context.SaveChanges(); - - - if (organization > 0) - { - - var generalGroup = (from org in context.Organizations - where org.Id == organization - select org.DirectoryId).FirstOrDefault(); - - if (generalGroup <= 0) - { - throw new Exception("Organización no encontrada."); - } - - var x = new GroupMember() - { - Group = new() - { - Id = generalGroup - }, - Identity = modelo.Identity, - Type = GroupMemberTypes.User - }; - - context.Attach(x.Group); - - context.GroupMembers.Add(x); - - context.SaveChanges(); - - } - - - // Confirmar los cambios. - transaction.Commit(); - - return new() - { - Response = Responses.Success, - Model = modelo - }; - - } - catch (Exception) - { - transaction.Rollback(); - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Obtener una cuenta según el Id. - /// - /// Id de la cuenta. - /// Filtros de búsqueda. - /// Contexto de base de datos. - public static async Task> Read(int id, QueryAccountFilter filters, DataContext context) - { - - try - { - - // Consulta de las cuentas. - var account = await Builders.Account.GetAccounts(id, filters, context).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Obtener una cuenta según el identificador único. - /// - /// Único. - /// Filtros de búsqueda. - /// Contexto de base de datos. - public static async Task> Read(string unique, QueryAccountFilter filters, DataContext context) - { - - try - { - - // Consulta de las cuentas. - var account = await Builders.Account.GetAccounts(unique, filters, context).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Obtener una cuenta según el id de la identidad. - /// - /// Id de la identidad. - /// Filtros de búsqueda. - /// Contexto de base de datos. - public static async Task> ReadByIdentity(int id, QueryAccountFilter filters, DataContext context) - { - - try - { - - // Consulta de las cuentas. - var account = await Builders.Account.GetAccountsByIdentity(id, filters, context).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Buscar por patron. - /// - /// patron de búsqueda - /// Filtros - /// Contexto de conexión - public static async Task> Search(string pattern, QueryAccountFilter filters, DataContext context) - { - - // Ejecución - try - { - - List accountModels = await Builders.Account.Search(pattern, filters, context).Take(10).ToListAsync(); - - // Si no existe el modelo - if (accountModels == null) - return new(Responses.NotRows); - - return new(Responses.Success, accountModels); - } - catch - { - } - - return new(); - } - - - - /// - /// Obtiene los usuarios con IDs coincidentes - /// - /// Lista de IDs - /// Contexto de base de datos - public static async Task> FindAll(List ids, QueryAccountFilter filters, DataContext context) - { - - // Ejecución - try - { - - var query = Builders.Account.FindAll(ids, filters, context); - - // Ejecuta - var result = await query.ToListAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotRows); - - return new(Responses.Success, result); - } - catch - { - } - - return new(); - } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Accounts.cs.cs b/LIN.Cloud.Identity/Data/Accounts.cs.cs new file mode 100644 index 0000000..801563b --- /dev/null +++ b/LIN.Cloud.Identity/Data/Accounts.cs.cs @@ -0,0 +1,274 @@ +namespace LIN.Cloud.Identity.Data; + + +public partial class Accounts +{ + + + + /// + /// Crear nueva cuenta. [Transacción] + /// + /// Modelo de la cuenta. + /// Contexto de conexión. + public static async Task> Create(AccountModel modelo, DataContext context, int organization = 0) + { + + // Pre. + modelo.Id = 0; + modelo.IdentityId = 0; + + // Transacción. + using var transaction = context.Database.BeginTransaction(); + + try + { + + // Guardar la cuenta. + await context.Accounts.AddAsync(modelo); + context.SaveChanges(); + + // Si la organización existe. + if (organization > 0) + { + + var generalGroup = (from org in context.Organizations + where org.Id == organization + select org.DirectoryId).FirstOrDefault(); + + if (generalGroup <= 0) + { + throw new Exception("Organización no encontrada."); + } + + var x = new GroupMember() + { + Group = new() + { + Id = generalGroup + }, + Identity = modelo.Identity, + Type = GroupMemberTypes.User + }; + + context.Attach(x.Group); + + context.GroupMembers.Add(x); + + context.SaveChanges(); + + } + + + // Confirmar los cambios. + transaction.Commit(); + + return new() + { + Response = Responses.Success, + Model = modelo + }; + + } + catch (Exception) + { + transaction.Rollback(); + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener una cuenta según el Id. + /// + /// Id de la cuenta. + /// Filtros de búsqueda. + /// Contexto de base de datos. + public static async Task> Read(int id, QueryAccountFilter filters, DataContext context) + { + + try + { + + // Consulta de las cuentas. + var account = await Builders.Account.GetAccounts(id, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener una cuenta según el identificador único. + /// + /// Único. + /// Filtros de búsqueda. + /// Contexto de base de datos. + public static async Task> Read(string unique, QueryAccountFilter filters, DataContext context) + { + + try + { + + // Consulta de las cuentas. + var account = await Builders.Account.GetAccounts(unique, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Obtener una cuenta según el id de la identidad. + /// + /// Id de la identidad. + /// Filtros de búsqueda. + /// Contexto de base de datos. + public static async Task> ReadByIdentity(int id, QueryAccountFilter filters, DataContext context) + { + + try + { + + // Consulta de las cuentas. + var account = await Builders.Account.GetAccountsByIdentity(id, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + /// + /// Buscar por patron. + /// + /// patron de búsqueda + /// Filtros + /// Contexto de conexión + public static async Task> Search(string pattern, QueryAccountFilter filters, DataContext context) + { + + // Ejecución + try + { + + List accountModels = await Builders.Account.Search(pattern, filters, context).Take(10).ToListAsync(); + + // Si no existe el modelo + if (accountModels == null) + return new(Responses.NotRows); + + return new(Responses.Success, accountModels); + } + catch + { + } + + return new(); + } + + + + /// + /// Obtiene los usuarios con IDs coincidentes + /// + /// Lista de IDs + /// Contexto de base de datos + public static async Task> FindAll(List ids, QueryAccountFilter filters, DataContext context) + { + + // Ejecución + try + { + + var query = Builders.Account.FindAll(ids, filters, context); + + // Ejecuta + var result = await query.ToListAsync(); + + // Si no existe el modelo + if (result == null) + return new(Responses.NotRows); + + return new(Responses.Success, result); + } + catch + { + } + + return new(); + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index d542722..1d9428b 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -43,11 +43,6 @@ app.UseCors("AllowAnyOrigin"); - - - - - // Swagger. app.UseSwagger(); app.UseSwaggerUI(); diff --git a/LIN.Cloud.Identity/Services/Models/JwtModel.cs b/LIN.Cloud.Identity/Services/Models/JwtModel.cs index 8104c6f..73f553d 100644 --- a/LIN.Cloud.Identity/Services/Models/JwtModel.cs +++ b/LIN.Cloud.Identity/Services/Models/JwtModel.cs @@ -28,12 +28,6 @@ public class JwtModel public int IdentityId { get; set; } - /// - /// Id de la organización. - /// - public int OrganizationId { get; set; } - - /// /// Id de la aplicación. /// diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 2491e13..06d7da1 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -6,9 +6,6 @@ global using Microsoft.AspNetCore.SignalR; global using LIN.Cloud.Identity.Services.Iam; - - - // Framework. global using System.Text; global using System.IO; @@ -38,5 +35,3 @@ // Módulos. global using LIN.Modules; - - diff --git a/LIN.Cloud.Identity/appsettings.Development.json b/LIN.Cloud.Identity/appsettings.Development.json index 0c208ae..b8cdc30 100644 --- a/LIN.Cloud.Identity/appsettings.Development.json +++ b/LIN.Cloud.Identity/appsettings.Development.json @@ -4,5 +4,13 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "AllowedHosts": "*", + "jwt": { + "key": "drfghjiwuytr567uijnbvgfder56y7uiolkjhgfdertyui2345fgh" + }, + "ConnectionStrings": { + "local": "", + "cloud": "" } -} +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/appsettings.json b/LIN.Cloud.Identity/appsettings.json deleted file mode 100644 index f8c1a57..0000000 --- a/LIN.Cloud.Identity/appsettings.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "jwt": { - "key": "drfghjiwuytr567uijnbvgfder56y7uiolkjhgfdertyui2345fgh" - }, - "ConnectionStrings": { - "local": "Data Source=(local);Initial Catalog=Identity;Integrated Security=True;TrustServerCertificate=True", - "cloud": "workstation id=linidentitydb.mssql.somee.com;packet size=4096;user id=linauth_SQLLogin_2;pwd=p4b691lkwx;data source=linidentitydb.mssql.somee.com;persist security info=False;initial catalog=linidentitydb;TrustServerCertificate=True; Encrypt=false;" - } -} \ No newline at end of file From 101ffcdf4fce71e35215efbe399c1cfa68b3be9e Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 2 Apr 2024 17:20:09 -0500 Subject: [PATCH 072/178] Mejoras --- LIN.Cloud.Identity.sln | 6 - .../Areas/Authentication/IntentsController.cs | 33 +++++ LIN.Cloud.Identity/Data/PassKeys.cs | 134 ++++++++++++++++++ LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 6 +- .../Services/Auth/Authentication.cs | 3 +- .../Services/Database/DataContext.cs | 23 ++- .../Services/Database/DataService.cs | 5 +- .../Services/Formats/Account.cs | 2 +- .../Services/Models/PassKeyDBModel.cs | 12 ++ .../Services/Realtime/PassKeyHub.cs | 8 ++ .../Services/Realtime/PassKeyHubActions.cs | 7 +- LIN.Cloud.Identity/Usings.cs | 3 - 12 files changed, 220 insertions(+), 22 deletions(-) create mode 100644 LIN.Cloud.Identity/Data/PassKeys.cs create mode 100644 LIN.Cloud.Identity/Services/Models/PassKeyDBModel.cs diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index a41ac76..6c7dc9e 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity", "LIN.C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types.Cloud.Identity", "..\..\Tipos\LIN.Types.Cloud.Identity\LIN.Types.Cloud.Identity.csproj", "{EA3955F9-EF0C-48E4-A428-02739EF322E1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Modules", "..\..\Tipos\LIN.Modules\LIN.Modules.csproj", "{B784D380-1A0F-40C2-9F31-E1A7641DD3B5}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http", "..\..\Tipos\Http\Http.csproj", "{26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types", "..\..\Tipos\LIN.Types\LIN.Types.csproj", "{E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}" @@ -27,10 +25,6 @@ Global {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Debug|Any CPU.Build.0 = Debug|Any CPU {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Release|Any CPU.ActiveCfg = Release|Any CPU {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Release|Any CPU.Build.0 = Release|Any CPU - {B784D380-1A0F-40C2-9F31-E1A7641DD3B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B784D380-1A0F-40C2-9F31-E1A7641DD3B5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B784D380-1A0F-40C2-9F31-E1A7641DD3B5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B784D380-1A0F-40C2-9F31-E1A7641DD3B5}.Release|Any CPU.Build.0 = Release|Any CPU {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Debug|Any CPU.Build.0 = Debug|Any CPU {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 59943cf..24decb1 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -7,6 +7,7 @@ namespace LIN.Cloud.Identity.Areas.Authentication; public class IntentsController : ControllerBase { + /// /// Obtiene la lista de intentos de llaves de paso están activos. /// @@ -49,4 +50,36 @@ where I.Expiración > timeNow + + /// + /// Obtiene la lista de intentos de llaves de paso están activos. + /// + /// Token de acceso + [HttpGet("count")] + [IdentityToken] + public async Task> Count([FromHeader] string token) + { + try + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + + var x = await Data.PassKeys.Count(tokenInfo.AccountId); + + // Retorna + return new(Responses.Success, x.Model); + } + catch + { + return new(Responses.Undefined) + { + Message = "Hubo un error al obtener los intentos de passkey" + }; + } + } + + + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/PassKeys.cs b/LIN.Cloud.Identity/Data/PassKeys.cs new file mode 100644 index 0000000..c603303 --- /dev/null +++ b/LIN.Cloud.Identity/Data/PassKeys.cs @@ -0,0 +1,134 @@ +namespace LIN.Cloud.Identity.Data; + + +public static class PassKeys +{ + + + + #region Abstracciones + + + + /// + /// Crear nueva identidad. + /// + /// Modelo. + public static async Task Create(PassKeyDBModel modelo) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Create(modelo, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + /// + /// Obtener una identidad según el Id. + /// + /// Id. + /// Filtros de búsqueda. + public static async Task> Count(int id) + { + + // Obtener conexión. + var (context, contextKey) = DataService.GetConnection(); + + // Función. + var response = await Count(id, context); + + // Retornar. + context.Close(contextKey); + return response; + + } + + + + + + #endregion + + + + + public static async Task Create(PassKeyDBModel modelo, DataContext context) + { + // Pre. + modelo.Id = 0; + + try + { + + // Guardar la identidad. + await context.PassKeys.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + + + + public static async Task> Count(int id, DataContext context) + { + + try + { + + + var time = DateTime.Now; + + + var c = await (from a in context.PassKeys + where a.AccountId == id + where a.Time.Year == time.Year + && a.Time.Month == time.Month + && a.Time.Day == time.Day + select a).CountAsync(); + + + + // Success. + return new() + { + Response = Responses.Success, + Model = c + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.NotRows + }; + } + + } + + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 28773b2..754940d 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,8 +9,9 @@ - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -23,7 +24,6 @@ - diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 1e5b8be..b6255a2 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -109,9 +109,8 @@ private bool ValidatePassword() if (Account == null) return false; - var ss = EncryptClass.Encrypt(Password); // Validar la contraseña. - if (EncryptClass.Encrypt(Password) != Account.Password) + if (Global.Utilities.Cryptography.Encrypt(Password) != Account.Password) return false; // Correcto. diff --git a/LIN.Cloud.Identity/Services/Database/DataContext.cs b/LIN.Cloud.Identity/Services/Database/DataContext.cs index f300e95..2d47de0 100644 --- a/LIN.Cloud.Identity/Services/Database/DataContext.cs +++ b/LIN.Cloud.Identity/Services/Database/DataContext.cs @@ -23,7 +23,6 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet Organizations { get; set; } - /// /// Grupos. /// @@ -42,6 +41,12 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet IdentityRoles { get; set; } + /// + /// PassKeys. + /// + public DbSet PassKeys { get; set; } + + @@ -97,9 +102,22 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } + + // Modelo: PassKey. + { + + modelBuilder.Entity() + .HasOne(t => t.Account) + .WithMany() + .HasForeignKey(y => y.AccountId) + .OnDelete(DeleteBehavior.NoAction); + } + + + // Modelo: GroupModel. { - + modelBuilder.Entity() .HasOne(t => t.Identity) .WithMany() @@ -170,6 +188,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("IDENTITY_ROLES"); modelBuilder.Entity().ToTable("GROUPS_MEMBERS"); modelBuilder.Entity().ToTable("ORGANIZATIONS"); + modelBuilder.Entity().ToTable("PASSKEYS"); // Base. base.OnModelCreating(modelBuilder); diff --git a/LIN.Cloud.Identity/Services/Database/DataService.cs b/LIN.Cloud.Identity/Services/Database/DataService.cs index 0205783..6f15c7f 100644 --- a/LIN.Cloud.Identity/Services/Database/DataService.cs +++ b/LIN.Cloud.Identity/Services/Database/DataService.cs @@ -53,17 +53,16 @@ public static (DataBase context, string contextKey) GetConnection() lock (cache) { cache.SetOnUse(); - string key = KeyGen.Generate(10, "db."); + string key = Global.Utilities.KeyGenerator. Generate(10, "db."); cache.Key = key; return (cache, key); } } - // Generar estancia forzosa. var newDB = new DataBase() { - Key = KeyGen.Generate(10, "db.") + Key = Global.Utilities.KeyGenerator.Generate(10, "db.") }; newDB.SetOnUse(); diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 90d85b3..7f14f58 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -58,7 +58,7 @@ public static AccountModel Process(AccountModel baseAccount) Creation = DateTime.Now, Name = baseAccount.Name.Trim(), Profile = baseAccount.Profile, - Password = EncryptClass.Encrypt(baseAccount.Password), + Password = Global.Utilities.Cryptography.Encrypt(baseAccount.Password), Visibility = baseAccount.Visibility, IdentityId = 0, Identity = new() diff --git a/LIN.Cloud.Identity/Services/Models/PassKeyDBModel.cs b/LIN.Cloud.Identity/Services/Models/PassKeyDBModel.cs new file mode 100644 index 0000000..f369f21 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Models/PassKeyDBModel.cs @@ -0,0 +1,12 @@ +namespace LIN.Cloud.Identity.Services.Models; + + +public class PassKeyDBModel +{ + + public int Id { get; set; } + public DateTime Time { get; set; } + public int AccountId { get; set; } + public AccountModel Account { get; set; } = null!; + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 028e1e4..d7b5635 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -195,6 +195,14 @@ public async Task ReceiveRequest(PassKeyModel modelo) //_ = Data.Logins.Create(loginLog); + + _ = Data.PassKeys.Create(new PassKeyDBModel + { + AccountId = info.AccountId, + Id = 0, + Time = DateTime.Now, + }); + //// Nuevo token var newToken = JwtService.Generate(new AccountModel() { diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index 47bc464..4e1e89a 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -16,9 +16,9 @@ public async Task JoinAdmin(string token) var tokenInformation = JwtService.Validate(token); // Validar. - if (!tokenInformation.IsAuthenticated) + if (!tokenInformation.IsAuthenticated) return; - + // Grupo de la cuenta. await Groups.AddToGroupAsync(Context.ConnectionId, BuildGroupName(tokenInformation.Unique)); @@ -64,6 +64,9 @@ public async Task JoinIntent(PassKeyModel attempt) else Attempts[attempt.User.ToLower()].Add(attempt); + + + // Yo await Groups.AddToGroupAsync(Context.ConnectionId, $"dbo.{Context.ConnectionId}"); diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 06d7da1..3475b6a 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -32,6 +32,3 @@ global using LIN.Types.Responses; global using LIN.Types.Enumerations; global using Http.ResponsesList; - -// Módulos. -global using LIN.Modules; From e96184e972b133a4b256f2c436eb6609379bd0a8 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 2 Apr 2024 18:12:21 -0500 Subject: [PATCH 073/178] Correcciones --- LIN.Cloud.Identity/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 1d9428b..ff1926e 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -23,7 +23,7 @@ string sql = ""; #if DEBUG -sql = builder.Configuration["ConnectionStrings:local"] ?? string.Empty; +sql = builder.Configuration["ConnectionStrings:cloud"] ?? string.Empty; #elif RELEASE sql = builder.Configuration["ConnectionStrings:cloud"] ?? string.Empty; #endif From bd7540aa8fb1618a9535f771eff8cdf2a0f28c76 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 10 Apr 2024 20:19:54 -0500 Subject: [PATCH 074/178] Licencia de software --- LICENSE | 15 +++++++++++++++ README.md | 12 ------------ 2 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2ec3700 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +LIN ACADEMIC LICENSE +Versión 1 - 10 abril 2024 + +Este software y su código fuente están disponibles para su visualización con fines exclusivamente académicos. Se concede permiso a cualquier persona que acceda a este software para ver su código fuente con el propósito de estudio, investigación o enseñanza, siempre y cuando se respeten los términos y condiciones de esta licencia. + +Limitaciones: +1. No se permite la distribución de este software ni de su código fuente, ya sea en su forma original o modificada. +2. No se permite el uso de este software ni de su código fuente con fines comerciales. +3. Cualquier uso fuera del ámbito académico está expresamente prohibido. + +Responsabilidad: +Este software se proporciona "tal cual", sin garantía de ningún tipo, expresa o implícita, incluidas, entre otras, las garantías de comerciabilidad, idoneidad para un propósito particular y no infracción. El titular de los derechos de autor no será responsable de ningún daño directo, indirecto, incidental, especial, ejemplar o consecuencial (incluidos, entre otros, la adquisición de bienes o servicios sustitutos, la pérdida de uso, datos o beneficios o interrupción del negocio) sin importar la causa y bajo cualquier responsabilidad teoría de responsabilidad, ya sea en contrato, responsabilidad estricta o agravio (incluida la negligencia o de otra manera) que surja de cualquier manera del uso de este software, incluso si se ha advertido de la posibilidad de tales daños. + +Aceptación: +Al acceder o utilizar este software, usted acepta cumplir con los términos y condiciones de esta licencia. Si no acepta estos términos y condiciones, no está autorizado a utilizar este software ni su código fuente. \ No newline at end of file diff --git a/README.md b/README.md index 6ba19f1..a3b5c4f 100644 --- a/README.md +++ b/README.md @@ -31,15 +31,3 @@ Este es un proyecto desarrollado en C# y .NET 8 que proporciona funcionalidades 3. Configura la conexión a la base de datos en el archivo `appsettings.json`, proporcionando la cadena de conexión correcta. 4. Compila y ejecuta la aplicación. - -## Contribución - -Si deseas contribuir a este proyecto, ¡serás bienvenido! Puedes contribuir de las siguientes formas: - -- Reportando problemas y errores. -- Proponiendo nuevas características. -- Enviando solicitudes de extracción para solucionar problemas o agregar características. - -## Licencia - -Este proyecto se distribuye bajo la Licencia MIT. Siéntete libre de utilizar, modificar y distribuir este proyecto de acuerdo con los términos de la licencia. From 35752741bd1dbb6e1115c9e6a06dc5c9fb74f6e3 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 12 Apr 2024 23:37:48 -0500 Subject: [PATCH 075/178] =?UTF-8?q?Actualizaci=C3=B3n=20de=20dependencias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 754940d..9f51994 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -8,10 +8,10 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 72838fa12c396a65b9fcc162660a8fafe9b98628 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 23 Apr 2024 20:44:06 -0500 Subject: [PATCH 076/178] Update --- LIN.Cloud.Identity/Areas/Accounts/AccountController.cs | 4 +--- .../Areas/Authentication/IntentsController.cs | 2 +- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- LIN.Cloud.Identity/Program.cs | 1 + LIN.Cloud.Identity/appsettings.Development.json | 6 +++--- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 9b4e839..09e76fd 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -218,7 +218,7 @@ public async Task> Search([FromQuery] string p { AccountContext = tokenInfo.AccountId, FindOn = FindOn.StableAccounts, - IsAdmin =false, + IsAdmin = false, IdentityContext = tokenInfo.IdentityId, }); @@ -254,6 +254,4 @@ public async Task> ReadAll([FromBody] List GetAll([FromHeader] string token) // Cuenta var account = (from a in PassKeyHub.Attempts where a.Key.Equals(tokenInfo.Unique, StringComparison.CurrentCultureIgnoreCase) - select a).FirstOrDefault().Value ?? new(); + select a).FirstOrDefault().Value ?? []; // Hora actual var timeNow = DateTime.Now; diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 9f51994..f70664a 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -8,7 +8,7 @@ - + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index ff1926e..92a787e 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -1,4 +1,5 @@ using LIN.Cloud.Identity.Services.Realtime; +using Http.Services; var builder = WebApplication.CreateBuilder(args); diff --git a/LIN.Cloud.Identity/appsettings.Development.json b/LIN.Cloud.Identity/appsettings.Development.json index b8cdc30..9cdb790 100644 --- a/LIN.Cloud.Identity/appsettings.Development.json +++ b/LIN.Cloud.Identity/appsettings.Development.json @@ -7,10 +7,10 @@ }, "AllowedHosts": "*", "jwt": { - "key": "drfghjiwuytr567uijnbvgfder56y7uiolkjhgfdertyui2345fgh" + "key": "drfghjiwuytr567uijnbvgfder56y7usdfghjwertyuisdfghjkjhgfdswertyujhgiolkjhgfdertyui2345fgh" }, "ConnectionStrings": { - "local": "", - "cloud": "" + "local": "Data Source=(local);Initial Catalog=Identity;Integrated Security=True;TrustServerCertificate=True", + "cloud": "workstation id=linidentitydb.mssql.somee.com;packet size=4096;user id=linauth_SQLLogin_2;pwd=p4b691lkwx;data source=linidentitydb.mssql.somee.com;persist security info=False;initial catalog=linidentitydb;TrustServerCertificate=True; Encrypt=false;" } } \ No newline at end of file From 94d0c642ec9cc844d018c92dd990d4ca9d67393a Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 16 May 2024 13:23:50 -0500 Subject: [PATCH 077/178] Servicios y mejoras --- .../AuthenticationController.cs | 14 ++++--- .../Areas/Authentication/IntentsController.cs | 2 - LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 1 - LIN.Cloud.Identity/Program.cs | 29 ++++---------- .../Services/Auth/Authentication.cs | 38 ++++++++++++------- .../Auth/Interfaces/IAuthentication.cs | 36 ++++++++++++++++++ .../Services/Auth/JwtService.cs | 9 +---- LIN.Cloud.Identity/Services/Configuration.cs | 31 --------------- .../Services/Realtime/PassKeyHub.cs | 1 - 9 files changed, 77 insertions(+), 84 deletions(-) create mode 100644 LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs delete mode 100644 LIN.Cloud.Identity/Services/Configuration.cs diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 11fc99c..328969c 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,8 +1,10 @@ +using LIN.Cloud.Identity.Services.Auth.Interfaces; + namespace LIN.Cloud.Identity.Areas.Authentication; [Route("authentication")] -public class AuthenticationController : ControllerBase +public class AuthenticationController(IAuthentication authentication) : ControllerBase { @@ -24,11 +26,11 @@ public async Task> Login([FromQuery] string us }; - // Generar el servicio. - var service = new Services.Auth.Authentication(user, password, application); + // Establecer credenciales. + authentication.SetCredentials(user, password, application); // Respuesta. - var response = await service.Start(); + var response = await authentication.Start(); // Validación al obtener el usuario switch (response) @@ -63,12 +65,12 @@ public async Task> Login([FromQuery] string us } // Genera el token - var token = JwtService.Generate(service.Account!, 0); + var token = authentication.GenerateToken(); // Respuesta. var http = new ReadOneResponse { - Model = service.Account!, + Model = authentication.GetData(), Response = Responses.Success, Token = token }; diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index f6b5074..93360ca 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -50,7 +50,6 @@ where I.Expiración > timeNow - /// /// Obtiene la lista de intentos de llaves de paso están activos. /// @@ -65,7 +64,6 @@ public async Task> Count([FromHeader] string token) // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - var x = await Data.PassKeys.Count(tokenInfo.AccountId); // Retorna diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index f70664a..9c642ae 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -15,7 +15,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 92a787e..d789a6f 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -1,24 +1,13 @@ using LIN.Cloud.Identity.Services.Realtime; using Http.Services; +using Http.Extensions; +using LIN.Cloud.Identity.Services.Auth.Interfaces; var builder = WebApplication.CreateBuilder(args); // Servicios de contenedor. -builder.Services.AddControllers(); -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); builder.Services.AddSignalR(); - -builder.Services.AddCors(options => -{ - options.AddPolicy("AllowAnyOrigin", - builder => - { - builder.AllowAnyOrigin() - .AllowAnyHeader() - .AllowAnyMethod(); - }); -}); +builder.Services.AddLINHttp(); // Servicios personalizados string sql = ""; @@ -34,19 +23,15 @@ // Servicios propios. builder.Services.AddIP(); +// Servicio de autenticación. +builder.Services.AddScoped(); + var app = builder.Build(); // Middlewares personalizados. app.UseIP(); - - -app.UseCors("AllowAnyOrigin"); - - -// Swagger. -app.UseSwagger(); -app.UseSwaggerUI(); +app.UseLINHttp(); // Base de datos. app.UseDataBase(); diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index b6255a2..2880ad9 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -1,28 +1,27 @@ namespace LIN.Cloud.Identity.Services.Auth; -public class Authentication +public class Authentication : Interfaces.IAuthentication { /// /// Usuario. /// - private string User { get; set; } - + private string User { get; set; } = string.Empty; /// /// Usuario. /// - private string Password { get; set; } + private string Password { get; set; } = string.Empty; /// /// Código de la aplicación. /// - private string AppCode { get; set; } + private string AppCode { get; set; } = string.Empty; @@ -35,22 +34,20 @@ public class Authentication /// - /// Generar nuevo servicio de autenticación. + /// Establecer credenciales. /// - /// Usuario. - /// Contraseña - /// Código de aplicación - public Authentication(string user, string password, string appCode) + /// Usuario. + /// Contraseña. + /// Código de app. + public void SetCredentials(string username, string password, string appCode) { - this.User = user; + this.User = username; this.Password = password; this.AppCode = appCode; } - - /// /// Iniciar el proceso. /// @@ -67,7 +64,7 @@ public async Task Start() // Validar contraseña. bool password = ValidatePassword(); - if(!password) + if (!password) return Responses.InvalidPassword; @@ -119,4 +116,17 @@ private bool ValidatePassword() } + + /// + /// Obtener el token. + /// + public string GenerateToken() => JwtService.Generate(Account!, 0); + + + + /// + /// Obtener el token. + /// + public AccountModel GetData() => Account!; + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs b/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs new file mode 100644 index 0000000..51eb0fd --- /dev/null +++ b/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs @@ -0,0 +1,36 @@ +namespace LIN.Cloud.Identity.Services.Auth.Interfaces; + +public interface IAuthentication +{ + + + /// + /// Iniciar el proceso. + /// + public Task Start(); + + + + /// + /// Establecer credenciales. + /// + /// Usuario único. + /// Contraseña. + /// Código de app. + public void SetCredentials(string username, string password, string appCode); + + + + /// + /// Obtener el token. + /// + public string GenerateToken(); + + + + /// + /// Obtener la data. + /// + public AccountModel GetData(); + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/JwtService.cs b/LIN.Cloud.Identity/Services/Auth/JwtService.cs index 97e88a6..9ffbf43 100644 --- a/LIN.Cloud.Identity/Services/Auth/JwtService.cs +++ b/LIN.Cloud.Identity/Services/Auth/JwtService.cs @@ -1,9 +1,4 @@ -using Microsoft.Extensions.Configuration; -using LIN.Cloud.Identity.Services.Models; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; - -namespace LIN.Cloud.Identity.Services.Auth; +namespace LIN.Cloud.Identity.Services.Auth; public class JwtService @@ -23,7 +18,7 @@ public class JwtService /// public static void Open() { - JwtKey = Configuration.GetConfiguration("jwt:key"); + JwtKey = Http.Services.Configuration.GetConfiguration("jwt:key"); } diff --git a/LIN.Cloud.Identity/Services/Configuration.cs b/LIN.Cloud.Identity/Services/Configuration.cs deleted file mode 100644 index eb4aeab..0000000 --- a/LIN.Cloud.Identity/Services/Configuration.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace LIN.Cloud.Identity.Services; - - -public class Configuration -{ - - - - private static IConfiguration? Config; - - private static bool _isStart = false; - - - public static string GetConfiguration(string route) - { - - if (_isStart && Config != null) - return Config[route] ?? string.Empty; - - var configBuilder = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", false, true); - - Config = configBuilder.Build(); - _isStart = true; - - return Config[route] ?? string.Empty; - - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index d7b5635..48ce893 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -13,7 +13,6 @@ public partial class PassKeyHub : Hub public readonly static Dictionary> Attempts = []; - /// /// Canal de intentos. /// From 37d3a03d13e85b99ac90e1659956db154c3174d4 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 30 May 2024 13:36:10 -0500 Subject: [PATCH 078/178] Mejoras --- .../Contexts}/DataContext.cs | 19 ++- .../Extensions/PersistenceExtensions.cs | 57 ++++++++ .../LIN.Cloud.Identity.Persistence.csproj | 20 +++ LIN.Cloud.Identity.Persistence/Models/A.cs | 20 +++ .../Models/PassKeyDBModel.cs | 6 +- .../Queries/AccountFindable.cs | 81 ++++++++++++ .../Queries/IdentityFindable.cs | 18 +++ .../Queries/Interfaces/IFindable.cs | 23 ++++ LIN.Cloud.Identity.sln | 6 + .../Areas/Accounts/AccountController.cs | 32 ++++- .../AuthenticationController.cs | 3 +- .../Areas/Authentication/IntentsController.cs | 2 +- .../Areas/Directories/DirectoryController.cs | 2 +- .../Areas/Groups/GroupsController.cs | 4 +- .../Areas/Organizations/IdentityController.cs | 2 +- .../Organizations/OrganizationController.cs | 2 +- LIN.Cloud.Identity/Data/PassKeys.cs | 4 +- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 6 +- LIN.Cloud.Identity/Program.cs | 6 +- .../Services/Database/DataService.cs | 125 ------------------ .../Services/Database/Database.cs | 99 -------------- .../Services/Middlewares/IPMiddleware.cs | 1 - .../Services/Realtime/PassKeyHub.cs | 4 +- LIN.Cloud.Identity/Usings.cs | 2 +- 24 files changed, 285 insertions(+), 259 deletions(-) rename {LIN.Cloud.Identity/Services/Database => LIN.Cloud.Identity.Persistence/Contexts}/DataContext.cs (95%) create mode 100644 LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs create mode 100644 LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj create mode 100644 LIN.Cloud.Identity.Persistence/Models/A.cs rename {LIN.Cloud.Identity/Services => LIN.Cloud.Identity.Persistence}/Models/PassKeyDBModel.cs (57%) create mode 100644 LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs create mode 100644 LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs create mode 100644 LIN.Cloud.Identity.Persistence/Queries/Interfaces/IFindable.cs delete mode 100644 LIN.Cloud.Identity/Services/Database/DataService.cs delete mode 100644 LIN.Cloud.Identity/Services/Database/Database.cs diff --git a/LIN.Cloud.Identity/Services/Database/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs similarity index 95% rename from LIN.Cloud.Identity/Services/Database/DataContext.cs rename to LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 2d47de0..92ab001 100644 --- a/LIN.Cloud.Identity/Services/Database/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -1,4 +1,8 @@ -namespace LIN.Cloud.Identity.Services.Database; +using LIN.Cloud.Identity.Persistence.Models; +using LIN.Types.Cloud.Identity.Models; +using Microsoft.EntityFrameworkCore; + +namespace LIN.Cloud.Identity.Persistence.Contexts; public class DataContext(DbContextOptions options) : DbContext(options) @@ -49,14 +53,13 @@ public class DataContext(DbContextOptions options) : DbContext(opti - + /// + /// Configuring database. + /// protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); base.OnConfiguring(optionsBuilder); - - } @@ -124,7 +127,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(t => t.IdentityId) .OnDelete(DeleteBehavior.NoAction); - + } @@ -133,7 +136,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() - .HasKey(t => new { t.IdentityId, @@ -178,9 +180,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } - - - // Nombres de las tablas. modelBuilder.Entity().ToTable("IDENTITIES"); modelBuilder.Entity().ToTable("ACCOUNTS"); diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs new file mode 100644 index 0000000..15b325f --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -0,0 +1,57 @@ +using LIN.Cloud.Identity.Persistence.Contexts; +using LIN.Cloud.Identity.Persistence.Queries; +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace LIN.Cloud.Identity.Persistence.Extensions; + +public static class PersistenceExtensions +{ + + + /// + /// Agregar servicios de persistence. + /// + /// Services. + public static IServiceCollection AddPersistence(this IServiceCollection services, IConfigurationManager configuration) + { + + + services.AddDbContextPool(options => + { + options.UseSqlServer(configuration.GetConnectionString("cloud")); + }); + + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + + + + /// + /// Habilitar el servicio de base de datos. + /// + public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) + { + try + { + + // Obtener servicio. + var context = app.ApplicationServices.GetService(); + + context?.Database.EnsureCreated() ; + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + return app; + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj new file mode 100644 index 0000000..f1b04e0 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + diff --git a/LIN.Cloud.Identity.Persistence/Models/A.cs b/LIN.Cloud.Identity.Persistence/Models/A.cs new file mode 100644 index 0000000..89c147a --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Models/A.cs @@ -0,0 +1,20 @@ +namespace LIN.Cloud.Identity.Persistence.Models; + + +public class QueryObjectFilter +{ + public int AccountContext { get; set; } + public int IdentityContext { get; set; } + public List OrganizationsDirectories { get; set; } = []; + public bool IsAdmin { get; set; } + public bool IncludePhoto { get; set; } = true; + public FindOn FindOn { get; set; } + +} + + +public enum FindOn +{ + StableAccounts, + AllAccounts +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Models/PassKeyDBModel.cs b/LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs similarity index 57% rename from LIN.Cloud.Identity/Services/Models/PassKeyDBModel.cs rename to LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs index f369f21..4ba6287 100644 --- a/LIN.Cloud.Identity/Services/Models/PassKeyDBModel.cs +++ b/LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs @@ -1,10 +1,12 @@ -namespace LIN.Cloud.Identity.Services.Models; +using LIN.Types.Cloud.Identity.Models; + +namespace LIN.Cloud.Identity.Persistence.Models; public class PassKeyDBModel { - public int Id { get; set; } + public int Id { get; set; } public DateTime Time { get; set; } public int AccountId { get; set; } public AccountModel Account { get; set; } = null!; diff --git a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs new file mode 100644 index 0000000..03b562b --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs @@ -0,0 +1,81 @@ +using LIN.Cloud.Identity.Persistence.Contexts; +using LIN.Cloud.Identity.Persistence.Queries.Interfaces; +using LIN.Types.Cloud.Identity.Enumerations; +using LIN.Types.Cloud.Identity.Models; + +namespace LIN.Cloud.Identity.Persistence.Queries; + + +public class AccountFindable(Contexts.DataContext context) : IFindable +{ + + + + /// + /// Buscar en la cuentas estables. + /// + /// Contexto de base de datos. + private IQueryable OnStable() + { + + // Hora actual. + var now = DateTime.Now; + + // Consulta. + var query = from account in context.Accounts + where account.Identity.Status != IdentityStatus.Disable + && account.Identity.EffectiveTime < now && account.Identity.ExpirationTime > now + select account; + + // Retornar. + return query; + } + + + + /// + /// Buscar en todas las cuentas. + /// + /// Contexto de base de datos. + private IQueryable OnAll() + { + + // Hora actual. + var now = DateTime.Now; + + // Consulta. + var query = from account in context.Accounts + select account; + + // Retornar. + return query; + } + + + + + public IQueryable GetAccounts(int id, Models.QueryObjectFilter filters) + { + + // Query general + IQueryable accounts; + + if (filters.FindOn == Models.FindOn.StableAccounts) + accounts = from account in (filters.FindOn == Models.FindOn.StableAccounts) ? OnStable() : OnAll() + where account.Id == id + select account; + + // Armar el modelo + accounts = BuildModel(accounts, filters, context); + + // Retorno + return accounts; + + } + + + public IQueryable GetAccounts(List ids, Models.QueryObjectFilter filter) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs new file mode 100644 index 0000000..df89022 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs @@ -0,0 +1,18 @@ +using LIN.Cloud.Identity.Persistence.Queries.Interfaces; +using LIN.Types.Cloud.Identity.Models; + +namespace LIN.Cloud.Identity.Persistence.Queries; + + +public class IdentityFindable : IFindable +{ + public IQueryable GetAccounts(int id, Models.QueryObjectFilter filter) + { + throw new NotImplementedException(); + } + + public IQueryable GetAccounts(List ids, Models.QueryObjectFilter filter) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Queries/Interfaces/IFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/Interfaces/IFindable.cs new file mode 100644 index 0000000..5ddc722 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Queries/Interfaces/IFindable.cs @@ -0,0 +1,23 @@ +namespace LIN.Cloud.Identity.Persistence.Queries.Interfaces; + + +public interface IFindable +{ + + + /// + /// Encontrar por Id. + /// + /// Id único. + public IQueryable GetAccounts(int id, Models.QueryObjectFilter filter); + + + + /// + /// Encontrar por Ids. + /// + /// Lista de ids únicos. + public IQueryable GetAccounts(List ids, Models.QueryObjectFilter filter); + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index 6c7dc9e..1a4cb89 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http", "..\..\Tipos\Http\Ht EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types", "..\..\Tipos\LIN.Types\LIN.Types.csproj", "{E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LIN.Cloud.Identity.Persistence", "LIN.Cloud.Identity.Persistence\LIN.Cloud.Identity.Persistence.csproj", "{6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Release|Any CPU.Build.0 = Release|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 09e76fd..03bcfb2 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Accounts; -[Route("account")] +[Route("[controller]")] public class AccountController : ControllerBase { @@ -10,7 +10,7 @@ public class AccountController : ControllerBase /// Crear una cuenta LIN. /// /// Modelo de la cuenta. - [HttpPost("create")] + [HttpPost] public async Task Create([FromBody] AccountModel? modelo) { @@ -192,6 +192,34 @@ public async Task> ReadByIdentity([FromQuery] + /// + /// Obtener una cuenta según Id de identidad. + /// + /// Id de la identidad. + /// Token de acceso. + [HttpPost("read/identity")] + [IdentityToken] + public async Task> ReadByIdentity([FromBody] List ids, [FromHeader] string token) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtiene el usuario + var response = await Data.Accounts.FindAll(ids, new() + { + AccountContext = tokenInfo.AccountId, + FindOn = FindOn.StableAccounts, + IsAdmin = false, + IdentityContext = tokenInfo.IdentityId, + }); + + return response; + + } + + + /// /// Obtiene una lista de diez (10) usuarios que coincidan con un patron. /// diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 328969c..6d25805 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -3,7 +3,7 @@ namespace LIN.Cloud.Identity.Areas.Authentication; -[Route("authentication")] +[Route("[controller]")] public class AuthenticationController(IAuthentication authentication) : ControllerBase { @@ -25,7 +25,6 @@ public async Task> Login([FromQuery] string us Message = "Uno o varios parámetros son invalido." }; - // Establecer credenciales. authentication.SetCredentials(user, password, application); diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 93360ca..43c46c0 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -3,7 +3,7 @@ namespace LIN.Cloud.Identity.Areas.Authentication; -[Route("Intents")] +[Route("[controller]")] public class IntentsController : ControllerBase { diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index b6e3dd6..eedc40f 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Directories; -[Route("directory")] +[Route("[controller]")] public class DirectoryController : ControllerBase { diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index 67fffe6..456165e 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Groups; -[Route("groups")] +[Route("[controller]")] public class GroupsController : ControllerBase { @@ -58,7 +58,6 @@ public async Task Create([FromBody] GroupModel group, [FromH - /// /// Obtener los grupos asociados a una organización. /// @@ -103,7 +102,6 @@ public async Task> ReadAll([FromHeader] string t - /// /// Obtener un grupo según el Id. /// diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 81290bc..4085a35 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; -[Route("identity")] +[Route("[controller]")] public class IdentityController : ControllerBase { diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 259ad3c..11745dd 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; -[Route("organizations")] +[Route("[controller]")] public class OrganizationsController : ControllerBase { diff --git a/LIN.Cloud.Identity/Data/PassKeys.cs b/LIN.Cloud.Identity/Data/PassKeys.cs index c603303..5fd4086 100644 --- a/LIN.Cloud.Identity/Data/PassKeys.cs +++ b/LIN.Cloud.Identity/Data/PassKeys.cs @@ -1,4 +1,6 @@ -namespace LIN.Cloud.Identity.Data; +using LIN.Cloud.Identity.Persistence.Models; + +namespace LIN.Cloud.Identity.Data; public static class PassKeys diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 9c642ae..618f8ec 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -10,11 +10,6 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - @@ -25,6 +20,7 @@ + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index d789a6f..d78b337 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -1,7 +1,7 @@ using LIN.Cloud.Identity.Services.Realtime; -using Http.Services; using Http.Extensions; using LIN.Cloud.Identity.Services.Auth.Interfaces; +using LIN.Cloud.Identity.Persistence.Extensions; var builder = WebApplication.CreateBuilder(args); @@ -18,13 +18,13 @@ sql = builder.Configuration["ConnectionStrings:cloud"] ?? string.Empty; #endif -builder.Services.AddDataBase(sql); - // Servicios propios. builder.Services.AddIP(); // Servicio de autenticación. builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddPersistence(builder.Configuration); var app = builder.Build(); diff --git a/LIN.Cloud.Identity/Services/Database/DataService.cs b/LIN.Cloud.Identity/Services/Database/DataService.cs deleted file mode 100644 index 6f15c7f..0000000 --- a/LIN.Cloud.Identity/Services/Database/DataService.cs +++ /dev/null @@ -1,125 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Database; - - -public static class DataService -{ - - - /// - /// Cache de conexiones. - /// - private static List Cache { get; set; } = []; - - - /// - /// Cantidad de instancias de base de datos. - /// - public static int MaxInstances { get; set; } = 10; - - - /// - /// Cadena de conexión SQL Server. - /// - public static string ConnectionString { get; private set; } = string.Empty; - - - - /// - /// Agregar base de datos al cache. - /// - /// Base de datos. - public static void AddToCache(DataBase db) - { - if (Cache.Count >= MaxInstances) - return; - Cache.Add(db); - } - - - - /// - /// Obtener una conexión a base de datos. - /// - public static (DataBase context, string contextKey) GetConnection() - { - - // Obtener base de datos del cache. - var cache = Cache.FirstOrDefault(db => !db.OnUseAction); - - // Validaciones. - if (cache != null && cache.Key == string.Empty) - { - // Bloquear el objecto. - lock (cache) - { - cache.SetOnUse(); - string key = Global.Utilities.KeyGenerator. Generate(10, "db."); - cache.Key = key; - return (cache, key); - } - } - - // Generar estancia forzosa. - var newDB = new DataBase() - { - Key = Global.Utilities.KeyGenerator.Generate(10, "db.") - }; - - newDB.SetOnUse(); - AddToCache(newDB); - return (newDB, newDB.Key); - - } - - - - /// - /// Obtener una conexión a base de datos. - /// - public static (DataBase context, string contextKey) GetConnectionForce() - { - return (new(), ""); - - } - - - - /// - /// Habilitar el servicio de base de datos. - /// - public static IServiceCollection AddDataBase(this IServiceCollection context, string sql) - { - ConnectionString = sql; - context.AddDbContext(options => - { - options.UseSqlServer(sql); -#if DEBUG - options.EnableSensitiveDataLogging(); -#endif - }); - - return context; - } - - - - /// - /// Habilitar el servicio de base de datos. - /// - public static IApplicationBuilder UseDataBase(this IApplicationBuilder context) - { - try - { - var (connection, key) = GetConnection(); - bool created = connection.Database.EnsureCreated(); - connection.Close(key); - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - } - return context; - } - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Database/Database.cs b/LIN.Cloud.Identity/Services/Database/Database.cs deleted file mode 100644 index 18444cf..0000000 --- a/LIN.Cloud.Identity/Services/Database/Database.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace LIN.Cloud.Identity.Services.Database; - - -public sealed class DataBase : DataContext -{ - - - /// - /// Obtiene o establece si la DataBase esta en uso. - /// - private volatile bool OnUse = false; - - - - /// - /// Obtiene si la DataBase esta en uso y la pone en uso. - /// - public bool OnUseAction - { - get - { - lock (this) - { - if (!OnUse) - { - OnUse = true; - return false; - } - return true; - } - } - } - - - - /// - /// LLave para cerrar la conexión. - /// - public string Key = string.Empty; - - - - /// - /// Nueva DataBase. - /// - public DataBase() : base(new DbContextOptionsBuilder().UseSqlServer(DataService.ConnectionString).Options) - { - } - - - - /// - /// Destructor. - /// - ~DataBase() - { - Dispose(); - } - - - - - /// - /// Establece que la conexión esta en uso. - /// - public void SetOnUse() - { - lock (this) - { - OnUse = true; - } - } - - - - /// - /// Cerrar la conexión con la base de datos. - /// - /// Llave - public void Close(string key) - { - lock (this) - { - if (Key != key) - return; - - foreach (var entry in ChangeTracker.Entries()) - entry.State = EntityState.Detached; - - ChangeTracker.Clear(); - Key = string.Empty; - OnUse = false; - } - } - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs index a5bb829..bda30b3 100644 --- a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs +++ b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs @@ -26,7 +26,6 @@ await context.Response.WriteAsJsonAsync(new ResponseBase return; } - // Item de IP. context.Items.Add("IP", ip); await next(context); diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 48ce893..35c7c7d 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -1,4 +1,6 @@ -namespace LIN.Cloud.Identity.Services.Realtime; +using LIN.Cloud.Identity.Persistence.Models; + +namespace LIN.Cloud.Identity.Services.Realtime; public partial class PassKeyHub : Hub diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 3475b6a..c856dd2 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -17,7 +17,7 @@ global using Microsoft.EntityFrameworkCore; // Tipos locales. -global using LIN.Cloud.Identity.Services.Database; +global using LIN.Cloud.Identity.Persistence.Contexts; global using LIN.Cloud.Identity.Services.Middlewares; global using LIN.Types.Cloud.Identity.Models; global using LIN.Types.Cloud.Identity.Enumerations; From 601aaa9936c1236873178d508ddce5aac444de0d Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 30 May 2024 14:07:07 -0500 Subject: [PATCH 079/178] Mejoras de servicios --- .../Areas/Accounts/AccountController.cs | 16 +- .../AuthenticationController.cs | 4 +- .../Areas/Authentication/IntentsController.cs | 4 +- .../Areas/Directories/DirectoryController.cs | 8 +- .../Areas/Groups/GroupsController.cs | 14 +- .../Areas/Groups/GroupsMembersController.cs | 32 +- .../Areas/Organizations/IdentityController.cs | 12 +- .../Areas/Organizations/MemberController.cs | 8 +- .../Organizations/OrganizationController.cs | 10 +- LIN.Cloud.Identity/Data/Accounts.cs | 248 ++++++++++++---- LIN.Cloud.Identity/Data/Accounts.cs.cs | 274 ------------------ LIN.Cloud.Identity/Data/DirectoryMembers.cs | 186 ++---------- LIN.Cloud.Identity/Data/GroupMembers.cs | 175 +---------- LIN.Cloud.Identity/Data/Groups.cs | 150 +--------- LIN.Cloud.Identity/Data/Identities.cs | 82 +----- LIN.Cloud.Identity/Data/IdentityRoles.cs | 89 +----- LIN.Cloud.Identity/Data/Organizations.cs | 103 +------ LIN.Cloud.Identity/Data/PassKeys.cs | 59 +--- LIN.Cloud.Identity/Program.cs | 3 +- .../Services/Auth/Authentication.cs | 4 +- .../Services/Extensions/LocalServices.cs | 30 ++ .../Services/Formats/Account.cs | 2 +- 22 files changed, 334 insertions(+), 1179 deletions(-) delete mode 100644 LIN.Cloud.Identity/Data/Accounts.cs.cs create mode 100644 LIN.Cloud.Identity/Services/Extensions/LocalServices.cs diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 03bcfb2..57ad508 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -2,7 +2,7 @@ namespace LIN.Cloud.Identity.Areas.Accounts; [Route("[controller]")] -public class AccountController : ControllerBase +public class AccountController(Data.Accounts accountData) : ControllerBase { @@ -35,7 +35,7 @@ public async Task Create([FromBody] AccountModel? modelo) modelo = Services.Formats.Account.Process(modelo); // Creación del usuario - var response = await Data.Accounts.Create(modelo, 0); + var response = await accountData.Create(modelo, 0); // Evaluación if (response.Response != Responses.Success) @@ -81,7 +81,7 @@ public async Task> Read([FromQuery] int id, [F JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtiene el usuario. - var response = await Data.Accounts.Read(id, new() + var response = await accountData.Read(id, new() { AccountContext = tokenInfo.AccountId, FindOn = FindOn.StableAccounts, @@ -126,7 +126,7 @@ public async Task> Read([FromQuery] string use // Obtiene el usuario. - var response = await Data.Accounts.Read(user, new() + var response = await accountData.Read(user, new() { AccountContext = tokenInfo.AccountId, FindOn = FindOn.StableAccounts, @@ -170,7 +170,7 @@ public async Task> ReadByIdentity([FromQuery] JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtiene el usuario. - var response = await Data.Accounts.ReadByIdentity(id, new() + var response = await accountData.ReadByIdentity(id, new() { AccountContext = tokenInfo.AccountId, FindOn = FindOn.StableAccounts, @@ -206,7 +206,7 @@ public async Task> ReadByIdentity([FromBody] L JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtiene el usuario - var response = await Data.Accounts.FindAll(ids, new() + var response = await accountData.FindAll(ids, new() { AccountContext = tokenInfo.AccountId, FindOn = FindOn.StableAccounts, @@ -242,7 +242,7 @@ public async Task> Search([FromQuery] string p // Obtiene el usuario - var response = await Data.Accounts.Search(pattern, new() + var response = await accountData.Search(pattern, new() { AccountContext = tokenInfo.AccountId, FindOn = FindOn.StableAccounts, @@ -269,7 +269,7 @@ public async Task> ReadAll([FromBody] List> LoginWithToken([FromHeader] JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtiene el usuario. - var response = await Data.Accounts.Read(tokenInfo.AccountId, new QueryAccountFilter() + var response = await accountData.Read(tokenInfo.AccountId, new QueryAccountFilter() { IsAdmin = true, FindOn = FindOn.StableAccounts diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 43c46c0..71b508e 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class IntentsController : ControllerBase +public class IntentsController (Data.PassKeys passkeyData) : ControllerBase { @@ -64,7 +64,7 @@ public async Task> Count([FromHeader] string token) // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - var x = await Data.PassKeys.Count(tokenInfo.AccountId); + var x = await passkeyData.Count(tokenInfo.AccountId); // Retorna return new(Responses.Success, x.Model); diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index eedc40f..a77f823 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -2,7 +2,7 @@ [Route("[controller]")] -public class DirectoryController : ControllerBase +public class DirectoryController (Data.DirectoryMembers directoryMembersData, Data.Groups groupsData) : ControllerBase { @@ -27,7 +27,7 @@ public async Task> ReadAll([FromHeader] string }; // Obtiene el usuario. - var response = await Data.DirectoryMembers.Read(tokenInfo.IdentityId, organization); + var response = await directoryMembersData.Read(tokenInfo.IdentityId, organization); // Si es erróneo if (response.Response != Responses.Success) @@ -57,7 +57,7 @@ public async Task> ReadMembers([FromHeader] str JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtener la organización. - var orgId = await Data.Groups.GetOwner(directory); + var orgId = await groupsData.GetOwner(directory); // Si hubo un error. if (orgId.Response != Responses.Success) @@ -84,7 +84,7 @@ public async Task> ReadMembers([FromHeader] str // Obtiene el usuario. - var response = await Data.DirectoryMembers.ReadMembers(directory); + var response = await directoryMembersData.ReadMembers(directory); // Si es erróneo if (response.Response != Responses.Success) diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index 456165e..e22a65f 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -2,7 +2,7 @@ [Route("[controller]")] -public class GroupsController : ControllerBase +public class GroupsController(Data.Groups groupData) : ControllerBase { @@ -38,7 +38,7 @@ public async Task Create([FromBody] GroupModel group, [FromH Services.Formats.Identities.Process(group.Identity); // Obtener el modelo. - var response = await Data.Groups.Create(group); + var response = await groupData.Create(group); // Si es erróneo if (response.Response != Responses.Success) @@ -86,7 +86,7 @@ public async Task> ReadAll([FromHeader] string t }; // Obtener el modelo. - var response = await Data.Groups.ReadAll(organization); + var response = await groupData.ReadAll(organization); // Si es erróneo if (response.Response != Responses.Success) @@ -117,7 +117,7 @@ public async Task> ReadOne([FromHeader] string t // Obtener la organización. - var orgId = await Data.Groups.GetOwner(id); + var orgId = await groupData.GetOwner(id); // Si hubo un error. if (orgId.Response != Responses.Success) @@ -143,7 +143,7 @@ public async Task> ReadOne([FromHeader] string t }; // Obtener el modelo. - var response = await Data.Groups.Read(id); + var response = await groupData.Read(id); // Si es erróneo if (response.Response != Responses.Success) @@ -174,7 +174,7 @@ public async Task> ReadIdentity([FromHeader] str // Obtener la organización. - var orgId = await Data.Groups.GetOwnerByIdentity(id); + var orgId = await groupData.GetOwnerByIdentity(id); // Si hubo un error. if (orgId.Response != Responses.Success) @@ -200,7 +200,7 @@ public async Task> ReadIdentity([FromHeader] str }; // Obtener el modelo. - var response = await Data.Groups.ReadByIdentity(id); + var response = await groupData.ReadByIdentity(id); // Si es erróneo if (response.Response != Responses.Success) diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index ca6a3b4..a72d4ab 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -2,7 +2,7 @@ [Route("Groups/members")] -public class GroupsMembersController : Controller +public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers) : Controller { @@ -20,7 +20,7 @@ public async Task Create([FromHeader] string token, [FromBod JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtener la organización. - var orgId = await Data.Groups.GetOwner(model.GroupId); + var orgId = await groupsData.GetOwner(model.GroupId); // Si hubo un error. if (orgId.Response != Responses.Success) @@ -46,7 +46,7 @@ public async Task Create([FromHeader] string token, [FromBod }; // Valida si el usuario pertenece a la organización. - var idIsIn = await Data.DirectoryMembers.IamIn(model.IdentityId, orgId.Model); + var idIsIn = await directoryMembersData.IamIn(model.IdentityId, orgId.Model); // Si no existe. if (idIsIn.Response != Responses.Success) @@ -58,7 +58,7 @@ public async Task Create([FromHeader] string token, [FromBod // Crear el usuario. - var response = await Data.GroupMembers.Create(model); + var response = await groupMembers.Create(model); // Retorna el resultado return response; @@ -82,7 +82,7 @@ public async Task Create([FromHeader] string token, [FromHea JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtener la organización. - var orgId = await Data.Groups.GetOwner(group); + var orgId = await groupsData.GetOwner(group); // Si hubo un error. if (orgId.Response != Responses.Success) @@ -112,7 +112,7 @@ public async Task Create([FromHeader] string token, [FromHea ids = ids.Distinct().ToList(); // Valida si el usuario pertenece a la organización. - var idIsIn = await Data.DirectoryMembers.IamIn(ids, orgId.Model); + var idIsIn = await directoryMembersData.IamIn(ids, orgId.Model); // Si no existe. if (idIsIn.Response != Responses.Success) @@ -124,7 +124,7 @@ public async Task Create([FromHeader] string token, [FromHea // Crear el usuario. - var response = await Data.GroupMembers.Create(ids.Select(id => new GroupMember + var response = await groupMembers.Create(ids.Select(id => new GroupMember { Group = new() { @@ -157,7 +157,7 @@ public async Task> ReadMembers([FromHeader] str JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtener la organización. - var orgId = await Data.Groups.GetOwner(group); + var orgId = await groupsData.GetOwner(group); // Si hubo un error. if (orgId.Response != Responses.Success) @@ -184,7 +184,7 @@ public async Task> ReadMembers([FromHeader] str // Obtiene el usuario. - var response = await Data.GroupMembers.ReadAll(group); + var response = await groupMembers.ReadAll(group); // Si es erróneo if (response.Response != Responses.Success) @@ -215,7 +215,7 @@ public async Task> Search([FromHeader] string JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtener la organización. - var orgId = await Data.Groups.GetOwner(group); + var orgId = await groupsData.GetOwner(group); // Si hubo un error. if (orgId.Response != Responses.Success) @@ -242,7 +242,7 @@ public async Task> Search([FromHeader] string // Obtiene los miembros. - var members = await Data.GroupMembers.Search(pattern, group); + var members = await groupMembers.Search(pattern, group); // Error al obtener los integrantes. if (members.Response != Responses.Success) @@ -275,7 +275,7 @@ public async Task> SearchOnGroups([FromHeader JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtener la organización. - var orgId = await Data.Groups.GetOwner(group); + var orgId = await groupsData.GetOwner(group); // Si hubo un error. if (orgId.Response != Responses.Success) @@ -302,7 +302,7 @@ public async Task> SearchOnGroups([FromHeader // Obtiene los miembros. - var members = await Data.GroupMembers.SearchGroups(pattern, group); + var members = await groupMembers.SearchGroups(pattern, group); // Error al obtener los integrantes. if (members.Response != Responses.Success) @@ -333,7 +333,7 @@ public async Task DeleteMembers([FromHeader] string token, [Fr JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtener la organización. - var orgId = await Data.Groups.GetOwner(group); + var orgId = await groupsData.GetOwner(group); // Si hubo un error. if (orgId.Response != Responses.Success) @@ -360,7 +360,7 @@ public async Task DeleteMembers([FromHeader] string token, [Fr // Obtiene el usuario. - var response = await Data.GroupMembers.Delete(identity, group); + var response = await groupMembers.Delete(identity, group); // Si es erróneo if (response.Response != Responses.Success) @@ -406,7 +406,7 @@ public async Task> OnMembers([FromHeader] string // Obtiene el usuario. - var response = await Data.GroupMembers.OnMembers(organization, identity); + var response = await groupMembers.OnMembers(organization, identity); // Si es erróneo if (response.Response != Responses.Success) diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 4085a35..73cc269 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -2,7 +2,7 @@ [Route("[controller]")] -public class IdentityController : ControllerBase +public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData) : ControllerBase { @@ -46,7 +46,7 @@ public async Task Create([FromBody] IdentityRolesModel rolMode }; // Obtener el modelo. - var response = await Data.IdentityRoles.Create(rolModel); + var response = await identityRolesData.Create(rolModel); // Si es erróneo if (response.Response != Responses.Success) @@ -96,7 +96,7 @@ public async Task> ReadAll([FromHeader] - var isIn = await Data.DirectoryMembers.IamIn(identity, organization); + var isIn = await directoryMembersData.IamIn(identity, organization); if (isIn.Response != Responses.Success) @@ -108,7 +108,7 @@ public async Task> ReadAll([FromHeader] // Obtener el modelo. - var response = await Data.IdentityRoles.ReadAll(identity, organization); + var response = await identityRolesData.ReadAll(identity, organization); // Si es erróneo if (response.Response != Responses.Success) @@ -156,7 +156,7 @@ public async Task ReadAll([FromHeader] string token, [FromHead - var isIn = await Data.DirectoryMembers.IamIn(identity, organization); + var isIn = await directoryMembersData.IamIn(identity, organization); if (isIn.Response != Responses.Success) @@ -168,7 +168,7 @@ public async Task ReadAll([FromHeader] string token, [FromHead // Obtener el modelo. - var response = await Data.IdentityRoles.Remove(identity, rol, organization); + var response = await identityRolesData.Remove(identity, rol, organization); // Si es erróneo if (response.Response != Responses.Success) diff --git a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs index 65a6254..6fd1f2f 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [Route("orgs/members")] -public class MemberController : ControllerBase +public class MemberController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData) : ControllerBase { @@ -38,7 +38,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr modelo = Services.Formats.Account.Process(modelo); // Organización. - var orgIdentity = await Data.Organizations.GetDomain(organization); + var orgIdentity = await organizationsData.GetDomain(organization); // Validar. if (orgIdentity.Response != Responses.Success) @@ -76,7 +76,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr // Creación del usuario - var response = await Data.Accounts.Create(modelo, organization); + var response = await accountsData.Create(modelo, organization); // Evaluación if (response.Response != Responses.Success) @@ -124,7 +124,7 @@ public async Task>> ReadAll([FromH // Obtiene los miembros. - var members = await Data.DirectoryMembers.ReadMembersByOrg(organization); + var members = await directoryMembersData.ReadMembersByOrg(organization); // Error al obtener los integrantes. if (members.Response != Responses.Success) diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 11745dd..dc6d194 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -2,7 +2,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [Route("[controller]")] -public class OrganizationsController : ControllerBase +public class OrganizationsController(Data.Organizations organizationsData, Data.DirectoryMembers directoryMembersData) : ControllerBase { @@ -38,7 +38,7 @@ public async Task Create([FromBody] OrganizationModel modelo // Creación de la organización. - var response = await Data.Organizations.Create(modelo); + var response = await organizationsData.Create(modelo); // Evaluación. if (response.Response != Responses.Success) @@ -75,7 +75,7 @@ public async Task> ReadOneByID([FromQuery // Obtiene la organización - var response = await Data.Organizations.Read(id); + var response = await organizationsData.Read(id); // Organización no encontrada. if (response.Response != Responses.Success) @@ -89,7 +89,7 @@ public async Task> ReadOneByID([FromQuery if (!response.Model.IsPublic) { - var iamIn = await Data.DirectoryMembers.IamIn(tokenInfo.IdentityId, response.Model.Id); + var iamIn = await directoryMembersData.IamIn(tokenInfo.IdentityId, response.Model.Id); if (iamIn.Response != Responses.Success) return new ReadOneResponse() @@ -134,7 +134,7 @@ public async Task> ReadAll([FromHeader] s // Obtiene la organización - var response = await Data.Organizations.ReadAll(tokenInfo.IdentityId); + var response = await organizationsData.ReadAll(tokenInfo.IdentityId); return response; diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index b769a42..964aeb1 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -1,27 +1,82 @@ namespace LIN.Cloud.Identity.Data; -public partial class Accounts +public class Accounts(DataContext context) { /// - /// Crear nueva cuenta. + /// Crear nueva cuenta. [Transacción] /// /// Modelo de la cuenta. /// Id de la organización. - public static async Task> Create(AccountModel modelo, int organization) + public async Task> Create(AccountModel modelo, int organization = 0) { - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); + // Pre. + modelo.Id = 0; + modelo.IdentityId = 0; - // Función. - var response = await Create(modelo, context, organization); + // Transacción. + using var transaction = context.Database.BeginTransaction(); - // Retornar. - context.Close(contextKey); - return response; + try + { + + // Guardar la cuenta. + await context.Accounts.AddAsync(modelo); + context.SaveChanges(); + + // Si la organización existe. + if (organization > 0) + { + + var generalGroup = (from org in context.Organizations + where org.Id == organization + select org.DirectoryId).FirstOrDefault(); + + if (generalGroup <= 0) + { + throw new Exception("Organización no encontrada."); + } + + var x = new GroupMember() + { + Group = new() + { + Id = generalGroup + }, + Identity = modelo.Identity, + Type = GroupMemberTypes.User + }; + + context.Attach(x.Group); + + context.GroupMembers.Add(x); + + context.SaveChanges(); + + } + + + // Confirmar los cambios. + transaction.Commit(); + + return new() + { + Response = Responses.Success, + Model = modelo + }; + + } + catch (Exception) + { + transaction.Rollback(); + return new() + { + Response = Responses.ExistAccount + }; + } } @@ -32,18 +87,37 @@ public static async Task> Create(AccountModel mode /// /// Id de la cuenta. /// Filtros de búsqueda. - public static async Task> Read(int id, Services.Models.QueryAccountFilter filters) + public async Task> Read(int id, QueryAccountFilter filters) { - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Read(id, filters, context); - - // Retornar. - context.Close(contextKey); - return response; + try + { + + // Consulta de las cuentas. + var account = await Builders.Account.GetAccounts(id, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } } @@ -54,39 +128,78 @@ public static async Task> Read(int id, Services.Mo /// /// Único. /// Filtros de búsqueda. - public static async Task> Read(string unique, Services.Models.QueryAccountFilter filters) + public async Task> Read(string unique, QueryAccountFilter filters) { - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Read(unique, filters, context); - - // Retornar. - context.Close(contextKey); - return response; + try + { + + // Consulta de las cuentas. + var account = await Builders.Account.GetAccounts(unique, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } } /// - /// Obtener una cuenta según el identificador único. + /// Obtener una cuenta según el id de la identidad. /// /// Id de la identidad. /// Filtros de búsqueda. - public static async Task> ReadByIdentity(int id, Services.Models.QueryAccountFilter filters) + public async Task> ReadByIdentity(int id, QueryAccountFilter filters) { - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - // Función. - var response = await ReadByIdentity(id, filters, context); - - // Retornar. - context.Close(contextKey); - return response; + try + { + + // Consulta de las cuentas. + var account = await Builders.Account.GetAccountsByIdentity(id, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (account == null) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = account + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } } @@ -97,17 +210,26 @@ public static async Task> ReadByIdentity(int id, S /// /// patron de búsqueda /// Filtros - public static async Task> Search(string pattern, QueryAccountFilter filters) + public async Task> Search(string pattern, QueryAccountFilter filters) { - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - // Función. - var response = await Search(pattern, filters, context); + // Ejecución + try + { + + List accountModels = await Builders.Account.Search(pattern, filters, context).Take(10).ToListAsync(); + + // Si no existe el modelo + if (accountModels == null) + return new(Responses.NotRows); - // Retornar. - context.Close(contextKey); - return response; + return new(Responses.Success, accountModels); + } + catch (Exception) + { + } + + return new(); } @@ -116,19 +238,31 @@ public static async Task> Search(string pattern, Q /// Obtiene los usuarios con IDs coincidentes /// /// Lista de IDs - /// Filtros. - public static async Task> FindAll(List ids, QueryAccountFilter filters) + public async Task> FindAll(List ids, QueryAccountFilter filters) { - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - // Función. - var response = await FindAll(ids, filters, context); + // Ejecución + try + { + + var query = Builders.Account.FindAll(ids, filters, context); + + // Ejecuta + var result = await query.ToListAsync(); - // Retornar. - context.Close(contextKey); - return response; + // Si no existe el modelo + if (result == null) + return new(Responses.NotRows); + + return new(Responses.Success, result); + } + catch (Exception) + { + } + + return new(); } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Accounts.cs.cs b/LIN.Cloud.Identity/Data/Accounts.cs.cs deleted file mode 100644 index 801563b..0000000 --- a/LIN.Cloud.Identity/Data/Accounts.cs.cs +++ /dev/null @@ -1,274 +0,0 @@ -namespace LIN.Cloud.Identity.Data; - - -public partial class Accounts -{ - - - - /// - /// Crear nueva cuenta. [Transacción] - /// - /// Modelo de la cuenta. - /// Contexto de conexión. - public static async Task> Create(AccountModel modelo, DataContext context, int organization = 0) - { - - // Pre. - modelo.Id = 0; - modelo.IdentityId = 0; - - // Transacción. - using var transaction = context.Database.BeginTransaction(); - - try - { - - // Guardar la cuenta. - await context.Accounts.AddAsync(modelo); - context.SaveChanges(); - - // Si la organización existe. - if (organization > 0) - { - - var generalGroup = (from org in context.Organizations - where org.Id == organization - select org.DirectoryId).FirstOrDefault(); - - if (generalGroup <= 0) - { - throw new Exception("Organización no encontrada."); - } - - var x = new GroupMember() - { - Group = new() - { - Id = generalGroup - }, - Identity = modelo.Identity, - Type = GroupMemberTypes.User - }; - - context.Attach(x.Group); - - context.GroupMembers.Add(x); - - context.SaveChanges(); - - } - - - // Confirmar los cambios. - transaction.Commit(); - - return new() - { - Response = Responses.Success, - Model = modelo - }; - - } - catch (Exception) - { - transaction.Rollback(); - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Obtener una cuenta según el Id. - /// - /// Id de la cuenta. - /// Filtros de búsqueda. - /// Contexto de base de datos. - public static async Task> Read(int id, QueryAccountFilter filters, DataContext context) - { - - try - { - - // Consulta de las cuentas. - var account = await Builders.Account.GetAccounts(id, filters, context).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Obtener una cuenta según el identificador único. - /// - /// Único. - /// Filtros de búsqueda. - /// Contexto de base de datos. - public static async Task> Read(string unique, QueryAccountFilter filters, DataContext context) - { - - try - { - - // Consulta de las cuentas. - var account = await Builders.Account.GetAccounts(unique, filters, context).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Obtener una cuenta según el id de la identidad. - /// - /// Id de la identidad. - /// Filtros de búsqueda. - /// Contexto de base de datos. - public static async Task> ReadByIdentity(int id, QueryAccountFilter filters, DataContext context) - { - - try - { - - // Consulta de las cuentas. - var account = await Builders.Account.GetAccountsByIdentity(id, filters, context).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Buscar por patron. - /// - /// patron de búsqueda - /// Filtros - /// Contexto de conexión - public static async Task> Search(string pattern, QueryAccountFilter filters, DataContext context) - { - - // Ejecución - try - { - - List accountModels = await Builders.Account.Search(pattern, filters, context).Take(10).ToListAsync(); - - // Si no existe el modelo - if (accountModels == null) - return new(Responses.NotRows); - - return new(Responses.Success, accountModels); - } - catch - { - } - - return new(); - } - - - - /// - /// Obtiene los usuarios con IDs coincidentes - /// - /// Lista de IDs - /// Contexto de base de datos - public static async Task> FindAll(List ids, QueryAccountFilter filters, DataContext context) - { - - // Ejecución - try - { - - var query = Builders.Account.FindAll(ids, filters, context); - - // Ejecuta - var result = await query.ToListAsync(); - - // Si no existe el modelo - if (result == null) - return new(Responses.NotRows); - - return new(Responses.Success, result); - } - catch - { - } - - return new(); - } - - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/DirectoryMembers.cs b/LIN.Cloud.Identity/Data/DirectoryMembers.cs index a96b74d..6d6cf3c 100644 --- a/LIN.Cloud.Identity/Data/DirectoryMembers.cs +++ b/LIN.Cloud.Identity/Data/DirectoryMembers.cs @@ -3,154 +3,17 @@ namespace LIN.Cloud.Identity.Data; -public static class DirectoryMembers +public class DirectoryMembers(DataContext context) { - - #region Abstracciones - - - - /// - /// Obtener los directorios (Grupos de organización) donde una identidad pertenece. - /// - /// Id de la identidad - public static async Task> Read(int id, int organization) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Read(id, organization, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Valida si una identidad es miembro de una organización. - /// - /// Identidad - /// Id de la organización - public static async Task> IamIn(int id, int organization) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await IamIn(id, organization, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - - - - - public static async Task> IamIn(List id, int organization) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await IamIn(id, organization, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Valida si una identidad es miembro de una organización. - /// - /// Identidad - /// Id de la organización - public static async Task> IamInByDir(int id, int directory) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await IamInByDir(id, directory, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// - /// - /// Id del directorio - public static async Task> ReadMembers(int id) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await ReadMembers(id, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - public static async Task>> ReadMembersByOrg(int id) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await ReadMembersByOrg(id, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - - #endregion - - - - - - /// /// Obtener los directorios (Grupos de organización) donde una identidad pertenece. /// /// Identidad /// Organización de contexto. /// Contexto. - public static async Task> Read(int id, int organization, DataContext context) + public async Task> Read(int id, int organization) { try @@ -198,19 +61,13 @@ on gm.GroupId equals o.DirectoryId - - - - - - /// /// Valida si una identidad es miembro de una organización. /// /// Identidad /// Id de la organización /// Contexto - public static async Task> IamIn(int id, int organization, DataContext context) + public async Task> IamIn(int id, int organization) { try @@ -255,14 +112,13 @@ on org.DirectoryId equals gm.GroupId - /// /// Valida si una lista de identidades son miembro de una organización. /// /// Identidades /// Id de la organización /// Contexto - public static async Task> IamIn(List ids, int organization, DataContext context) + public async Task> IamIn(List ids, int organization) { try @@ -310,7 +166,7 @@ where ids.Contains(gm.IdentityId) /// Identidad /// Id del directorio /// Contexto - public static async Task> IamInByDir(int id, int directory, DataContext context) + public async Task> IamInByDir(int id, int directory) { try @@ -360,7 +216,7 @@ on org.DirectoryId equals gm.GroupId /// /// Directorio /// Contexto - public static async Task> ReadMembers(int id, DataContext context) + public async Task> ReadMembers(int id) { try @@ -412,7 +268,7 @@ on org.DirectoryId equals gm.GroupId /// /// Directorio /// Contexto - public static async Task>> ReadMembersByOrg(int id, DataContext context) + public async Task>> ReadMembersByOrg(int id) { try @@ -426,19 +282,19 @@ join a in context.Accounts on gm.IdentityId equals a.IdentityId select new SessionModel { - Account = new() - { - Id = a.Id, - Name = a.Name, - Visibility = a.Visibility, - IdentityService = a.IdentityService, - Identity = new() - { - Id = a.Identity.Id, - Unique = a.Identity.Unique - } - }, - Profile = gm + Account = new() + { + Id = a.Id, + Name = a.Name, + Visibility = a.Visibility, + IdentityService = a.IdentityService, + Identity = new() + { + Id = a.Identity.Id, + Unique = a.Identity.Unique + } + }, + Profile = gm }).ToListAsync(); @@ -468,6 +324,4 @@ on gm.IdentityId equals a.IdentityId } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/GroupMembers.cs b/LIN.Cloud.Identity/Data/GroupMembers.cs index cfd985f..9965e1a 100644 --- a/LIN.Cloud.Identity/Data/GroupMembers.cs +++ b/LIN.Cloud.Identity/Data/GroupMembers.cs @@ -1,175 +1,16 @@ namespace LIN.Cloud.Identity.Data; -public static class GroupMembers +public class GroupMembers(DataContext context) { - - #region Abstracciones - - - - /// - /// Crear nuevo integrante en un grupo. - /// - /// Modelo. - public static async Task Create(GroupMember modelo) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnectionForce(); - - // Función. - var response = await Create(modelo, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Crear nuevos integrantes en un grupo. - /// - /// Modelos. - public static async Task Create(IEnumerable modelos) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Create(modelos, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener los miembros de un grupo. - /// - /// Id del grupo - public static async Task> ReadAll(int id) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await ReadAll(id, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Buscar en los integrantes de un grupo. - /// - /// Patron de búsqueda. - /// Id del grupo. - public static async Task> Search(string pattern, int group) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Search(pattern, group, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - - /// - /// Buscar en los integrantes de un grupo. - /// - /// Patron de búsqueda. - /// Id del grupo. - public static async Task> SearchGroups(string pattern, int group) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await SearchGroups(pattern, group, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Eliminar un integrante de un grupo. - /// - /// Identidad. - /// Id del grupo. - public static async Task Delete(int identity, int group) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Delete(identity, group, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - - /// - /// Obtener los grupos donde una identidad esta de integrante - /// - /// Id del grupo - public static async Task> OnMembers(int organization, int identity) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await OnMembers(organization, identity, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - #endregion - - - /// /// Crear nuevo integrante en un grupo. /// /// Modelo. /// Contexto de conexión. - public static async Task Create(GroupMember modelo, DataContext context) + public async Task Create(GroupMember modelo) { try { @@ -205,7 +46,7 @@ public static async Task Create(GroupMember modelo, DataContext /// /// Modelos. /// Contexto de conexión. - public static async Task Create(IEnumerable modelos, DataContext context) + public async Task Create(IEnumerable modelos) { try { @@ -258,7 +99,7 @@ INSERT INTO [dbo].[GROUPS_MEMBERS] /// /// Id del grupo. /// Contexto de base de datos. - public static async Task> ReadAll(int id, DataContext context) + public async Task> ReadAll(int id) { try @@ -309,7 +150,7 @@ public static async Task> ReadAll(int id, DataConte /// Patron de búsqueda. /// Id del grupo. /// Contexto de base de datos. - public static async Task> Search(string pattern, int group, DataContext context) + public async Task> Search(string pattern, int group) { try @@ -355,7 +196,7 @@ public static async Task> Search(string pattern, /// Patron de búsqueda. /// Id del grupo. /// Contexto de base de datos. - public static async Task> SearchGroups(string pattern, int group, DataContext context) + public async Task> SearchGroups(string pattern, int group) { try @@ -402,7 +243,7 @@ public static async Task> SearchGroups(string pat /// Identidad. /// Id del grupo. /// Contexto de base de datos. - public static async Task Delete(int identity, int group, DataContext context) + public async Task Delete(int identity, int group) { try @@ -440,7 +281,7 @@ public static async Task Delete(int identity, int group, DataConte /// /// /// - public static async Task> OnMembers(int organization, int identity, DataContext context) + public async Task> OnMembers(int organization, int identity) { try diff --git a/LIN.Cloud.Identity/Data/Groups.cs b/LIN.Cloud.Identity/Data/Groups.cs index 2d630aa..a4e71cf 100644 --- a/LIN.Cloud.Identity/Data/Groups.cs +++ b/LIN.Cloud.Identity/Data/Groups.cs @@ -1,152 +1,16 @@ namespace LIN.Cloud.Identity.Data; -public static class Groups +public class Groups (DataContext context) { - - #region Abstracciones - - - - /// - /// Crear un nuevo grupo. - /// - /// Modelo. - public static async Task> Create(GroupModel modelo) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Create(modelo, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener un grupo según el Id. - /// - /// Id. - public static async Task> Read(int id) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Read(id, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - - /// - /// Obtener un grupo según el Id de la identidad. - /// - /// Id. - public static async Task> ReadByIdentity(int id) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await ReadByIdentity(id, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener los grupos asociados a una organización. - /// - /// Id de la organización. - public static async Task> ReadAll(int organization) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await ReadAll(organization, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener la organización propietaria de un grupo. - /// - /// Id del grupo. - public static async Task> GetOwner(int group) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await GetOwner(group, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener la organización propietaria de un grupo. - /// - /// Id de la identidad del grupo. - public static async Task> GetOwnerByIdentity(int identity) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await GetOwnerByIdentity(identity, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - #endregion - - - /// /// Crear nuevo grupo. /// /// Modelo. /// Contexto de conexión. - public static async Task> Create(GroupModel modelo, DataContext context) + public async Task> Create(GroupModel modelo) { // Pre. modelo.Id = 0; @@ -244,7 +108,7 @@ public static async Task> Create(GroupModel modelo, /// /// Id. /// Contexto de base de datos. - public static async Task> Read(int id, DataContext context) + public async Task> Read(int id) { try @@ -293,7 +157,7 @@ public static async Task> Read(int id, DataContext c /// /// Identidad. /// Contexto de base de datos. - public static async Task> ReadByIdentity(int id, DataContext context) + public async Task> ReadByIdentity(int id) { try @@ -342,7 +206,7 @@ public static async Task> ReadByIdentity(int id, Dat /// /// Organización. /// Contexto de base de datos. - public static async Task> ReadAll(int organization, DataContext context) + public async Task> ReadAll(int organization) { try @@ -391,7 +255,7 @@ public static async Task> ReadAll(int organization, /// /// Id del grupo. /// Contexto de base de datos. - public static async Task> GetOwner(int id, DataContext context) + public async Task> GetOwner(int id) { try @@ -434,7 +298,7 @@ public static async Task> GetOwner(int id, DataContext cont /// /// Id de la identidad. /// Contexto de base de datos. - public static async Task> GetOwnerByIdentity(int id, DataContext context) + public async Task> GetOwnerByIdentity(int id) { try diff --git a/LIN.Cloud.Identity/Data/Identities.cs b/LIN.Cloud.Identity/Data/Identities.cs index 7107668..2d1cf3e 100644 --- a/LIN.Cloud.Identity/Data/Identities.cs +++ b/LIN.Cloud.Identity/Data/Identities.cs @@ -1,90 +1,16 @@ namespace LIN.Cloud.Identity.Data; -public static class Identities +public class Identities(DataContext context) { - - #region Abstracciones - - - - /// - /// Crear nueva identidad. - /// - /// Modelo. - public static async Task> Create(IdentityModel modelo) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Create(modelo, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener una identidad según el Id. - /// - /// Id. - /// Filtros de búsqueda. - public static async Task> Read(int id, Services.Models.QueryIdentityFilter filters) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Read(id, filters, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener una identidad según el Unique. - /// - /// Unique. - /// Filtros de búsqueda. - public static async Task> Read(string unique, Services.Models.QueryIdentityFilter filters) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Read(unique, filters, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - #endregion - - - /// /// Crear nueva identidad. /// /// Modelo. /// Contexto de conexión. - public static async Task> Create(IdentityModel modelo, DataContext context) + public async Task> Create(IdentityModel modelo) { // Pre. modelo.Id = 0; @@ -124,7 +50,7 @@ public static async Task> Create(IdentityModel mo /// Id de la identidad. /// Filtros de búsqueda. /// Contexto de base de datos. - public static async Task> Read(int id, QueryIdentityFilter filters, DataContext context) + public async Task> Read(int id, QueryIdentityFilter filters) { try @@ -166,7 +92,7 @@ public static async Task> Read(int id, QueryIdent /// Unique. /// Filtros de búsqueda. /// Contexto de base de datos. - public static async Task> Read(string unique, QueryIdentityFilter filters, DataContext context) + public async Task> Read(string unique, QueryIdentityFilter filters) { try diff --git a/LIN.Cloud.Identity/Data/IdentityRoles.cs b/LIN.Cloud.Identity/Data/IdentityRoles.cs index 3db39c1..07d2387 100644 --- a/LIN.Cloud.Identity/Data/IdentityRoles.cs +++ b/LIN.Cloud.Identity/Data/IdentityRoles.cs @@ -1,91 +1,16 @@ namespace LIN.Cloud.Identity.Data; -public static class IdentityRoles +public class IdentityRoles(DataContext context) { - - #region Abstracciones - - - - /// - /// Crear nuevo rol. - /// - /// Modelo. - public static async Task Create(IdentityRolesModel modelo) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Create(modelo, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener los roles asociados a una identidad en una organización determinada. - /// - /// Identidad. - /// Organización. - public static async Task> ReadAll(int identity, int organization) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await ReadAll(identity, organization, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Eliminar el rol de una identidad en una organización. - /// - /// Identidad. - /// Rol. - /// Organización. - public static async Task Remove(int identity, Roles rol, int organization) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Remove(identity, rol, organization, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - #endregion - - - /// /// Crear nuevo rol en identidad. /// /// Modelo. /// Contexto de conexión. - public static async Task Create(IdentityRolesModel modelo, DataContext context) + public async Task Create(IdentityRolesModel modelo) { try @@ -124,7 +49,7 @@ public static async Task Create(IdentityRolesModel modelo, DataCon /// Identidad. /// Organización. /// Contexto de base de datos. - public static async Task> ReadAll(int identity, int organization, DataContext context) + public async Task> ReadAll(int identity, int organization) { try @@ -132,7 +57,7 @@ public static async Task> ReadAll(int identi List Roles = []; - await RolesOn(identity, organization, context, [], Roles); + await RolesOn(identity, organization, [], Roles); return new() { @@ -161,7 +86,7 @@ public static async Task> ReadAll(int identi /// Rol. /// Organización. /// Contexto de base de datos. - public static async Task Remove(int identity, Roles rol, int organization, DataContext context) + public async Task Remove(int identity, Roles rol, int organization) { try @@ -203,7 +128,7 @@ public static async Task Remove(int identity, Roles rol, int organ - private static async Task RolesOn(int identity, int organization, DataContext context, List ids, List roles) + private async Task RolesOn(int identity, int organization, List ids, List roles) { var query = from id in context.Identities @@ -253,7 +178,7 @@ private static async Task RolesOn(int identity, int organization, DataContext co // Recorrer. foreach (var @base in bases) - await RolesOn(@base, organization, context, ids, roles); + await RolesOn(@base, organization, ids, roles); } diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index 5158922..233f16e 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -1,109 +1,16 @@ namespace LIN.Cloud.Identity.Data; -public static class Organizations +public class Organizations(DataContext context) { - - #region Abstracciones - - - - /// - /// Crear nueva organización. - /// - /// Modelo. - public static async Task Create(OrganizationModel modelo) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Create(modelo, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener una organización según el Id. - /// - /// Id de la identidad - public static async Task> Read(int id) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Read(id, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener las organizaciones donde un usuario es miembro. - /// - /// Id de la identidad - public static async Task> ReadAll(int id) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await ReadAll(id, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener el dominio de la organización. - /// - /// Id de la organización. - public static async Task> GetDomain(int id) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await GetDomain(id, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - #endregion - - - /// /// Crear nueva organización. [Transacción] /// /// Modelo. /// Contexto de conexión. - public static async Task Create(OrganizationModel modelo, DataContext context) + public async Task Create(OrganizationModel modelo) { using var transaction = context.Database.BeginTransaction(); @@ -214,7 +121,7 @@ public static async Task Create(OrganizationModel modelo, DataCo /// /// Id. /// Contexto de base de datos. - public static async Task> Read(int id, DataContext context) + public async Task> Read(int id) { try @@ -256,7 +163,7 @@ public static async Task> Read(int id, DataCo /// /// Identidad /// Contexto - public static async Task> ReadAll(int id, DataContext context) + public async Task> ReadAll(int id) { try @@ -302,7 +209,7 @@ on org.DirectoryId equals gm.GroupId /// /// Id de la organización. /// Contexto de base de datos. - public static async Task> GetDomain(int id, DataContext context) + public async Task> GetDomain(int id) { try diff --git a/LIN.Cloud.Identity/Data/PassKeys.cs b/LIN.Cloud.Identity/Data/PassKeys.cs index 5fd4086..190e358 100644 --- a/LIN.Cloud.Identity/Data/PassKeys.cs +++ b/LIN.Cloud.Identity/Data/PassKeys.cs @@ -3,66 +3,13 @@ namespace LIN.Cloud.Identity.Data; -public static class PassKeys +public class PassKeys(DataContext context) { - #region Abstracciones - - - - /// - /// Crear nueva identidad. - /// - /// Modelo. - public static async Task Create(PassKeyDBModel modelo) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Create(modelo, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - /// - /// Obtener una identidad según el Id. - /// - /// Id. - /// Filtros de búsqueda. - public static async Task> Count(int id) - { - - // Obtener conexión. - var (context, contextKey) = DataService.GetConnection(); - - // Función. - var response = await Count(id, context); - - // Retornar. - context.Close(contextKey); - return response; - - } - - - - - - #endregion - - - - public static async Task Create(PassKeyDBModel modelo, DataContext context) + public async Task Create(PassKeyDBModel modelo) { // Pre. modelo.Id = 0; @@ -93,7 +40,7 @@ public static async Task Create(PassKeyDBModel modelo, DataContext - public static async Task> Count(int id, DataContext context) + public async Task> Count(int id) { try diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index d78b337..2ffe29c 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -2,6 +2,7 @@ using Http.Extensions; using LIN.Cloud.Identity.Services.Auth.Interfaces; using LIN.Cloud.Identity.Persistence.Extensions; +using LIN.Cloud.Identity.Services.Extensions; var builder = WebApplication.CreateBuilder(args); @@ -20,10 +21,10 @@ // Servicios propios. builder.Services.AddIP(); +builder.Services.AddLocalServices(); // Servicio de autenticación. builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddPersistence(builder.Configuration); var app = builder.Build(); diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 2880ad9..b2a42fb 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Services.Auth; -public class Authentication : Interfaces.IAuthentication +public class Authentication (Data.Accounts accountData) : Interfaces.IAuthentication { @@ -80,7 +80,7 @@ private async Task GetAccount() { // Obtener la cuenta. - var account = await Data.Accounts.Read(User, new() + var account = await accountData.Read(User, new() { FindOn = FindOn.StableAccounts, IsAdmin = true diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs new file mode 100644 index 0000000..8eb36f8 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -0,0 +1,30 @@ +namespace LIN.Cloud.Identity.Services.Extensions; + + +public static class LocalServices +{ + + + + /// + /// Agregar servicios locales. + /// + /// Services. + public static IServiceCollection AddLocalServices(this IServiceCollection services) + { + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 7f14f58..1433dd5 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -26,7 +26,7 @@ public static (bool pass, string message) Validate(AccountModel baseAccount) if (!ValidarCadena(baseAccount.Identity.Unique)) - return (false, "La identidad de la cuenta no puede contener simbolos NO alfanumericos."); + return (false, "La identidad de la cuenta no puede contener símbolos NO alfanuméricos."); return (true, ""); From c21c5dd06452c54f14c2d2b10ebc9e61ec30cd7e Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 30 May 2024 14:23:06 -0500 Subject: [PATCH 080/178] =?UTF-8?q?Soluci=C3=B3n=20de=20errores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contexts/DataContext.cs | 16 ++++++++-------- .../Extensions/PersistenceExtensions.cs | 4 ++-- .../Queries/AccountFindable.cs | 2 +- .../Areas/Accounts/AccountController.cs | 2 -- .../Areas/Directories/DirectoryController.cs | 4 ++-- .../Areas/Groups/GroupsController.cs | 10 +++++----- .../Areas/Groups/GroupsMembersController.cs | 16 ++++++++-------- .../Areas/Organizations/IdentityController.cs | 8 ++++---- .../Areas/Organizations/MemberController.cs | 6 +++--- .../Services/Extensions/LocalServices.cs | 18 ++++++++++-------- LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 14 ++++---------- .../Services/Realtime/PassKeyHub.cs | 4 ++-- 12 files changed, 49 insertions(+), 55 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 92ab001..3544e49 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -53,14 +53,14 @@ public class DataContext(DbContextOptions options) : DbContext(opti - /// - /// Configuring database. - /// - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); - base.OnConfiguring(optionsBuilder); - } + ///// + ///// Configuring database. + ///// + //protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + //{ + // optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); + // base.OnConfiguring(optionsBuilder); + //} diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 15b325f..d8c42eb 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -24,8 +24,8 @@ public static IServiceCollection AddPersistence(this IServiceCollection services options.UseSqlServer(configuration.GetConnectionString("cloud")); }); - services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); return services; } diff --git a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs index 03b562b..f7c1155 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs @@ -66,7 +66,7 @@ public IQueryable GetAccounts(int id, Models.QueryObjectFilter fil select account; // Armar el modelo - accounts = BuildModel(accounts, filters, context); + accounts = null;// BuildModel(accounts, filters, context); // Retorno return accounts; diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 57ad508..f26d89d 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -123,8 +123,6 @@ public async Task> Read([FromQuery] string use // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - - // Obtiene el usuario. var response = await accountData.Read(user, new() { diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index a77f823..9b3dc6f 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -2,7 +2,7 @@ [Route("[controller]")] -public class DirectoryController (Data.DirectoryMembers directoryMembersData, Data.Groups groupsData) : ControllerBase +public class DirectoryController (Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : ControllerBase { @@ -69,7 +69,7 @@ public async Task> ReadMembers([FromHeader] str // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index e22a65f..b8e3dfb 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -2,7 +2,7 @@ [Route("[controller]")] -public class GroupsController(Data.Groups groupData) : ControllerBase +public class GroupsController(Data.Groups groupData, RolesIam rolesIam) : ControllerBase { @@ -20,7 +20,7 @@ public async Task Create([FromBody] GroupModel group, [FromH JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, group.OwnerId ?? 0); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, group.OwnerId ?? 0); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -72,7 +72,7 @@ public async Task> ReadAll([FromHeader] string t JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -129,7 +129,7 @@ public async Task> ReadOne([FromHeader] string t // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -186,7 +186,7 @@ public async Task> ReadIdentity([FromHeader] str // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index a72d4ab..9369104 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -2,7 +2,7 @@ [Route("Groups/members")] -public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers) : Controller +public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : Controller { @@ -32,7 +32,7 @@ public async Task Create([FromHeader] string token, [FromBod // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -94,7 +94,7 @@ public async Task Create([FromHeader] string token, [FromHea // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -169,7 +169,7 @@ public async Task> ReadMembers([FromHeader] str // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -227,7 +227,7 @@ public async Task> Search([FromHeader] string // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -287,7 +287,7 @@ public async Task> SearchOnGroups([FromHeader // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -345,7 +345,7 @@ public async Task DeleteMembers([FromHeader] string token, [Fr // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -391,7 +391,7 @@ public async Task> OnMembers([FromHeader] string // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 73cc269..8c4b103 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -2,7 +2,7 @@ [Route("[controller]")] -public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData) : ControllerBase +public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData, RolesIam rolesIam) : ControllerBase { @@ -20,7 +20,7 @@ public async Task Create([FromBody] IdentityRolesModel rolMode JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, rolModel.OrganizationId); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, rolModel.OrganizationId); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -81,7 +81,7 @@ public async Task> ReadAll([FromHeader] JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -141,7 +141,7 @@ public async Task ReadAll([FromHeader] string token, [FromHead JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs index 6fd1f2f..d8262ea 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [Route("orgs/members")] -public class MemberController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData) : ControllerBase +public class MemberController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, RolesIam rolesIam) : ControllerBase { @@ -61,7 +61,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr modelo.Identity.Unique = $"{modelo.Identity.Unique}@{orgIdentity.Model.Unique}"; // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -109,7 +109,7 @@ public async Task>> ReadAll([FromH // Confirmar el rol. - var (_, roles) = await RolesIam.RolesOn(tokenInfo.IdentityId, organization); + var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 8eb36f8..c4bdac8 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -13,14 +13,16 @@ public static class LocalServices public static IServiceCollection AddLocalServices(this IServiceCollection services) { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); return services; diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index af611ae..4fada6c 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -1,17 +1,15 @@ namespace LIN.Cloud.Identity.Services.Iam; -public class RolesIam +public class RolesIam(DataContext context) { - public static async Task<(List identities, List roles)> RolesOnByDir(int identity, int directory) + public async Task<(List identities, List roles)> RolesOnByDir(int identity, int directory) { - var (context, contextKey) = DataService.GetConnection(); - var query = await (from org in context.Organizations where org.DirectoryId == directory select org.Id).FirstOrDefaultAsync(); @@ -27,28 +25,24 @@ public class RolesIam await RolesOn(identity, query, context, identities, roles); - context.Close(contextKey); return (identities, roles); } - public static async Task<(List identities, List roles)> RolesOn(int identity, int organization) + public async Task<(List identities, List roles)> RolesOn(int identity, int organization) { List identities = [identity]; List roles = []; - var (context, contextKey) = DataService.GetConnection(); - await RolesOn(identity, organization, context, identities, roles); - context.Close(contextKey); return (identities, roles); } - private static async Task RolesOn(int identity, int organization, DataContext context, List ids, List roles) + private async Task RolesOn(int identity, int organization, DataContext context, List ids, List roles) { diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 35c7c7d..03e88c0 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -3,7 +3,7 @@ namespace LIN.Cloud.Identity.Services.Realtime; -public partial class PassKeyHub : Hub +public partial class PassKeyHub (Data.PassKeys passKeysData) : Hub { @@ -197,7 +197,7 @@ public async Task ReceiveRequest(PassKeyModel modelo) - _ = Data.PassKeys.Create(new PassKeyDBModel + _ = passKeysData.Create(new PassKeyDBModel { AccountId = info.AccountId, Id = 0, From 0565780ff2af28360a343582185c83a7f5526ae2 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 30 May 2024 14:27:20 -0500 Subject: [PATCH 081/178] Ruta de obtener por Ids de indentidades --- .../Areas/Accounts/AccountController.cs | 4 +- LIN.Cloud.Identity/Data/Accounts.cs | 34 +++++++++++++++ LIN.Cloud.Identity/Data/Builders/Account.cs | 43 +++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index f26d89d..e3b078c 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -106,7 +106,7 @@ public async Task> Read([FromQuery] int id, [F /// /// Obtener una cuenta. /// - /// Identidad. + /// Identidad única. /// Token de acceso. [HttpGet("read/user")] [IdentityToken] @@ -204,7 +204,7 @@ public async Task> ReadByIdentity([FromBody] L JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Obtiene el usuario - var response = await accountData.FindAll(ids, new() + var response = await accountData.FindAllByIdentities(ids, new() { AccountContext = tokenInfo.AccountId, FindOn = FindOn.StableAccounts, diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index 964aeb1..8b36326 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -265,4 +265,38 @@ public async Task> FindAll(List ids, QueryAcc + + + + /// + /// Obtiene los usuarios con IDs coincidentes + /// + /// Lista de IDs + public async Task> FindAllByIdentities(List ids, QueryAccountFilter filters) + { + + // Ejecución + try + { + + var query = Builders.Account.FindAllByIdentities(ids, filters, context); + + // Ejecuta + var result = await query.ToListAsync(); + + // Si no existe el modelo + if (result == null) + return new(Responses.NotRows); + + return new(Responses.Success, result); + } + catch (Exception) + { + } + + return new(); + } + + + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Builders/Account.cs b/LIN.Cloud.Identity/Data/Builders/Account.cs index be55542..0272adb 100644 --- a/LIN.Cloud.Identity/Data/Builders/Account.cs +++ b/LIN.Cloud.Identity/Data/Builders/Account.cs @@ -46,6 +46,18 @@ public static IQueryable OnAll(DataContext context) + + + + + + + + + + + + /// /// Obtener cuentas. /// @@ -186,6 +198,37 @@ where ids.Contains(account.Id) } + + + + + + public static IQueryable FindAllByIdentities(IEnumerable ids, QueryAccountFilter filters, DataContext context) + { + IQueryable accounts; + + if (filters.FindOn == FindOn.StableAccounts) + { + accounts = from account in OnStable(context) + where ids.Contains(account.IdentityId) + select account; + } + else + { + accounts = from account in OnAll(context) + where ids.Contains(account.IdentityId) + select account; + } + + + // Armar el modelo. + accounts = BuildModel(accounts, filters, context); + + // Retorno + return accounts; + + } + private static readonly byte[] selector = []; From f01ffc2c5f1cce89b496346f8b991f4c867c3307 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 31 May 2024 16:26:49 -0500 Subject: [PATCH 082/178] Https --- LIN.Cloud.Identity/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 2ffe29c..a66e738 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -40,7 +40,6 @@ // Hub. app.MapHub("/realTime/auth/passkey"); -app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); From 69cacd182fe7a03768534a5d12d6c7c474473a70 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 18 Jun 2024 09:45:51 -0500 Subject: [PATCH 083/178] Update --- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs | 1 - .../Areas/Organizations/OrganizationController.cs | 1 - LIN.Cloud.Identity/Data/Accounts.cs | 3 --- LIN.Cloud.Identity/Data/Organizations.cs | 1 - LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs | 4 ++++ 7 files changed, 6 insertions(+), 8 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index f1b04e0..838037b 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index 9369104..63bbd9d 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -30,7 +30,6 @@ public async Task Create([FromHeader] string token, [FromBod Response = Responses.Unauthorized }; - // Confirmar el rol. var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index dc6d194..04c509a 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -22,7 +22,6 @@ public async Task Create([FromBody] OrganizationModel modelo Message = "Parámetros inválidos." }; - // Ordenar el modelo. { modelo.Id = 0; diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index 8b36326..d1543bb 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -265,9 +265,6 @@ public async Task> FindAll(List ids, QueryAcc - - - /// /// Obtiene los usuarios con IDs coincidentes /// diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index 233f16e..e855048 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -27,7 +27,6 @@ public async Task Create(OrganizationModel modelo) await context.Organizations.AddAsync(modelo); - context.SaveChanges(); diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 618f8ec..80ff2ac 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -8,7 +8,7 @@ - + diff --git a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs index bda30b3..36805a0 100644 --- a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs +++ b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs @@ -29,6 +29,10 @@ await context.Response.WriteAsJsonAsync(new ResponseBase // Item de IP. context.Items.Add("IP", ip); await next(context); + + // Headers. + // context.Response.Headers.Append("client-ip", ip.MapToIPv4().ToString()); + } From c17e208a1ab5423c42d5638bf176ce88d2b90fa6 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 27 Jun 2024 02:39:14 -0500 Subject: [PATCH 084/178] Clean code --- .../Queries/AccountFindable.cs | 2 +- LIN.Cloud.Identity.sln | 8 +- .../Areas/Groups/GroupsMembersController.cs | 1 - .../Areas/Organizations/IdentityController.cs | 3 - .../Areas/Organizations/MemberController.cs | 2 - .../Organizations/OrganizationController.cs | 2 - LIN.Cloud.Identity/Data/GroupMembers.cs | 11 +-- .../Properties/launchSettings.json | 8 +- .../Services/Formats/Account.cs | 7 +- LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 40 +++++----- .../Services/Middlewares/IPMiddleware.cs | 7 +- .../Services/Middlewares/QuotaMiddleware.cs | 28 +++---- .../Services/Realtime/PassKeyHub.cs | 3 +- .../Services/Realtime/PassKeyHubActions.cs | 2 - LIN.Identity.Tests/LIN.Identity.Tests.csproj | 37 +++++++++ LIN.Identity.Tests/Validations.cs | 77 +++++++++++++++++++ 16 files changed, 174 insertions(+), 64 deletions(-) create mode 100644 LIN.Identity.Tests/LIN.Identity.Tests.csproj create mode 100644 LIN.Identity.Tests/Validations.cs diff --git a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs index f7c1155..d5bb398 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs @@ -66,7 +66,7 @@ public IQueryable GetAccounts(int id, Models.QueryObjectFilter fil select account; // Armar el modelo - accounts = null;// BuildModel(accounts, filters, context); + accounts = null!;// BuildModel(accounts, filters, context); // Retorno return accounts; diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index 1a4cb89..664b321 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -11,7 +11,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http", "..\..\Tipos\Http\Ht EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types", "..\..\Tipos\LIN.Types\LIN.Types.csproj", "{E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LIN.Cloud.Identity.Persistence", "LIN.Cloud.Identity.Persistence\LIN.Cloud.Identity.Persistence.csproj", "{6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity.Persistence", "LIN.Cloud.Identity.Persistence\LIN.Cloud.Identity.Persistence.csproj", "{6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LIN.Identity.Tests", "LIN.Identity.Tests\LIN.Identity.Tests.csproj", "{157863A4-209B-42A0-AE80-F6C403692480}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,6 +41,10 @@ Global {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.ActiveCfg = Release|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.Build.0 = Release|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.Build.0 = Debug|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Release|Any CPU.ActiveCfg = Release|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index 63bbd9d..520af7a 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -258,7 +258,6 @@ public async Task> Search([FromHeader] string - /// /// Buscar en los grupos de un grupo. /// diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 8c4b103..279464e 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -65,7 +65,6 @@ public async Task Create([FromBody] IdentityRolesModel rolMode - /// /// Obtener los roles asociados a una identidad. /// @@ -124,7 +123,6 @@ public async Task> ReadAll([FromHeader] - /// /// Eliminar los roles asociados a una identidad. /// @@ -184,5 +182,4 @@ public async Task ReadAll([FromHeader] string token, [FromHead - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs index d8262ea..8fe7a00 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs @@ -30,8 +30,6 @@ public async Task Create([FromBody] AccountModel modelo, [Fr // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - - // Ajustar el modelo. modelo.Visibility = Visibility.Hidden; modelo.Password = $"pwd@{DateTime.Now.Year}"; diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 04c509a..37e1787 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -131,11 +131,9 @@ public async Task> ReadAll([FromHeader] s // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene la organización var response = await organizationsData.ReadAll(tokenInfo.IdentityId); - return response; } diff --git a/LIN.Cloud.Identity/Data/GroupMembers.cs b/LIN.Cloud.Identity/Data/GroupMembers.cs index 9965e1a..6ce53e2 100644 --- a/LIN.Cloud.Identity/Data/GroupMembers.cs +++ b/LIN.Cloud.Identity/Data/GroupMembers.cs @@ -56,7 +56,7 @@ public async Task Create(IEnumerable modelos) { try { - context.Database.ExecuteSqlRaw(""" + await context.Database.ExecuteSqlRawAsync(""" INSERT INTO [dbo].[GROUPS_MEMBERS] ([IdentityId] ,[GroupId] @@ -68,12 +68,9 @@ INSERT INTO [dbo].[GROUPS_MEMBERS] """, member.Identity.Id, member.Group.Id, (int)GroupMemberTypes.User); } - catch (Exception ex) + catch (Exception) { - } - - } return new() @@ -159,7 +156,7 @@ public async Task> Search(string pattern, int gro // Consulta. var members = await (from g in context.GroupMembers where g.GroupId == @group - && g.Identity.Unique.ToLower().Contains(pattern.ToLower()) + && g.Identity.Unique.Contains(pattern, StringComparison.CurrentCultureIgnoreCase) select g.Identity).ToListAsync(); @@ -205,7 +202,7 @@ public async Task> SearchGroups(string pattern, i // Consulta. var members = await (from g in context.GroupMembers where g.GroupId == @group - && g.Identity.Unique.ToLower().Contains(pattern.ToLower()) + && g.Identity.Unique.Contains(pattern, StringComparison.CurrentCultureIgnoreCase) && g.Identity.Type == IdentityType.Group select g.Identity).ToListAsync(); diff --git a/LIN.Cloud.Identity/Properties/launchSettings.json b/LIN.Cloud.Identity/Properties/launchSettings.json index 89a6453..1319480 100644 --- a/LIN.Cloud.Identity/Properties/launchSettings.json +++ b/LIN.Cloud.Identity/Properties/launchSettings.json @@ -3,7 +3,7 @@ "http": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, @@ -13,7 +13,7 @@ "https": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, @@ -23,7 +23,7 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -31,7 +31,7 @@ "WSL": { "commandName": "WSL2", "launchBrowser": true, - "launchUrl": "https://localhost:7064/swagger", + "launchUrl": "https://localhost:7064/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "https://localhost:7064;http://localhost:5166" diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 1433dd5..249385a 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -16,15 +16,12 @@ public static (bool pass, string message) Validate(AccountModel baseAccount) { - if (baseAccount.Name == null || baseAccount.Name.Trim().Length <= 0) + if (string.IsNullOrWhiteSpace(baseAccount.Name)) return (false, "La cuenta debe de tener un nombre valido."); - - if (baseAccount.Identity == null || baseAccount.Identity.Unique == null|| baseAccount.Identity.Unique.Trim().Length <= 0) + if (baseAccount.Identity == null || string.IsNullOrWhiteSpace(baseAccount.Identity.Unique)) return (false, "La cuenta debe de tener una identidad unica valida."); - - if (!ValidarCadena(baseAccount.Identity.Unique)) return (false, "La identidad de la cuenta no puede contener símbolos NO alfanuméricos."); diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 4fada6c..187c8ff 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -7,7 +7,7 @@ public class RolesIam(DataContext context) - public async Task<(List identities, List roles)> RolesOnByDir(int identity, int directory) + public async Task<(List identities, List roles)> RolesOnByDir(int identity, int directory) { var query = await (from org in context.Organizations @@ -23,38 +23,52 @@ public class RolesIam(DataContext context) List identities = [identity]; List roles = []; - await RolesOn(identity, query, context, identities, roles); + await RolesOn(identity, query, identities, roles); return (identities, roles); } - - public async Task<(List identities, List roles)> RolesOn(int identity, int organization) + /// + /// Obtener los roles directos y heredades de una identidad en una organización. + /// + /// Id de la identidad. + /// Id de la organización. + public async Task<(List identities, List roles)> RolesOn(int identity, int organization) { List identities = [identity]; List roles = []; - await RolesOn(identity, organization, context, identities, roles); + await RolesOn(identity, organization, identities, roles); return (identities, roles); } - private async Task RolesOn(int identity, int organization, DataContext context, List ids, List roles) - { + /// + /// Obtener los roles. + /// + /// Id de la identidad. + /// Id de la organización. + /// Lista de Ids de identidades asociadas. + /// Lista de roles asociados. + private async Task RolesOn(int identity, int organization, List ids, List roles) + { + // Consulta. var query = from id in context.Identities where id.Id == identity select new { + // Encontrar grupos donde la identidad pertenece. In = (from member in context.GroupMembers where !ids.Contains(member.Group.IdentityId) && member.IdentityId == identity select member.Group.IdentityId).ToList(), + // Obtener roles. Roles = (from IR in context.IdentityRoles where IR.IdentityId == identity && IR.OrganizationId == organization @@ -79,24 +93,15 @@ private async Task RolesOn(int identity, int organization, DataContext context, roles.AddRange(localRoles); ids.AddRange(bases); - - // Recorrer. foreach (var @base in bases) - await RolesOn(@base, organization, context, ids, roles); + await RolesOn(@base, organization, ids, roles); } } - - - - - - - } @@ -139,7 +144,6 @@ public static bool ValidateAlterMembers(IEnumerable roles) Roles.AccountOperator ]; - var sets = availed.Intersect(roles); return sets.Any(); diff --git a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs index 36805a0..535380c 100644 --- a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs +++ b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs @@ -4,6 +4,7 @@ public class IPMiddleware : IMiddleware { + /// /// Invocación del Middleware. /// @@ -28,10 +29,12 @@ await context.Response.WriteAsJsonAsync(new ResponseBase // Item de IP. context.Items.Add("IP", ip); - await next(context); // Headers. - // context.Response.Headers.Append("client-ip", ip.MapToIPv4().ToString()); + context.Response.Headers.Append("client-ip", ip.MapToIPv4().ToString()); + + // Pipeline. + await next(context); } diff --git a/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs index d666cc8..cac70c9 100644 --- a/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs +++ b/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs @@ -27,20 +27,20 @@ public class QuotaMiddleware : IMiddleware public async Task InvokeAsync(HttpContext context, RequestDelegate next) { - //// Comprobar el limite de cuota. - //if (ActualQuote >= MaxQuote) - //{ - // context.Response.StatusCode = 503; - // await context.Response.WriteAsJsonAsync(new ResponseBase - // { - // Message = $"Actualmente estamos al limite de solicitudes.", - // Response = Responses.UnavailableService - // }); - // return; - //} - - //// Incrementar. - //ActualQuote++; + // Comprobar el limite de cuota. + if (ActualQuote >= MaxQuote) + { + context.Response.StatusCode = 503; + await context.Response.WriteAsJsonAsync(new ResponseBase + { + Message = $"Actualmente estamos al limite de solicitudes.", + Response = Responses.UnavailableService + }); + return; + } + + // Incrementar. + ActualQuote++; // Ejecución del flujo (Pipeline). await next(context); diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 03e88c0..da71b35 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -35,7 +35,6 @@ public override Task OnDisconnectedAsync(Exception? exception) var e = Attempts.Values.Where(T => T.Where(T => T.HubKey == Context.ConnectionId).Any()).FirstOrDefault() ?? new(); - _ = e.Where(T => { if (T.HubKey == Context.ConnectionId && T.Status == PassKeyStatus.Undefined) @@ -107,7 +106,7 @@ public async Task ReceiveRequest(PassKeyModel modelo) return; } - // Obtiene el attempt + // Obtiene el attempt. List attempts = Attempts[modelo.User.ToLower()].Where(A => A.HubKey == modelo.HubKey).ToList(); // Elemento diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index 4e1e89a..433b6ba 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -26,7 +26,6 @@ public async Task JoinAdmin(string token) - /// /// Nuevo intento de inicio. /// @@ -75,5 +74,4 @@ public async Task JoinIntent(PassKeyModel attempt) } - } \ No newline at end of file diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj new file mode 100644 index 0000000..781d5f6 --- /dev/null +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -0,0 +1,37 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + diff --git a/LIN.Identity.Tests/Validations.cs b/LIN.Identity.Tests/Validations.cs new file mode 100644 index 0000000..7fb2b97 --- /dev/null +++ b/LIN.Identity.Tests/Validations.cs @@ -0,0 +1,77 @@ +using LIN.Types.Cloud.Identity.Models; + +namespace LIN.Identity.Tests; + + +public class Validations +{ + + + /// + /// Validar cuentas. + /// + [Fact] + public void ValidateAccounts() + { + + var accounts = new Dictionary + { + { + new() + { + IdentityService = Types.Cloud.Identity.Enumerations.IdentityService.LIN, + Identity = new() + { + }, + Name = "John Doe", + Password = "password", + Visibility = Types.Cloud.Identity.Enumerations.Visibility.Visible + }, false + }, + { + new() + { + IdentityService = Types.Cloud.Identity.Enumerations.IdentityService.LIN, + Identity = new() + { + Unique = "johnDoe" + }, + Name = "John Doe", + Password = "password", + Visibility = Types.Cloud.Identity.Enumerations.Visibility.Visible + }, true + }, + { + new() + { + + }, false + }, + + { + new() + { + IdentityService = Types.Cloud.Identity.Enumerations.IdentityService.LIN, + Identity = new() + { + Unique = "johnDoe" + }, + Name = "", + Password = "", + Visibility = Types.Cloud.Identity.Enumerations.Visibility.Visible + }, false + }, + }; + + + foreach (var account in accounts) + { + (bool final, _) = LIN.Cloud.Identity.Services.Formats.Account.Validate(account.Key); + Assert.Equal(account.Value, final); + } + + } + + + +} \ No newline at end of file From 2561eb8a1d51061684769e06e7a8555e3b7a65d3 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 27 Jun 2024 14:52:29 -0500 Subject: [PATCH 085/178] Allow Apps y services --- .../Contexts/DataContext.cs | 59 ++++++++++ .../Extensions/PersistenceExtensions.cs | 5 +- .../Authentication/AllowAppsController.cs | 6 ++ .../Areas/Authentication/IntentsController.cs | 4 +- .../Areas/Directories/DirectoryController.cs | 4 +- .../Areas/Groups/GroupsController.cs | 8 +- .../Areas/Groups/GroupsMembersController.cs | 16 +-- .../Areas/Organizations/IdentityController.cs | 6 +- .../Areas/Organizations/MemberController.cs | 8 +- LIN.Cloud.Identity/Data/AllowApps.cs | 93 ++++++++++++++++ LIN.Cloud.Identity/Data/Groups.cs | 2 +- LIN.Cloud.Identity/Data/Identities.cs | 8 +- LIN.Cloud.Identity/Data/Organizations.cs | 10 +- LIN.Cloud.Identity/Data/PassKeys.cs | 10 +- LIN.Cloud.Identity/Program.cs | 4 +- .../Services/Auth/AllowService.cs | 29 +++++ .../Services/Auth/Authentication.cs | 2 +- .../Services/Auth/Interfaces/IAllowService.cs | 6 ++ .../Services/Extensions/LocalServices.cs | 29 +++-- LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 101 +++--------------- .../Services/Models/QueryAccountFilter.cs | 4 +- .../Services/Realtime/PassKeyHub.cs | 2 +- .../Services/Utils/IIdentityService.cs | 6 ++ .../Services/Utils/IdentityService.cs | 58 ++++++++++ LIN.Cloud.Identity/Usings.cs | 39 +++---- 25 files changed, 352 insertions(+), 167 deletions(-) create mode 100644 LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs create mode 100644 LIN.Cloud.Identity/Data/AllowApps.cs create mode 100644 LIN.Cloud.Identity/Services/Auth/AllowService.cs create mode 100644 LIN.Cloud.Identity/Services/Auth/Interfaces/IAllowService.cs create mode 100644 LIN.Cloud.Identity/Services/Utils/IIdentityService.cs create mode 100644 LIN.Cloud.Identity/Services/Utils/IdentityService.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 3544e49..7e9173c 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -51,6 +51,19 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet PassKeys { get; set; } + /// + /// Aplicaciones. + /// + public DbSet Applications { get; set; } + + + + /// + /// Allow apps. + /// + public DbSet AllowApps { get; set; } + + ///// @@ -180,6 +193,50 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } + + // Modelo: Application. + { + modelBuilder.Entity() + .HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t=>t.IdentityId); + + + modelBuilder.Entity() + .HasIndex(t => t.IdentityId) + .IsUnique(); + + modelBuilder.Entity() + .HasOne(t => t.Owner) + .WithMany() + .HasForeignKey(t => t.OwnerId) + .OnDelete(DeleteBehavior.NoAction); + + } + + // Modelo: Allow Apps. + { + modelBuilder.Entity() + .HasOne(t => t.Application) + .WithMany() + .HasForeignKey(t => t.ApplicationId); + + modelBuilder.Entity() + .HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t => t.IdentityId); + + modelBuilder.Entity() + .HasKey(t => new + { + t.ApplicationId, + t.IdentityId + }); + + } + + + // Nombres de las tablas. modelBuilder.Entity().ToTable("IDENTITIES"); modelBuilder.Entity().ToTable("ACCOUNTS"); @@ -188,6 +245,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("GROUPS_MEMBERS"); modelBuilder.Entity().ToTable("ORGANIZATIONS"); modelBuilder.Entity().ToTable("PASSKEYS"); + modelBuilder.Entity().ToTable("ALLOW_APPS"); + modelBuilder.Entity().ToTable("APPLICATIONS"); // Base. base.OnModelCreating(modelBuilder); diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index d8c42eb..3dd4704 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -40,8 +40,9 @@ public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) try { - // Obtener servicio. - var context = app.ApplicationServices.GetService(); + var scope = app.ApplicationServices.CreateScope(); + + var context = scope.ServiceProvider.GetService(); context?.Database.EnsureCreated() ; } diff --git a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs new file mode 100644 index 0000000..b4b421c --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs @@ -0,0 +1,6 @@ +namespace LIN.Cloud.Identity.Areas.Authentication +{ + public class AllowAppsController + { + } +} diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 71b508e..96ebcf3 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class IntentsController (Data.PassKeys passkeyData) : ControllerBase +public class IntentsController(Data.PassKeys passkeyData) : ControllerBase { @@ -21,7 +21,7 @@ public HttpReadAllResponse GetAll([FromHeader] string token) // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - + // Cuenta var account = (from a in PassKeyHub.Attempts where a.Key.Equals(tokenInfo.Unique, StringComparison.CurrentCultureIgnoreCase) diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index 9b3dc6f..ddf364f 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -2,7 +2,7 @@ [Route("[controller]")] -public class DirectoryController (Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : ControllerBase +public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : ControllerBase { @@ -69,7 +69,7 @@ public async Task> ReadMembers([FromHeader] str // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index b8e3dfb..a1b36fe 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -20,7 +20,7 @@ public async Task Create([FromBody] GroupModel group, [FromH JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, group.OwnerId ?? 0); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, group.OwnerId ?? 0); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -72,7 +72,7 @@ public async Task> ReadAll([FromHeader] string t JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -129,7 +129,7 @@ public async Task> ReadOne([FromHeader] string t // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -186,7 +186,7 @@ public async Task> ReadIdentity([FromHeader] str // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index 520af7a..2faeba8 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -31,7 +31,7 @@ public async Task Create([FromHeader] string token, [FromBod }; // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -93,7 +93,7 @@ public async Task Create([FromHeader] string token, [FromHea // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -168,7 +168,7 @@ public async Task> ReadMembers([FromHeader] str // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -226,7 +226,7 @@ public async Task> Search([FromHeader] string // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -285,7 +285,7 @@ public async Task> SearchOnGroups([FromHeader // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -343,7 +343,7 @@ public async Task DeleteMembers([FromHeader] string token, [Fr // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -387,9 +387,9 @@ public async Task> OnMembers([FromHeader] string // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - + // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 279464e..3692bf7 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -20,7 +20,7 @@ public async Task Create([FromBody] IdentityRolesModel rolMode JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, rolModel.OrganizationId); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, rolModel.OrganizationId); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -80,7 +80,7 @@ public async Task> ReadAll([FromHeader] JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -139,7 +139,7 @@ public async Task ReadAll([FromHeader] string token, [FromHead JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs index 8fe7a00..c1473fb 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs @@ -33,8 +33,8 @@ public async Task Create([FromBody] AccountModel modelo, [Fr // Ajustar el modelo. modelo.Visibility = Visibility.Hidden; modelo.Password = $"pwd@{DateTime.Now.Year}"; - modelo = Services.Formats.Account.Process(modelo); - + modelo = Services.Formats.Account.Process(modelo); + // Organización. var orgIdentity = await organizationsData.GetDomain(organization); @@ -59,7 +59,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr modelo.Identity.Unique = $"{modelo.Identity.Unique}@{orgIdentity.Model.Unique}"; // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -107,7 +107,7 @@ public async Task>> ReadAll([FromH // Confirmar el rol. - var (_, roles) = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Data/AllowApps.cs b/LIN.Cloud.Identity/Data/AllowApps.cs new file mode 100644 index 0000000..9b44a19 --- /dev/null +++ b/LIN.Cloud.Identity/Data/AllowApps.cs @@ -0,0 +1,93 @@ +namespace LIN.Cloud.Identity.Data; + + +public class AllowApps(DataContext context, LIN.Cloud.Identity.Services.Utils.IIdentityService identityService) +{ + + + /// + /// Crear acceso a app. + /// + /// Modelo. + public async Task> Create(AllowApp modelo) + { + + // Transacción. + using var transaction = context.Database.BeginTransaction(); + + try + { + + // Attach. + context.Attach(modelo.Application); + context.Attach(modelo.Identity); + + // Guardar la cuenta. + await context.AllowApps.AddAsync(modelo); + context.SaveChanges(); + + // Confirmar los cambios. + transaction.Commit(); + + return new() + { + Response = Responses.Success, + Model = modelo + }; + + } + catch (Exception) + { + transaction.Rollback(); + return new() + { + Response = Responses.ResourceExist + }; + } + + } + + + + /// + /// Obtener las apps a las que una identidad tiene acceso o no. + /// + public async Task> ReadAll(int id) + { + + // Ejecución + try + { + + var identities = await identityService.GetIdenties(id); + + + + var query = await (from allow in context.AllowApps + where identities.Contains(allow.IdentityId) + select new AllowApp + { + ApplicationId = allow.ApplicationId, + IdentityId = allow.IdentityId, + IsAllow = allow.IsAllow, + Application = new() + { + Id = allow.Application.Id, + IdentityId = allow.Application.IdentityId, + Name = allow.Application.Name, + OwnerId = allow.Application.OwnerId, + } + }).ToListAsync(); + + return new(Responses.Success, query); + + } + catch (Exception) + { + } + + return new(); + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Groups.cs b/LIN.Cloud.Identity/Data/Groups.cs index a4e71cf..3d7fc6d 100644 --- a/LIN.Cloud.Identity/Data/Groups.cs +++ b/LIN.Cloud.Identity/Data/Groups.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Data; -public class Groups (DataContext context) +public class Groups(DataContext context) { diff --git a/LIN.Cloud.Identity/Data/Identities.cs b/LIN.Cloud.Identity/Data/Identities.cs index 2d1cf3e..6c3de35 100644 --- a/LIN.Cloud.Identity/Data/Identities.cs +++ b/LIN.Cloud.Identity/Data/Identities.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Data; -public class Identities(DataContext context) +public class Identities(DataContext context) { @@ -10,7 +10,7 @@ public class Identities(DataContext context) /// /// Modelo. /// Contexto de conexión. - public async Task> Create(IdentityModel modelo) + public async Task> Create(IdentityModel modelo) { // Pre. modelo.Id = 0; @@ -50,7 +50,7 @@ public async Task> Create(IdentityModel modelo) /// Id de la identidad. /// Filtros de búsqueda. /// Contexto de base de datos. - public async Task> Read(int id, QueryIdentityFilter filters) + public async Task> Read(int id, QueryIdentityFilter filters) { try @@ -92,7 +92,7 @@ public async Task> Read(int id, QueryIdentityFil /// Unique. /// Filtros de búsqueda. /// Contexto de base de datos. - public async Task> Read(string unique, QueryIdentityFilter filters) + public async Task> Read(string unique, QueryIdentityFilter filters) { try diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index e855048..c852245 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Data; -public class Organizations(DataContext context) +public class Organizations(DataContext context) { @@ -10,7 +10,7 @@ public class Organizations(DataContext context) /// /// Modelo. /// Contexto de conexión. - public async Task Create(OrganizationModel modelo) + public async Task Create(OrganizationModel modelo) { using var transaction = context.Database.BeginTransaction(); @@ -120,7 +120,7 @@ public async Task Create(OrganizationModel modelo) /// /// Id. /// Contexto de base de datos. - public async Task> Read(int id) + public async Task> Read(int id) { try @@ -162,7 +162,7 @@ public async Task> Read(int id) /// /// Identidad /// Contexto - public async Task> ReadAll(int id) + public async Task> ReadAll(int id) { try @@ -208,7 +208,7 @@ on org.DirectoryId equals gm.GroupId /// /// Id de la organización. /// Contexto de base de datos. - public async Task> GetDomain(int id) + public async Task> GetDomain(int id) { try diff --git a/LIN.Cloud.Identity/Data/PassKeys.cs b/LIN.Cloud.Identity/Data/PassKeys.cs index 190e358..9ab6062 100644 --- a/LIN.Cloud.Identity/Data/PassKeys.cs +++ b/LIN.Cloud.Identity/Data/PassKeys.cs @@ -3,13 +3,13 @@ namespace LIN.Cloud.Identity.Data; -public class PassKeys(DataContext context) +public class PassKeys(DataContext context) { - - public async Task Create(PassKeyDBModel modelo) + + public async Task Create(PassKeyDBModel modelo) { // Pre. modelo.Id = 0; @@ -39,8 +39,8 @@ public async Task Create(PassKeyDBModel modelo) - - public async Task> Count(int id) + + public async Task> Count(int id) { try diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index a66e738..af7fb37 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -1,8 +1,8 @@ -using LIN.Cloud.Identity.Services.Realtime; using Http.Extensions; -using LIN.Cloud.Identity.Services.Auth.Interfaces; using LIN.Cloud.Identity.Persistence.Extensions; +using LIN.Cloud.Identity.Services.Auth.Interfaces; using LIN.Cloud.Identity.Services.Extensions; +using LIN.Cloud.Identity.Services.Realtime; var builder = WebApplication.CreateBuilder(args); diff --git a/LIN.Cloud.Identity/Services/Auth/AllowService.cs b/LIN.Cloud.Identity/Services/Auth/AllowService.cs new file mode 100644 index 0000000..b9e78bd --- /dev/null +++ b/LIN.Cloud.Identity/Services/Auth/AllowService.cs @@ -0,0 +1,29 @@ +using LIN.Cloud.Identity.Services.Auth.Interfaces; + +namespace LIN.Cloud.Identity.Services.Auth; + + +public class AllowService(DataContext context) : IAllowService +{ + + + /// + /// Validar si una lista de identidades puede acceder a una aplicación. + /// + /// Ids de la identidad. + /// Id de la aplicación. + public async Task IsAllow(IEnumerable identities, int appId) + { + + // Consulta. + var isAllow = await (from allow in context.AllowApps + where allow.ApplicationId == appId + && identities.Contains(allow.Identity.Id) + select allow.IsAllow).ToListAsync(); + + return isAllow.Contains(true); + + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index b2a42fb..d0f3a90 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Services.Auth; -public class Authentication (Data.Accounts accountData) : Interfaces.IAuthentication +public class Authentication(Data.Accounts accountData) : Interfaces.IAuthentication { diff --git a/LIN.Cloud.Identity/Services/Auth/Interfaces/IAllowService.cs b/LIN.Cloud.Identity/Services/Auth/Interfaces/IAllowService.cs new file mode 100644 index 0000000..c1d0f34 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Auth/Interfaces/IAllowService.cs @@ -0,0 +1,6 @@ +namespace LIN.Cloud.Identity.Services.Auth.Interfaces; + +public interface IAllowService +{ + Task IsAllow(IEnumerable identities, int appId); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index c4bdac8..5ff88b9 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -1,4 +1,7 @@ -namespace LIN.Cloud.Identity.Services.Extensions; +using LIN.Cloud.Identity.Services.Auth.Interfaces; +using LIN.Cloud.Identity.Services.Utils; + +namespace LIN.Cloud.Identity.Services.Extensions; public static class LocalServices @@ -13,16 +16,22 @@ public static class LocalServices public static IServiceCollection AddLocalServices(this IServiceCollection services) { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // Iam. + services.AddScoped(); + + // Allow. + services.AddScoped(); + services.AddScoped(); - services.AddScoped(); return services; diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 187c8ff..576efd7 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -1,103 +1,30 @@ -namespace LIN.Cloud.Identity.Services.Iam; - - -public class RolesIam(DataContext context) -{ - - - - - public async Task<(List identities, List roles)> RolesOnByDir(int identity, int directory) - { - - var query = await (from org in context.Organizations - where org.DirectoryId == directory - select org.Id).FirstOrDefaultAsync(); - - - if (query <= 0) - return ([], []); - - - - List identities = [identity]; - List roles = []; - - await RolesOn(identity, query, identities, roles); - - return (identities, roles); - } +using LIN.Cloud.Identity.Services.Utils; +namespace LIN.Cloud.Identity.Services.Iam; - /// - /// Obtener los roles directos y heredades de una identidad en una organización. - /// - /// Id de la identidad. - /// Id de la organización. - public async Task<(List identities, List roles)> RolesOn(int identity, int organization) - { - List identities = [identity]; - List roles = []; - - await RolesOn(identity, organization, identities, roles); - - return (identities, roles); - } - +public class RolesIam(DataContext context, IIdentityService identityService) +{ /// - /// Obtener los roles. + /// Obtener los roles de una identitdad en una organización. /// /// Id de la identidad. /// Id de la organización. - /// Lista de Ids de identidades asociadas. - /// Lista de roles asociados. - private async Task RolesOn(int identity, int organization, List ids, List roles) + public async Task> RolesOn(int identity, int organization) { - // Consulta. - var query = from id in context.Identities - where id.Id == identity - select new - { - // Encontrar grupos donde la identidad pertenece. - In = (from member in context.GroupMembers - where !ids.Contains(member.Group.IdentityId) - && member.IdentityId == identity - select member.Group.IdentityId).ToList(), - - // Obtener roles. - Roles = (from IR in context.IdentityRoles - where IR.IdentityId == identity - && IR.OrganizationId == organization - select IR.Rol).ToList() - }; - - - // Si hay elementos. - if (query.Any()) - { - - // Ejecuta la consulta. - var local = query.ToList(); - - // Obtiene los roles. - var localRoles = local.SelectMany(t => t.Roles); - - // Obtiene las bases. - var bases = local.SelectMany(t => t.In); - - // Agregar a los objetos. - roles.AddRange(localRoles); - ids.AddRange(bases); + // Identitades + var identities = await identityService.GetIdenties(identity); - // Recorrer. - foreach (var @base in bases) - await RolesOn(@base, organization, ids, roles); + // Obtener roles. + var roles = await (from rol in context.IdentityRoles + where identities.Contains(rol.IdentityId) + && rol.OrganizationId == organization + select rol.Rol).ToListAsync(); - } + return roles; } diff --git a/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs b/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs index 407d249..1c624cb 100644 --- a/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs +++ b/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs @@ -3,8 +3,8 @@ public class QueryAccountFilter { - public int AccountContext { get; set; } - public int IdentityContext { get; set; } + public int AccountContext { get; set; } + public int IdentityContext { get; set; } public List OrganizationsDirectories { get; set; } = []; public bool IsAdmin { get; set; } public bool IncludePhoto { get; set; } = true; diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index da71b35..e6aaf31 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -3,7 +3,7 @@ namespace LIN.Cloud.Identity.Services.Realtime; -public partial class PassKeyHub (Data.PassKeys passKeysData) : Hub +public partial class PassKeyHub(Data.PassKeys passKeysData) : Hub { diff --git a/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs b/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs new file mode 100644 index 0000000..dc086ff --- /dev/null +++ b/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs @@ -0,0 +1,6 @@ +namespace LIN.Cloud.Identity.Services.Utils; + +public interface IIdentityService +{ + Task> GetIdenties(int identity); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs new file mode 100644 index 0000000..8f88e3f --- /dev/null +++ b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs @@ -0,0 +1,58 @@ +namespace LIN.Cloud.Identity.Services.Utils; + + +public class IdentityService(DataContext context) : IIdentityService +{ + + + + public async Task> GetIdenties(int identity) + { + List result = [identity]; + await GetIdenties(identity, result); + return result; + } + + private async Task GetIdenties(int identity, List ids) + { + // Consulta. + var query = from id in context.Identities + where id.Id == identity + select new + { + // Encontrar grupos donde la identidad pertenece. + In = (from member in context.GroupMembers + where !ids.Contains(member.Group.IdentityId) + && member.IdentityId == identity + select member.Group.IdentityId).ToList(), + }; + + + // Si hay elementos. + if (query.Any()) + { + // Ejecuta la consulta. + var local = query.ToList(); + + // Obtiene las bases. + var bases = local.SelectMany(t => t.In); + + // Agregar a los objetos. + ids.AddRange(bases); + + // Recorrer. + foreach (var @base in bases) + await GetIdenties(@base, ids); + + } + } + + + + + + + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index c856dd2..e9283ba 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -1,34 +1,25 @@ // Sistema. -global using LIN.Cloud.Identity; -global using LIN.Cloud.Identity.Services; +global using Http.ResponsesList; +// Tipos locales. +global using LIN.Cloud.Identity.Persistence.Contexts; global using LIN.Cloud.Identity.Services.Auth; -global using LIN.Cloud.Identity.Services.Models; -global using Microsoft.AspNetCore.SignalR; +global using LIN.Cloud.Identity.Services.Filters; global using LIN.Cloud.Identity.Services.Iam; - -// Framework. -global using System.Text; -global using System.IO; -global using System.Globalization; -global using Microsoft.AspNetCore.Mvc.Filters; +global using LIN.Cloud.Identity.Services.Middlewares; +global using LIN.Cloud.Identity.Services.Models; +global using LIN.Types.Cloud.Identity.Enumerations; +global using LIN.Types.Cloud.Identity.Models; +// Tipos Generales +global using LIN.Types.Responses; global using Microsoft.AspNetCore.Mvc; - +global using Microsoft.AspNetCore.Mvc.Filters; +global using Microsoft.AspNetCore.SignalR; // SQL. global using Microsoft.EntityFrameworkCore; - -// Tipos locales. -global using LIN.Cloud.Identity.Persistence.Contexts; -global using LIN.Cloud.Identity.Services.Middlewares; -global using LIN.Types.Cloud.Identity.Models; -global using LIN.Types.Cloud.Identity.Enumerations; -global using LIN.Cloud.Identity.Services.Filters; - // Tipos Extras. global using Microsoft.IdentityModel.Tokens; global using System.IdentityModel.Tokens.Jwt; +global using System.IO; global using System.Security.Claims; - -// Tipos Generales -global using LIN.Types.Responses; -global using LIN.Types.Enumerations; -global using Http.ResponsesList; +// Framework. +global using System.Text; From 19903e09654d5629d4bbf3832d1714efa0cb30b6 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 9 Jul 2024 16:15:49 -0500 Subject: [PATCH 086/178] Allow Update --- .../Authentication/AllowAppsController.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs index b4b421c..241d44a 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs @@ -1,6 +1,19 @@ -namespace LIN.Cloud.Identity.Areas.Authentication +namespace LIN.Cloud.Identity.Areas.Authentication; + + +[Route("applications/allow")] +public class AllowAppsController : ControllerBase { - public class AllowAppsController + + + + public async void GetAll([FromHeader] string token, [FromQuery] int identity) { + } -} + + + + + +} \ No newline at end of file From 4b27ab76cef15c34efa9aa111767b0c0f5219d35 Mon Sep 17 00:00:00 2001 From: Alexander Bravo <102942726+AlexGBravo@users.noreply.github.com> Date: Thu, 29 Aug 2024 22:42:30 +0000 Subject: [PATCH 087/178] Correcciones --- LIN.Cloud.Identity/Areas/Accounts/AccountController.cs | 10 +++++----- LIN.Cloud.Identity/Services/Auth/Authentication.cs | 3 +-- .../Services/Extensions/LocalServices.cs | 1 - 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index e3b078c..a726ebe 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -31,23 +31,23 @@ public async Task Create([FromBody] AccountModel? modelo) Message = message }; - // Organización del modelo + // Organización del modelo. modelo = Services.Formats.Account.Process(modelo); - // Creación del usuario + // Creación del usuario. var response = await accountData.Create(modelo, 0); - // Evaluación + // Evaluación. if (response.Response != Responses.Success) return new(response.Response) { Message = "Hubo un error al crear la cuenta." }; - // Obtiene el usuario + // Obtiene el usuario. var token = JwtService.Generate(response.Model, 0); - // Retorna el resultado + // Retorna el resultado. return new CreateResponse() { LastID = response.Model.Identity.Id, diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index d0f3a90..99911f7 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -17,14 +17,12 @@ public class Authentication(Data.Accounts accountData) : Interfaces.IAuthenticat private string Password { get; set; } = string.Empty; - /// /// Código de la aplicación. /// private string AppCode { get; set; } = string.Empty; - /// /// Modelo obtenido. /// @@ -64,6 +62,7 @@ public async Task Start() // Validar contraseña. bool password = ValidatePassword(); +// Si la contraseña es invalida. if (!password) return Responses.InvalidPassword; diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 5ff88b9..b82ba48 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -32,7 +32,6 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddScoped(); services.AddScoped(); - return services; } From 7526154e3612e18bdda509f01d202847f87dc4c3 Mon Sep 17 00:00:00 2001 From: Alexander Bravo <102942726+AlexGBravo@users.noreply.github.com> Date: Thu, 29 Aug 2024 17:45:30 -0500 Subject: [PATCH 088/178] Create dotnet-desktop.yml --- .github/workflows/dotnet-desktop.yml | 115 +++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/workflows/dotnet-desktop.yml diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml new file mode 100644 index 0000000..22ec423 --- /dev/null +++ b/.github/workflows/dotnet-desktop.yml @@ -0,0 +1,115 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow will build, test, sign and package a WPF or Windows Forms desktop application +# built on .NET Core. +# To learn how to migrate your existing application to .NET Core, +# refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework +# +# To configure this workflow: +# +# 1. Configure environment variables +# GitHub sets default environment variables for every workflow run. +# Replace the variables relative to your project in the "env" section below. +# +# 2. Signing +# Generate a signing certificate in the Windows Application +# Packaging Project or add an existing signing certificate to the project. +# Next, use PowerShell to encode the .pfx file using Base64 encoding +# by running the following Powershell script to generate the output string: +# +# $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte +# [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt' +# +# Open the output file, SigningCertificate_Encoded.txt, and copy the +# string inside. Then, add the string to the repo as a GitHub secret +# and name it "Base64_Encoded_Pfx." +# For more information on how to configure your signing certificate for +# this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing +# +# Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key". +# See "Build the Windows Application Packaging project" below to see how the secret is used. +# +# For more information on GitHub Actions, refer to https://github.com/features/actions +# For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications, +# refer to https://github.com/microsoft/github-actions-for-desktop-apps + +name: .NET Core Desktop + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + + strategy: + matrix: + configuration: [Debug, Release] + + runs-on: windows-latest # For a list of available runner types, refer to + # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on + + env: + Solution_Name: your-solution-name # Replace with your solution name, i.e. MyWpfApp.sln. + Test_Project_Path: your-test-project-path # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj. + Wap_Project_Directory: your-wap-project-directory-name # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package. + Wap_Project_Path: your-wap-project-path # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj. + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@v2 + + # Execute all unit tests in the solution + - name: Execute unit tests + run: dotnet test + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: Restore the application + run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration + env: + Configuration: ${{ matrix.configuration }} + + # Decode the base 64 encoded pfx and save the Signing_Certificate + - name: Decode the pfx + run: | + $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") + $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx + [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte) + + # Create the app package by building and packaging the Windows Application Packaging project + - name: Create the app package + run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }} + env: + Appx_Bundle: Always + Appx_Bundle_Platforms: x86|x64 + Appx_Package_Build_Mode: StoreUpload + Configuration: ${{ matrix.configuration }} + + # Remove the pfx + - name: Remove the pfx + run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx + + # Upload the MSIX package: https://github.com/marketplace/actions/upload-a-build-artifact + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: MSIX Package + path: ${{ env.Wap_Project_Directory }}\AppPackages From 5bd3494f173e664b0566fc8ea40ba35a89b2beb1 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 31 Aug 2024 10:06:04 -0500 Subject: [PATCH 089/178] Mejoras --- .../Areas/Accounts/AccountController.cs | 2 ++ LIN.Cloud.Identity/Data/Builders/Account.cs | 12 ------------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index a726ebe..1bc2029 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -32,6 +32,8 @@ public async Task Create([FromBody] AccountModel? modelo) }; // Organización del modelo. + modelo.Identity.EffectiveTime = default; + modelo.Identity.ExpirationTime = default; modelo = Services.Formats.Account.Process(modelo); // Creación del usuario. diff --git a/LIN.Cloud.Identity/Data/Builders/Account.cs b/LIN.Cloud.Identity/Data/Builders/Account.cs index 0272adb..ef0e551 100644 --- a/LIN.Cloud.Identity/Data/Builders/Account.cs +++ b/LIN.Cloud.Identity/Data/Builders/Account.cs @@ -46,18 +46,6 @@ public static IQueryable OnAll(DataContext context) - - - - - - - - - - - - /// /// Obtener cuentas. /// From 946c7763041583e1fb78a3d864f3b4b890c6300f Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 31 Aug 2024 10:18:32 -0500 Subject: [PATCH 090/178] Mejoras de claridad --- .../Extensions/PersistenceExtensions.cs | 7 ++----- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- LIN.Cloud.Identity/Program.cs | 9 --------- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 6 +++--- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 3dd4704..30b95f5 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -18,7 +18,6 @@ public static class PersistenceExtensions public static IServiceCollection AddPersistence(this IServiceCollection services, IConfigurationManager configuration) { - services.AddDbContextPool(options => { options.UseSqlServer(configuration.GetConnectionString("cloud")); @@ -40,11 +39,9 @@ public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) try { - var scope = app.ApplicationServices.CreateScope(); - + var scope = app.ApplicationServices.CreateScope(); var context = scope.ServiceProvider.GetService(); - - context?.Database.EnsureCreated() ; + context?.Database.EnsureCreated(); } catch (Exception ex) { diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 838037b..49901ed 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index af7fb37..1e6dca4 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -10,15 +10,6 @@ builder.Services.AddSignalR(); builder.Services.AddLINHttp(); -// Servicios personalizados -string sql = ""; - -#if DEBUG -sql = builder.Configuration["ConnectionStrings:cloud"] ?? string.Empty; -#elif RELEASE -sql = builder.Configuration["ConnectionStrings:cloud"] ?? string.Empty; -#endif - // Servicios propios. builder.Services.AddIP(); builder.Services.AddLocalServices(); diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 781d5f6..80819ba 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -14,9 +14,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From af8b13383c376dc9621fb5b6ad0fdf942502e179 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 31 Aug 2024 10:34:20 -0500 Subject: [PATCH 091/178] Fix --- .../Extensions/PersistenceExtensions.cs | 1 - LIN.Cloud.Identity.sln | 2 +- LIN.Cloud.Identity/Properties/launchSettings.json | 9 --------- LIN.Cloud.Identity/appsettings.Development.json | 4 ++-- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 30b95f5..d68b02b 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -17,7 +17,6 @@ public static class PersistenceExtensions /// Services. public static IServiceCollection AddPersistence(this IServiceCollection services, IConfigurationManager configuration) { - services.AddDbContextPool(options => { options.UseSqlServer(configuration.GetConnectionString("cloud")); diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index 664b321..d314c33 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -13,7 +13,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types", "..\..\Tipos\LI EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity.Persistence", "LIN.Cloud.Identity.Persistence\LIN.Cloud.Identity.Persistence.csproj", "{6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LIN.Identity.Tests", "LIN.Identity.Tests\LIN.Identity.Tests.csproj", "{157863A4-209B-42A0-AE80-F6C403692480}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Identity.Tests", "LIN.Identity.Tests\LIN.Identity.Tests.csproj", "{157863A4-209B-42A0-AE80-F6C403692480}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/LIN.Cloud.Identity/Properties/launchSettings.json b/LIN.Cloud.Identity/Properties/launchSettings.json index 1319480..102c98a 100644 --- a/LIN.Cloud.Identity/Properties/launchSettings.json +++ b/LIN.Cloud.Identity/Properties/launchSettings.json @@ -3,27 +3,18 @@ "http": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, "dotnetRunMessages": true, "applicationUrl": "http://localhost:5166" }, "https": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, "dotnetRunMessages": true, "applicationUrl": "https://localhost:7064;http://localhost:5166" }, "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/LIN.Cloud.Identity/appsettings.Development.json b/LIN.Cloud.Identity/appsettings.Development.json index 9cdb790..a4a6afb 100644 --- a/LIN.Cloud.Identity/appsettings.Development.json +++ b/LIN.Cloud.Identity/appsettings.Development.json @@ -7,10 +7,10 @@ }, "AllowedHosts": "*", "jwt": { - "key": "drfghjiwuytr567uijnbvgfder56y7usdfghjwertyuisdfghjkjhgfdswertyujhgiolkjhgfdertyui2345fgh" + "key": "" }, "ConnectionStrings": { "local": "Data Source=(local);Initial Catalog=Identity;Integrated Security=True;TrustServerCertificate=True", - "cloud": "workstation id=linidentitydb.mssql.somee.com;packet size=4096;user id=linauth_SQLLogin_2;pwd=p4b691lkwx;data source=linidentitydb.mssql.somee.com;persist security info=False;initial catalog=linidentitydb;TrustServerCertificate=True; Encrypt=false;" + "cloud": "" } } \ No newline at end of file From 937c1d1f74bcc2e724be26f1ada74bc855f58d67 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 31 Aug 2024 13:09:13 -0500 Subject: [PATCH 092/178] =?UTF-8?q?Soluci=C3=B3n=20de=20errores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contexts/DataContext.cs | 8 +++++--- .../Areas/Authentication/AllowAppsController.cs | 11 ----------- LIN.Cloud.Identity/appsettings.Development.json | 4 ++-- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 7e9173c..39aece1 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -219,12 +219,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasOne(t => t.Application) .WithMany() - .HasForeignKey(t => t.ApplicationId); - + .HasForeignKey(t => t.ApplicationId) + .OnDelete(DeleteBehavior.NoAction); + modelBuilder.Entity() .HasOne(t => t.Identity) .WithMany() - .HasForeignKey(t => t.IdentityId); + .HasForeignKey(t => t.IdentityId) + .OnDelete(DeleteBehavior.NoAction); modelBuilder.Entity() .HasKey(t => new diff --git a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs index 241d44a..4891182 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs @@ -5,15 +5,4 @@ public class AllowAppsController : ControllerBase { - - - public async void GetAll([FromHeader] string token, [FromQuery] int identity) - { - - } - - - - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/appsettings.Development.json b/LIN.Cloud.Identity/appsettings.Development.json index a4a6afb..f47ccdf 100644 --- a/LIN.Cloud.Identity/appsettings.Development.json +++ b/LIN.Cloud.Identity/appsettings.Development.json @@ -7,10 +7,10 @@ }, "AllowedHosts": "*", "jwt": { - "key": "" + "key": "drfghjiwuytr567uijnbvgfder56y7usdfghjwertyuisdfghjkjhgfdswertyujhgiolkjhgx2fdertyui2345fgh" }, "ConnectionStrings": { "local": "Data Source=(local);Initial Catalog=Identity;Integrated Security=True;TrustServerCertificate=True", - "cloud": "" + "cloud": "workstation id=authdatadb.mssql.somee.com;packet size=4096;user id=linauth_SQLLogin_3;pwd=yvvav2rhyd;data source=authdatadb.mssql.somee.com;persist security info=False;initial catalog=authdatadb;TrustServerCertificate=True; Encrypt=false;" } } \ No newline at end of file From 08e1475f14f87bc07b06033d0c2e4a59173a343d Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Fri, 6 Sep 2024 23:01:10 -0500 Subject: [PATCH 093/178] Update --- LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs | 4 ---- LIN.Cloud.Identity/Areas/Groups/GroupsController.cs | 6 ------ LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs | 1 - 3 files changed, 11 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index ddf364f..d37a9f0 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -5,7 +5,6 @@ public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : ControllerBase { - /// /// Obtener los directorios asociados. /// @@ -42,7 +41,6 @@ public async Task> ReadAll([FromHeader] string } - /// /// Obtener los integrantes asociados a un directorio. /// @@ -98,6 +96,4 @@ public async Task> ReadMembers([FromHeader] str } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index a1b36fe..303cb82 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -5,7 +5,6 @@ public class GroupsController(Data.Groups groupData, RolesIam rolesIam) : ControllerBase { - /// /// Crear nuevo grupo. /// @@ -57,7 +56,6 @@ public async Task Create([FromBody] GroupModel group, [FromH } - /// /// Obtener los grupos asociados a una organización. /// @@ -101,7 +99,6 @@ public async Task> ReadAll([FromHeader] string t } - /// /// Obtener un grupo según el Id. /// @@ -158,7 +155,6 @@ public async Task> ReadOne([FromHeader] string t } - /// /// Obtener un grupo según el Id. /// @@ -214,6 +210,4 @@ public async Task> ReadIdentity([FromHeader] str } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index 2faeba8..be01368 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -121,7 +121,6 @@ public async Task Create([FromHeader] string token, [FromHea Response = Responses.Unauthorized }; - // Crear el usuario. var response = await groupMembers.Create(ids.Select(id => new GroupMember { From 5a846b1304a9286f5913fb4494dd01c4f4289de7 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 21 Sep 2024 11:20:19 -0500 Subject: [PATCH 094/178] Pruebas unitarias --- .../Contexts/DataContext.cs | 42 +-- .../Extensions/DataBaseExtensions.cs | 24 ++ .../Extensions/PersistenceExtensions.cs | 4 - .../Models/PassKeyDBModel.cs | 1 - .../Models/{A.cs => QueryObjectFilter.cs} | 0 .../Queries/IdentityFindable.cs | 1 - .../Areas/Accounts/AccountController.cs | 8 - .../AuthenticationController.cs | 17 +- .../Areas/Authentication/IntentsController.cs | 13 +- .../Areas/Directories/DirectoryController.cs | 1 - .../Areas/Groups/GroupsController.cs | 5 - .../Areas/Groups/GroupsMembersController.cs | 18 -- .../Areas/Organizations/IdentityController.cs | 14 - .../Areas/Organizations/MemberController.cs | 7 - .../Organizations/OrganizationController.cs | 12 - LIN.Cloud.Identity/Data/Accounts.cs | 21 +- LIN.Cloud.Identity/Data/AllowApps.cs | 3 - LIN.Cloud.Identity/Data/DirectoryMembers.cs | 2 - LIN.Cloud.Identity/Program.cs | 3 +- .../Services/Auth/AllowService.cs | 3 - .../Services/Auth/Authentication.cs | 5 +- .../Services/Auth/JwtService.cs | 8 - .../Services/Extensions/LocalServices.cs | 3 - LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 4 +- .../Services/Middlewares/QuotaMiddleware.cs | 2 +- .../Services/Realtime/PassKeyHub.cs | 2 +- LIN.Cloud.Identity/Usings.cs | 1 + LIN.Identity.Tests/Data/Accounts.cs | 274 ++++++++++++++++++ LIN.Identity.Tests/LIN.Identity.Tests.csproj | 4 + LIN.Identity.Tests/Validations.cs | 3 - 30 files changed, 334 insertions(+), 171 deletions(-) create mode 100644 LIN.Cloud.Identity.Persistence/Extensions/DataBaseExtensions.cs rename LIN.Cloud.Identity.Persistence/Models/{A.cs => QueryObjectFilter.cs} (100%) create mode 100644 LIN.Identity.Tests/Data/Accounts.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 39aece1..0c750fb 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -64,19 +64,6 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet AllowApps { get; set; } - - - ///// - ///// Configuring database. - ///// - //protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - //{ - // optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); - // base.OnConfiguring(optionsBuilder); - //} - - - /// /// Generación del modelo de base de datos. /// @@ -97,8 +84,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .IsUnique(); } - - // Modelo: Account. { @@ -118,7 +103,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } - // Modelo: PassKey. { @@ -129,8 +113,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.NoAction); } - - // Modelo: GroupModel. { @@ -143,11 +125,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } - // Modelo: GroupMemberModel. { - modelBuilder.Entity() .HasKey(t => new { @@ -170,7 +150,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } - // Modelo: IdentityRolesModel. { modelBuilder.Entity() @@ -193,7 +172,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } - // Modelo: Application. { modelBuilder.Entity() @@ -237,18 +215,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } - - // Nombres de las tablas. - modelBuilder.Entity().ToTable("IDENTITIES"); - modelBuilder.Entity().ToTable("ACCOUNTS"); - modelBuilder.Entity().ToTable("GROUPS"); - modelBuilder.Entity().ToTable("IDENTITY_ROLES"); - modelBuilder.Entity().ToTable("GROUPS_MEMBERS"); - modelBuilder.Entity().ToTable("ORGANIZATIONS"); - modelBuilder.Entity().ToTable("PASSKEYS"); - modelBuilder.Entity().ToTable("ALLOW_APPS"); - modelBuilder.Entity().ToTable("APPLICATIONS"); + modelBuilder.Entity().ToTable("identities"); + modelBuilder.Entity().ToTable("accounts"); + modelBuilder.Entity().ToTable("groups"); + modelBuilder.Entity().ToTable("identity_roles"); + modelBuilder.Entity().ToTable("group_members"); + modelBuilder.Entity().ToTable("organizations"); + modelBuilder.Entity().ToTable("access_keys"); + modelBuilder.Entity().ToTable("allow_apps"); + modelBuilder.Entity().ToTable("applications"); // Base. base.OnModelCreating(modelBuilder); diff --git a/LIN.Cloud.Identity.Persistence/Extensions/DataBaseExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/DataBaseExtensions.cs new file mode 100644 index 0000000..e6d1d93 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Extensions/DataBaseExtensions.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; + +namespace LIN.Cloud.Identity.Persistence.Extensions; + +public static class DataBaseExtensions +{ + + /// + /// Obtener transacción. + /// /// Agregar servicios de persistence. /// @@ -29,7 +28,6 @@ public static IServiceCollection AddPersistence(this IServiceCollection services } - /// /// Habilitar el servicio de base de datos. /// @@ -49,6 +47,4 @@ public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) return app; } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs b/LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs index 4ba6287..554198e 100644 --- a/LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs +++ b/LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs @@ -2,7 +2,6 @@ namespace LIN.Cloud.Identity.Persistence.Models; - public class PassKeyDBModel { diff --git a/LIN.Cloud.Identity.Persistence/Models/A.cs b/LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs similarity index 100% rename from LIN.Cloud.Identity.Persistence/Models/A.cs rename to LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs diff --git a/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs index df89022..0193ff7 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs @@ -3,7 +3,6 @@ namespace LIN.Cloud.Identity.Persistence.Queries; - public class IdentityFindable : IFindable { public IQueryable GetAccounts(int id, Models.QueryObjectFilter filter) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 1bc2029..1a9fde0 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -5,7 +5,6 @@ namespace LIN.Cloud.Identity.Areas.Accounts; public class AccountController(Data.Accounts accountData) : ControllerBase { - /// /// Crear una cuenta LIN. /// @@ -61,7 +60,6 @@ public async Task Create([FromBody] AccountModel? modelo) } - /// /// Obtener una cuenta. /// @@ -104,7 +102,6 @@ public async Task> Read([FromQuery] int id, [F } - /// /// Obtener una cuenta. /// @@ -148,7 +145,6 @@ public async Task> Read([FromQuery] string use } - /// /// Obtener una cuenta según Id de identidad. /// @@ -191,7 +187,6 @@ public async Task> ReadByIdentity([FromQuery] } - /// /// Obtener una cuenta según Id de identidad. /// @@ -219,7 +214,6 @@ public async Task> ReadByIdentity([FromBody] L } - /// /// Obtiene una lista de diez (10) usuarios que coincidan con un patron. /// @@ -254,7 +248,6 @@ public async Task> Search([FromQuery] string p } - /// /// Obtiene una lista cuentas. /// @@ -281,5 +274,4 @@ public async Task> ReadAll([FromBody] List - /// Inicia una sesión de usuario + /// Inicia una sesión de usuario. /// - /// Usuario único - /// Contraseña del usuario - /// Key de aplicación + /// Usuario único. + /// Contraseña del usuario. + /// Key de aplicación. [HttpGet("login")] public async Task> Login([FromQuery] string user, [FromQuery] string password, [FromHeader] string application) { @@ -75,15 +74,13 @@ public async Task> Login([FromQuery] string us }; return http; - } - /// - /// Inicia una sesión de usuario por medio del token + /// Inicia una sesión de usuario por medio del token. /// - /// Token de acceso + /// Token de acceso. [HttpGet("LoginWithToken")] [IdentityToken] public async Task> LoginWithToken([FromHeader] string token) @@ -107,6 +104,4 @@ public async Task> LoginWithToken([FromHeader] } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 96ebcf3..efb7147 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -7,11 +7,10 @@ namespace LIN.Cloud.Identity.Areas.Authentication; public class IntentsController(Data.PassKeys passkeyData) : ControllerBase { - /// /// Obtiene la lista de intentos de llaves de paso están activos. /// - /// Token de acceso + /// Token de acceso. [HttpGet] [IdentityToken] public HttpReadAllResponse GetAll([FromHeader] string token) @@ -39,7 +38,7 @@ where I.Expiración > timeNow // Retorna return new(Responses.Success, intentos); } - catch + catch (Exception) { return new(Responses.Undefined) { @@ -49,18 +48,16 @@ where I.Expiración > timeNow } - /// /// Obtiene la lista de intentos de llaves de paso están activos. /// - /// Token de acceso + /// Token de acceso. [HttpGet("count")] [IdentityToken] public async Task> Count([FromHeader] string token) { try { - // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); @@ -69,7 +66,7 @@ public async Task> Count([FromHeader] string token) // Retorna return new(Responses.Success, x.Model); } - catch + catch (Exception) { return new(Responses.Undefined) { @@ -78,6 +75,4 @@ public async Task> Count([FromHeader] string token) } } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index d37a9f0..37f96ba 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Areas.Directories; - [Route("[controller]")] public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : ControllerBase { diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index 303cb82..bdcd758 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Areas.Groups; - [Route("[controller]")] public class GroupsController(Data.Groups groupData, RolesIam rolesIam) : ControllerBase { @@ -112,7 +111,6 @@ public async Task> ReadOne([FromHeader] string t // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupData.GetOwner(id); @@ -124,7 +122,6 @@ public async Task> ReadOne([FromHeader] string t Response = Responses.Unauthorized }; - // Confirmar el rol. var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); @@ -168,7 +165,6 @@ public async Task> ReadIdentity([FromHeader] str // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupData.GetOwnerByIdentity(id); @@ -180,7 +176,6 @@ public async Task> ReadIdentity([FromHeader] str Response = Responses.Unauthorized }; - // Confirmar el rol. var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index be01368..42e6e2b 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -1,11 +1,9 @@ namespace LIN.Cloud.Identity.Areas.Groups; - [Route("Groups/members")] public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : Controller { - /// /// Crear nuevo integrante. /// @@ -55,7 +53,6 @@ public async Task Create([FromHeader] string token, [FromBod Response = Responses.Unauthorized }; - // Crear el usuario. var response = await groupMembers.Create(model); @@ -65,7 +62,6 @@ public async Task Create([FromHeader] string token, [FromBod } - /// /// Crear nuevos integrantes en un grupo. /// @@ -106,7 +102,6 @@ public async Task Create([FromHeader] string token, [FromHea Response = Responses.Unauthorized }; - // Solo elementos distintos. ids = ids.Distinct().ToList(); @@ -140,7 +135,6 @@ public async Task Create([FromHeader] string token, [FromHea } - /// /// Obtener los integrantes asociados a un grupo. /// @@ -165,7 +159,6 @@ public async Task> ReadMembers([FromHeader] str Response = Responses.Unauthorized }; - // Confirmar el rol. var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); @@ -197,7 +190,6 @@ public async Task> ReadMembers([FromHeader] str } - /// /// Buscar en los integrantes de un grupo. /// @@ -223,7 +215,6 @@ public async Task> Search([FromHeader] string Response = Responses.Unauthorized }; - // Confirmar el rol. var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); @@ -256,7 +247,6 @@ public async Task> Search([FromHeader] string } - /// /// Buscar en los grupos de un grupo. /// @@ -297,7 +287,6 @@ public async Task> SearchOnGroups([FromHeader Response = Responses.Unauthorized }; - // Obtiene los miembros. var members = await groupMembers.SearchGroups(pattern, group); @@ -315,7 +304,6 @@ public async Task> SearchOnGroups([FromHeader } - /// /// Eliminar un integrante /// @@ -340,7 +328,6 @@ public async Task DeleteMembers([FromHeader] string token, [Fr Response = Responses.Unauthorized }; - // Confirmar el rol. var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); @@ -355,7 +342,6 @@ public async Task DeleteMembers([FromHeader] string token, [Fr Response = Responses.Unauthorized }; - // Obtiene el usuario. var response = await groupMembers.Delete(identity, group); @@ -372,7 +358,6 @@ public async Task DeleteMembers([FromHeader] string token, [Fr } - /// /// Obtener los grupos a los que una identidad pertenece. /// @@ -386,7 +371,6 @@ public async Task> OnMembers([FromHeader] string // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Confirmar el rol. var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); @@ -417,6 +401,4 @@ public async Task> OnMembers([FromHeader] string } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 3692bf7..8750e91 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -1,11 +1,9 @@ namespace LIN.Cloud.Identity.Areas.Organizations; - [Route("[controller]")] public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData, RolesIam rolesIam) : ControllerBase { - /// /// Crear nuevo grupo. /// @@ -64,7 +62,6 @@ public async Task Create([FromBody] IdentityRolesModel rolMode } - /// /// Obtener los roles asociados a una identidad. /// @@ -93,11 +90,8 @@ public async Task> ReadAll([FromHeader] Response = Responses.Unauthorized }; - - var isIn = await directoryMembersData.IamIn(identity, organization); - if (isIn.Response != Responses.Success) return new() { @@ -105,7 +99,6 @@ public async Task> ReadAll([FromHeader] Response = Responses.NotFoundDirectory }; - // Obtener el modelo. var response = await identityRolesData.ReadAll(identity, organization); @@ -122,7 +115,6 @@ public async Task> ReadAll([FromHeader] } - /// /// Eliminar los roles asociados a una identidad. /// @@ -152,11 +144,8 @@ public async Task ReadAll([FromHeader] string token, [FromHead Response = Responses.Unauthorized }; - - var isIn = await directoryMembersData.IamIn(identity, organization); - if (isIn.Response != Responses.Success) return new() { @@ -164,7 +153,6 @@ public async Task ReadAll([FromHeader] string token, [FromHead Response = Responses.NotFoundDirectory }; - // Obtener el modelo. var response = await identityRolesData.Remove(identity, rol, organization); @@ -180,6 +168,4 @@ public async Task ReadAll([FromHeader] string token, [FromHead } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs index c1473fb..34f72b2 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs @@ -7,7 +7,6 @@ namespace LIN.Cloud.Identity.Areas.Organizations; public class MemberController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, RolesIam rolesIam) : ControllerBase { - /// /// Crea un nuevo miembro en una organización. /// @@ -72,7 +71,6 @@ public async Task Create([FromBody] AccountModel modelo, [Fr Response = Responses.Unauthorized }; - // Creación del usuario var response = await accountsData.Create(modelo, organization); @@ -80,7 +78,6 @@ public async Task Create([FromBody] AccountModel modelo, [Fr if (response.Response != Responses.Success) return new(response.Response); - // Retorna el resultado return new CreateResponse() { @@ -92,7 +89,6 @@ public async Task Create([FromBody] AccountModel modelo, [Fr } - /// /// Obtiene la lista de integrantes asociados a una organización. /// @@ -105,7 +101,6 @@ public async Task>> ReadAll([FromH // Información del token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); ; - // Confirmar el rol. var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); @@ -120,7 +115,6 @@ public async Task>> ReadAll([FromH Response = Responses.Unauthorized }; - // Obtiene los miembros. var members = await directoryMembersData.ReadMembersByOrg(organization); @@ -137,5 +131,4 @@ public async Task>> ReadAll([FromH } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 37e1787..58b4194 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -5,7 +5,6 @@ namespace LIN.Cloud.Identity.Areas.Organizations; public class OrganizationsController(Data.Organizations organizationsData, Data.DirectoryMembers directoryMembersData) : ControllerBase { - /// /// Crea una nueva organización. /// @@ -35,7 +34,6 @@ public async Task Create([FromBody] OrganizationModel modelo modelo.Directory.Identity.Status = IdentityStatus.Enable; } - // Creación de la organización. var response = await organizationsData.Create(modelo); @@ -54,7 +52,6 @@ public async Task Create([FromBody] OrganizationModel modelo } - /// /// Obtiene una organización por medio del Id. /// @@ -72,7 +69,6 @@ public async Task> ReadOneByID([FromQuery // Token. JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene la organización var response = await organizationsData.Read(id); @@ -102,12 +98,8 @@ public async Task> ReadOneByID([FromQuery Name = "Organización privada" } }; - - - } - return new ReadOneResponse() { Response = Responses.Success, @@ -117,8 +109,6 @@ public async Task> ReadOneByID([FromQuery } - - /// /// Obtiene las organizaciones donde un usuario es miembro. /// @@ -138,6 +128,4 @@ public async Task> ReadAll([FromHeader] s } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index d1543bb..2e0dba9 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -1,10 +1,8 @@ namespace LIN.Cloud.Identity.Data; - public class Accounts(DataContext context) { - /// /// Crear nueva cuenta. [Transacción] /// @@ -18,7 +16,7 @@ public async Task> Create(AccountModel modelo, int modelo.IdentityId = 0; // Transacción. - using var transaction = context.Database.BeginTransaction(); + using var transaction = context.Database.GetTransaction(); try { @@ -60,7 +58,7 @@ public async Task> Create(AccountModel modelo, int // Confirmar los cambios. - transaction.Commit(); + transaction?.Commit(); return new() { @@ -71,7 +69,7 @@ public async Task> Create(AccountModel modelo, int } catch (Exception) { - transaction.Rollback(); + transaction?.Rollback(); return new() { Response = Responses.ExistAccount @@ -81,7 +79,6 @@ public async Task> Create(AccountModel modelo, int } - /// /// Obtener una cuenta según el Id. /// @@ -122,7 +119,6 @@ public async Task> Read(int id, QueryAccountFilter } - /// /// Obtener una cuenta según el identificador único. /// @@ -163,7 +159,6 @@ public async Task> Read(string unique, QueryAccoun } - /// /// Obtener una cuenta según el id de la identidad. /// @@ -204,7 +199,6 @@ public async Task> ReadByIdentity(int id, QueryAcc } - /// /// Buscar por patron. /// @@ -220,7 +214,7 @@ public async Task> Search(string pattern, QueryAcc List accountModels = await Builders.Account.Search(pattern, filters, context).Take(10).ToListAsync(); // Si no existe el modelo - if (accountModels == null) + if (accountModels == null ||!accountModels.Any()) return new(Responses.NotRows); return new(Responses.Success, accountModels); @@ -233,7 +227,6 @@ public async Task> Search(string pattern, QueryAcc } - /// /// Obtiene los usuarios con IDs coincidentes /// @@ -251,7 +244,7 @@ public async Task> FindAll(List ids, QueryAcc var result = await query.ToListAsync(); // Si no existe el modelo - if (result == null) + if (result == null || !result.Any()) return new(Responses.NotRows); return new(Responses.Success, result); @@ -264,7 +257,6 @@ public async Task> FindAll(List ids, QueryAcc } - /// /// Obtiene los usuarios con IDs coincidentes /// @@ -282,7 +274,7 @@ public async Task> FindAllByIdentities(List i var result = await query.ToListAsync(); // Si no existe el modelo - if (result == null) + if (result == null || !result.Any()) return new(Responses.NotRows); return new(Responses.Success, result); @@ -295,5 +287,4 @@ public async Task> FindAllByIdentities(List i } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/AllowApps.cs b/LIN.Cloud.Identity/Data/AllowApps.cs index 9b44a19..3ba9ba1 100644 --- a/LIN.Cloud.Identity/Data/AllowApps.cs +++ b/LIN.Cloud.Identity/Data/AllowApps.cs @@ -61,8 +61,6 @@ public async Task> ReadAll(int id) var identities = await identityService.GetIdenties(id); - - var query = await (from allow in context.AllowApps where identities.Contains(allow.IdentityId) select new AllowApp @@ -85,7 +83,6 @@ where identities.Contains(allow.IdentityId) catch (Exception) { } - return new(); } diff --git a/LIN.Cloud.Identity/Data/DirectoryMembers.cs b/LIN.Cloud.Identity/Data/DirectoryMembers.cs index 6d6cf3c..c50eb66 100644 --- a/LIN.Cloud.Identity/Data/DirectoryMembers.cs +++ b/LIN.Cloud.Identity/Data/DirectoryMembers.cs @@ -234,8 +234,6 @@ on org.DirectoryId equals gm.GroupId IdentityId = gm.IdentityId }).ToListAsync(); - - // Si la cuenta no existe. if (members == null) return new() diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 1e6dca4..0cd1baa 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -1,5 +1,4 @@ using Http.Extensions; -using LIN.Cloud.Identity.Persistence.Extensions; using LIN.Cloud.Identity.Services.Auth.Interfaces; using LIN.Cloud.Identity.Services.Extensions; using LIN.Cloud.Identity.Services.Realtime; @@ -34,4 +33,4 @@ app.UseAuthorization(); app.MapControllers(); -app.Run(); +app.Run(); \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/AllowService.cs b/LIN.Cloud.Identity/Services/Auth/AllowService.cs index b9e78bd..8a43a91 100644 --- a/LIN.Cloud.Identity/Services/Auth/AllowService.cs +++ b/LIN.Cloud.Identity/Services/Auth/AllowService.cs @@ -2,11 +2,9 @@ namespace LIN.Cloud.Identity.Services.Auth; - public class AllowService(DataContext context) : IAllowService { - /// /// Validar si una lista de identidades puede acceder a una aplicación. /// @@ -25,5 +23,4 @@ public async Task IsAllow(IEnumerable identities, int appId) } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 99911f7..1ccb65d 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Services.Auth; - public class Authentication(Data.Accounts accountData) : Interfaces.IAuthentication { @@ -29,8 +28,6 @@ public class Authentication(Data.Accounts accountData) : Interfaces.IAuthenticat public AccountModel? Account { get; set; } = null; - - /// /// Establecer credenciales. /// @@ -62,7 +59,7 @@ public async Task Start() // Validar contraseña. bool password = ValidatePassword(); -// Si la contraseña es invalida. + // Si la contraseña es invalida. if (!password) return Responses.InvalidPassword; diff --git a/LIN.Cloud.Identity/Services/Auth/JwtService.cs b/LIN.Cloud.Identity/Services/Auth/JwtService.cs index 9ffbf43..0590ef7 100644 --- a/LIN.Cloud.Identity/Services/Auth/JwtService.cs +++ b/LIN.Cloud.Identity/Services/Auth/JwtService.cs @@ -1,18 +1,14 @@ namespace LIN.Cloud.Identity.Services.Auth; - public class JwtService { - - /// /// Llave del token /// private static string JwtKey { get; set; } = string.Empty; - /// /// Inicia el servicio JwtService /// @@ -22,8 +18,6 @@ public static void Open() } - - /// /// Genera un JSON Web Token /// @@ -60,7 +54,6 @@ internal static string Generate(AccountModel user, int appID) } - /// /// Valida un JSON Web token /// @@ -135,5 +128,4 @@ internal static JwtModel Validate(string token) } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index b82ba48..73877b4 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -7,8 +7,6 @@ namespace LIN.Cloud.Identity.Services.Extensions; public static class LocalServices { - - /// /// Agregar servicios locales. /// @@ -36,5 +34,4 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 576efd7..4e08572 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -8,14 +8,14 @@ public class RolesIam(DataContext context, IIdentityService identityService) /// - /// Obtener los roles de una identitdad en una organización. + /// Obtener los roles de una identidad en una organización. /// /// Id de la identidad. /// Id de la organización. public async Task> RolesOn(int identity, int organization) { - // Identitades + // Identidades. var identities = await identityService.GetIdenties(identity); // Obtener roles. diff --git a/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs index cac70c9..0a1e1c9 100644 --- a/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs +++ b/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs @@ -14,7 +14,7 @@ public class QuotaMiddleware : IMiddleware /// /// Solicitudes que se están procesando actualmente. /// - private volatile static int ActualQuote = 0; + private static volatile int ActualQuote = 0; diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index e6aaf31..020bfe1 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -12,7 +12,7 @@ public partial class PassKeyHub(Data.PassKeys passKeysData) : Hub /// String: Usuario. /// PasskeyModels: Lista de intentos. /// - public readonly static Dictionary> Attempts = []; + public static readonly Dictionary> Attempts = []; /// diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index e9283ba..78a835a 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -23,3 +23,4 @@ global using System.Security.Claims; // Framework. global using System.Text; +global using LIN.Cloud.Identity.Persistence.Extensions; \ No newline at end of file diff --git a/LIN.Identity.Tests/Data/Accounts.cs b/LIN.Identity.Tests/Data/Accounts.cs new file mode 100644 index 0000000..dab1a0d --- /dev/null +++ b/LIN.Identity.Tests/Data/Accounts.cs @@ -0,0 +1,274 @@ +using LIN.Cloud.Identity.Persistence.Contexts; +using LIN.Cloud.Identity.Services.Models; +using LIN.Types.Cloud.Identity.Models; +using LIN.Types.Responses; +using Microsoft.EntityFrameworkCore; + +namespace LIN.Identity.Tests.Data; + +public class Accounts +{ + + private static DataContext GetInMemoryDbContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: "TestDatabase") + .Options; + + return new DataContext(options); + } + + [Fact] + public async Task Create_ShouldReturnSuccessResponse_WhenAccountIsCreated() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + var accountModel = new AccountModel { Name = "Test Account" }; + + // Act + var response = await accounts.Create(accountModel); + + // Assert + Assert.Equal(Responses.Success, response.Response); + Assert.NotNull(response.Model); + Assert.Equal("Test Account", response.Model.Name); + } + + [Fact] + public async Task Create_ShouldReturnExistAccountResponse_WhenOrganizationNotFound() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + var accountModel = new AccountModel { Name = "Test Account" }; + + // Act + var response = await accounts.Create(accountModel, organization: 999); + + // Assert + Assert.Equal(Responses.ExistAccount, response.Response); + } + + [Fact] + public async Task Read_ShouldReturnNotRowsResponse_WhenAccountDoesNotExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + + // Act + var response = await accounts.Read(999, new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.NotRows, response.Response); + } + + [Fact] + public async Task ReadByUnique_ShouldReturnSuccessResponse_WhenAccountExists() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + var accountModel = new AccountModel + { + Name = "Test Account", + Identity = new IdentityModel + { + Unique = "unique-id", + EffectiveTime = DateTime.Now, + ExpirationTime = DateTime.Now.AddDays(1) + } + }; + await context.Accounts.AddAsync(accountModel); + await context.SaveChangesAsync(); + + // Act + var response = await accounts.Read("unique-id", new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.Success, response.Response); + Assert.NotNull(response.Model); + Assert.Equal("Test Account", response.Model.Name); + } + + [Fact] + public async Task ReadByUnique_ShouldReturnNotRowsResponse_WhenAccountDoesNotExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + + // Act + var response = await accounts.Read("non-existent-id", new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.NotRows, response.Response); + } + + [Fact] + public async Task ReadByIdentity_ShouldReturnSuccessResponse_WhenAccountExists() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + var accountModel = new AccountModel + { + Name = "Test Account", + Identity = new IdentityModel + { + Unique = "unique-id", + EffectiveTime = DateTime.Now, + ExpirationTime = DateTime.Now.AddDays(1) + } + }; + await context.Accounts.AddAsync(accountModel); + await context.SaveChangesAsync(); + + // Act + var response = await accounts.ReadByIdentity(1, new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.Success, response.Response); + Assert.NotNull(response.Model); + Assert.Equal("Test Account", response.Model.Name); + } + + [Fact] + public async Task ReadByIdentity_ShouldReturnNotRowsResponse_WhenAccountDoesNotExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + + // Act + var response = await accounts.ReadByIdentity(999, new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.NotRows, response.Response); + } + + [Fact] + public async Task Search_ShouldReturnSuccessResponse_WhenAccountsMatchPattern() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + var accountModel = new AccountModel + { + Name = "Test Account", + Identity = new IdentityModel + { + Unique = "Test", + EffectiveTime = DateTime.Now, + ExpirationTime = DateTime.Now.AddDays(1) + } + }; + await context.Accounts.AddAsync(accountModel); + await context.SaveChangesAsync(); + + // Act + var response = await accounts.Search("Test", new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.Success, response.Response); + Assert.NotEmpty(response.Models); + } + + [Fact] + public async Task Search_ShouldReturnNotRowsResponse_WhenNoAccountsMatchPattern() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + + // Act + var response = await accounts.Search("NonExistentPattern", new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.NotRows, response.Response); + } + + [Fact] + public async Task FindAll_ShouldReturnSuccessResponse_WhenAccountsExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + var accountModel = new AccountModel + { + Name = "Test Account", + Identity = new() + { + Unique = "test", + EffectiveTime = DateTime.Now, + ExpirationTime = DateTime.Now.AddDays(1) + } + }; + await context.Accounts.AddAsync(accountModel); + await context.SaveChangesAsync(); + + // Act + var response = await accounts.FindAll(new List { accountModel.Id }, new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.Success, response.Response); + Assert.NotEmpty(response.Models); + } + + [Fact] + public async Task FindAll_ShouldReturnNotRowsResponse_WhenAccountsDoNotExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + + // Act + var response = await accounts.FindAll(new List { 999 }, new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.NotRows, response.Response); + } + + [Fact] + public async Task FindAllByIdentities_ShouldReturnSuccessResponse_WhenAccountsExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + var accountModel = new AccountModel + { + Name = "Test Account", + Identity = new() + { + Unique = "test", + EffectiveTime = DateTime.Now, + ExpirationTime = DateTime.Now.AddDays(1) + } + }; + await context.Accounts.AddAsync(accountModel); + await context.SaveChangesAsync(); + + // Act + var response = await accounts.FindAllByIdentities(new List { 1 }, new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.Success, response.Response); + Assert.NotEmpty(response.Models); + } + + [Fact] + public async Task FindAllByIdentities_ShouldReturnNotRowsResponse_WhenAccountsDoNotExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var accounts = new Cloud.Identity.Data.Accounts(context); + + // Act + var response = await accounts.FindAllByIdentities(new List { 999 }, new QueryAccountFilter()); + + // Assert + Assert.Equal(Responses.NotRows, response.Response); + } + +} \ No newline at end of file diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 80819ba..18ed4f4 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -14,7 +14,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + + all @@ -23,6 +26,7 @@ + diff --git a/LIN.Identity.Tests/Validations.cs b/LIN.Identity.Tests/Validations.cs index 7fb2b97..5d26a33 100644 --- a/LIN.Identity.Tests/Validations.cs +++ b/LIN.Identity.Tests/Validations.cs @@ -6,7 +6,6 @@ namespace LIN.Identity.Tests; public class Validations { - /// /// Validar cuentas. /// @@ -72,6 +71,4 @@ public void ValidateAccounts() } - - } \ No newline at end of file From 3305725530161918153fa741d956612f8aab7384 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 21 Sep 2024 11:29:19 -0500 Subject: [PATCH 095/178] Unitarias --- LIN.Cloud.Identity/Data/AllowApps.cs | 2 +- .../Services/Auth/JwtService.cs | 4 +- LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 2 +- .../Services/Utils/IIdentityService.cs | 2 +- .../Services/Utils/IdentityService.cs | 18 ++--- LIN.Identity.Tests/Auth/JwtServiceTests.cs | 70 +++++++++++++++++++ LIN.Identity.Tests/LIN.Identity.Tests.csproj | 2 +- 7 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 LIN.Identity.Tests/Auth/JwtServiceTests.cs diff --git a/LIN.Cloud.Identity/Data/AllowApps.cs b/LIN.Cloud.Identity/Data/AllowApps.cs index 3ba9ba1..1e729b2 100644 --- a/LIN.Cloud.Identity/Data/AllowApps.cs +++ b/LIN.Cloud.Identity/Data/AllowApps.cs @@ -59,7 +59,7 @@ public async Task> ReadAll(int id) try { - var identities = await identityService.GetIdenties(id); + var identities = await identityService.GetIdentities(id); var query = await (from allow in context.AllowApps where identities.Contains(allow.IdentityId) diff --git a/LIN.Cloud.Identity/Services/Auth/JwtService.cs b/LIN.Cloud.Identity/Services/Auth/JwtService.cs index 0590ef7..fe4ebd2 100644 --- a/LIN.Cloud.Identity/Services/Auth/JwtService.cs +++ b/LIN.Cloud.Identity/Services/Auth/JwtService.cs @@ -22,7 +22,7 @@ public static void Open() /// Genera un JSON Web Token /// /// Modelo de usuario - internal static string Generate(AccountModel user, int appID) + public static string Generate(AccountModel user, int appID) { if (JwtKey == string.Empty) @@ -58,7 +58,7 @@ internal static string Generate(AccountModel user, int appID) /// Valida un JSON Web token /// /// Token a validar - internal static JwtModel Validate(string token) + public static JwtModel Validate(string token) { try { diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 4e08572..188cbfd 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -16,7 +16,7 @@ public async Task> RolesOn(int identity, int organization) { // Identidades. - var identities = await identityService.GetIdenties(identity); + var identities = await identityService.GetIdentities(identity); // Obtener roles. var roles = await (from rol in context.IdentityRoles diff --git a/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs b/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs index dc086ff..c7c5763 100644 --- a/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs +++ b/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs @@ -2,5 +2,5 @@ public interface IIdentityService { - Task> GetIdenties(int identity); + Task> GetIdentities(int identity); } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs index 8f88e3f..8eb5200 100644 --- a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs +++ b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs @@ -4,16 +4,14 @@ public class IdentityService(DataContext context) : IIdentityService { - - - public async Task> GetIdenties(int identity) + public async Task> GetIdentities(int identity) { List result = [identity]; - await GetIdenties(identity, result); + await GetIdentities(identity, result); return result; } - private async Task GetIdenties(int identity, List ids) + private async Task GetIdentities(int identity, List ids) { // Consulta. var query = from id in context.Identities @@ -42,17 +40,9 @@ private async Task GetIdenties(int identity, List ids) // Recorrer. foreach (var @base in bases) - await GetIdenties(@base, ids); + await GetIdentities(@base, ids); } } - - - - - - - - } \ No newline at end of file diff --git a/LIN.Identity.Tests/Auth/JwtServiceTests.cs b/LIN.Identity.Tests/Auth/JwtServiceTests.cs new file mode 100644 index 0000000..14bd486 --- /dev/null +++ b/LIN.Identity.Tests/Auth/JwtServiceTests.cs @@ -0,0 +1,70 @@ +using LIN.Cloud.Identity.Services.Auth; +using LIN.Types.Cloud.Identity.Models; + +namespace LIN.Identity.Tests.Auth +{ + public class JwtServiceTests + { + private const string TestJwtKey = "wTgdLCN4GuV37K5I1H6C331Z146tylLINkiZt69nokIdwTgdLCN4GuV37K5I1H6C331Z146tylLINkiZt69nokId"; + + public JwtServiceTests() + { + // Configurar la llave JWT para las pruebas + typeof(JwtService).GetProperty("JwtKey", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).SetValue(null, TestJwtKey); + } + + [Fact] + public void Generate_ShouldReturnValidToken() + { + // Arrange + var user = new AccountModel + { + Id = 1, + Identity = new IdentityModel { Unique = "unique_user" } + }; + int appID = 123; + + // Act + var token = JwtService.Generate(user, appID); + + // Assert + Assert.False(string.IsNullOrEmpty(token)); + } + + [Fact] + public void Validate_ShouldReturnAuthenticatedModel_WhenTokenIsValid() + { + // Arrange + var user = new AccountModel + { + Id = 1, + Identity = new IdentityModel { Unique = "unique_user" } + }; + int appID = 123; + var token = JwtService.Generate(user, appID); + + // Act + var result = JwtService.Validate(token); + + // Assert + Assert.True(result.IsAuthenticated); + Assert.Equal(user.Id, result.AccountId); + Assert.Equal(user.Identity.Unique, result.Unique); + Assert.Equal(appID, result.ApplicationId); + } + + [Fact] + public void Validate_ShouldReturnNotAuthenticatedModel_WhenTokenIsInvalid() + { + // Arrange + var invalidToken = "invalid_token"; + + // Act + var result = JwtService.Validate(invalidToken); + + // Assert + Assert.False(result.IsAuthenticated); + } + + } +} \ No newline at end of file diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 18ed4f4..6de0cf1 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -16,7 +16,7 @@ - + From 049b3af1361d27dd911f1841bc5e54542903d21c Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 21 Sep 2024 11:33:49 -0500 Subject: [PATCH 096/178] Mejoras de unitarias --- LIN.Identity.Tests/Auth/JwtServiceTests.cs | 103 ++++++++++----------- LIN.Identity.Tests/Data/Accounts.cs | 12 ++- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/LIN.Identity.Tests/Auth/JwtServiceTests.cs b/LIN.Identity.Tests/Auth/JwtServiceTests.cs index 14bd486..17becdb 100644 --- a/LIN.Identity.Tests/Auth/JwtServiceTests.cs +++ b/LIN.Identity.Tests/Auth/JwtServiceTests.cs @@ -1,70 +1,69 @@ using LIN.Cloud.Identity.Services.Auth; using LIN.Types.Cloud.Identity.Models; -namespace LIN.Identity.Tests.Auth +namespace LIN.Identity.Tests.Auth; + +public class JwtServiceTests { - public class JwtServiceTests - { - private const string TestJwtKey = "wTgdLCN4GuV37K5I1H6C331Z146tylLINkiZt69nokIdwTgdLCN4GuV37K5I1H6C331Z146tylLINkiZt69nokId"; + private const string TestJwtKey = "wTgdLCN4GuV37K5I1H6C331Z146tylLINkiZt69nokIdwTgdLCN4GuV37K5I1H6C331Z146tylLINkiZt69nokId"; - public JwtServiceTests() - { - // Configurar la llave JWT para las pruebas - typeof(JwtService).GetProperty("JwtKey", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).SetValue(null, TestJwtKey); - } + public JwtServiceTests() + { + // Configurar la llave JWT para las pruebas + typeof(JwtService).GetProperty("JwtKey", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)?.SetValue(null, TestJwtKey); + } - [Fact] - public void Generate_ShouldReturnValidToken() + [Fact] + public void GenerateValidToken() + { + // Arrange + var user = new AccountModel { - // Arrange - var user = new AccountModel - { - Id = 1, - Identity = new IdentityModel { Unique = "unique_user" } - }; - int appID = 123; + Id = 1, + Identity = new IdentityModel { Unique = "unique_user" } + }; + int appID = 123; - // Act - var token = JwtService.Generate(user, appID); + // Act + var token = JwtService.Generate(user, appID); - // Assert - Assert.False(string.IsNullOrEmpty(token)); - } + // Assert + Assert.False(string.IsNullOrEmpty(token)); + } - [Fact] - public void Validate_ShouldReturnAuthenticatedModel_WhenTokenIsValid() + [Fact] + public void ValidateTrueToken() + { + // Arrange + var user = new AccountModel { - // Arrange - var user = new AccountModel - { - Id = 1, - Identity = new IdentityModel { Unique = "unique_user" } - }; - int appID = 123; - var token = JwtService.Generate(user, appID); - - // Act - var result = JwtService.Validate(token); + Id = 1, + Identity = new IdentityModel { Unique = "unique_user" } + }; + int appID = 123; + var token = JwtService.Generate(user, appID); - // Assert - Assert.True(result.IsAuthenticated); - Assert.Equal(user.Id, result.AccountId); - Assert.Equal(user.Identity.Unique, result.Unique); - Assert.Equal(appID, result.ApplicationId); - } + // Act + var result = JwtService.Validate(token); - [Fact] - public void Validate_ShouldReturnNotAuthenticatedModel_WhenTokenIsInvalid() - { - // Arrange - var invalidToken = "invalid_token"; + // Assert + Assert.True(result.IsAuthenticated); + Assert.Equal(user.Id, result.AccountId); + Assert.Equal(user.Identity.Unique, result.Unique); + Assert.Equal(appID, result.ApplicationId); + } - // Act - var result = JwtService.Validate(invalidToken); + [Fact] + public void ValidateFalseToken() + { + // Arrange + var invalidToken = "invalid_token"; - // Assert - Assert.False(result.IsAuthenticated); - } + // Act + var result = JwtService.Validate(invalidToken); + // Assert + Assert.False(result.IsAuthenticated); } + } \ No newline at end of file diff --git a/LIN.Identity.Tests/Data/Accounts.cs b/LIN.Identity.Tests/Data/Accounts.cs index dab1a0d..cf62451 100644 --- a/LIN.Identity.Tests/Data/Accounts.cs +++ b/LIN.Identity.Tests/Data/Accounts.cs @@ -9,6 +9,10 @@ namespace LIN.Identity.Tests.Data; public class Accounts { + /// + /// Obtener base de datos en memoria. + /// + /// private static DataContext GetInMemoryDbContext() { var options = new DbContextOptionsBuilder() @@ -209,7 +213,7 @@ public async Task FindAll_ShouldReturnSuccessResponse_WhenAccountsExist() await context.SaveChangesAsync(); // Act - var response = await accounts.FindAll(new List { accountModel.Id }, new QueryAccountFilter()); + var response = await accounts.FindAll([accountModel.Id], new QueryAccountFilter()); // Assert Assert.Equal(Responses.Success, response.Response); @@ -224,7 +228,7 @@ public async Task FindAll_ShouldReturnNotRowsResponse_WhenAccountsDoNotExist() var accounts = new Cloud.Identity.Data.Accounts(context); // Act - var response = await accounts.FindAll(new List { 999 }, new QueryAccountFilter()); + var response = await accounts.FindAll([999], new QueryAccountFilter()); // Assert Assert.Equal(Responses.NotRows, response.Response); @@ -250,7 +254,7 @@ public async Task FindAllByIdentities_ShouldReturnSuccessResponse_WhenAccountsEx await context.SaveChangesAsync(); // Act - var response = await accounts.FindAllByIdentities(new List { 1 }, new QueryAccountFilter()); + var response = await accounts.FindAllByIdentities([1], new QueryAccountFilter()); // Assert Assert.Equal(Responses.Success, response.Response); @@ -265,7 +269,7 @@ public async Task FindAllByIdentities_ShouldReturnNotRowsResponse_WhenAccountsDo var accounts = new Cloud.Identity.Data.Accounts(context); // Act - var response = await accounts.FindAllByIdentities(new List { 999 }, new QueryAccountFilter()); + var response = await accounts.FindAllByIdentities([999], new QueryAccountFilter()); // Assert Assert.Equal(Responses.NotRows, response.Response); From fc5996ba3fdc40e25c95b2f979608f0ffc8ac854 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 21 Sep 2024 11:37:16 -0500 Subject: [PATCH 097/178] Nuevas validaciones de formato --- LIN.Identity.Tests/Validations.cs | 175 ++++++++++++++++++++---------- 1 file changed, 117 insertions(+), 58 deletions(-) diff --git a/LIN.Identity.Tests/Validations.cs b/LIN.Identity.Tests/Validations.cs index 5d26a33..ec03ed8 100644 --- a/LIN.Identity.Tests/Validations.cs +++ b/LIN.Identity.Tests/Validations.cs @@ -1,74 +1,133 @@ -using LIN.Types.Cloud.Identity.Models; +using LIN.Cloud.Identity.Services.Formats; +using LIN.Types.Cloud.Identity.Enumerations; +using LIN.Types.Cloud.Identity.Models; namespace LIN.Identity.Tests; - -public class Validations +public class AccountTests { - /// - /// Validar cuentas. - /// [Fact] - public void ValidateAccounts() + public void Validate_ShouldReturnFalse_WhenNameIsEmpty() { + // Arrange + var account = new AccountModel + { + Name = "", + Identity = new IdentityModel { Unique = "unique_user" } + }; + + // Act + var result = Account.Validate(account); + + // Assert + Assert.False(result.pass); + Assert.Equal("La cuenta debe de tener un nombre valido.", result.message); + } - var accounts = new Dictionary + [Fact] + public void Validate_ShouldReturnFalse_WhenIdentityIsNull() + { + // Arrange + var account = new AccountModel { - { - new() - { - IdentityService = Types.Cloud.Identity.Enumerations.IdentityService.LIN, - Identity = new() - { - }, - Name = "John Doe", - Password = "password", - Visibility = Types.Cloud.Identity.Enumerations.Visibility.Visible - }, false - }, - { - new() - { - IdentityService = Types.Cloud.Identity.Enumerations.IdentityService.LIN, - Identity = new() - { - Unique = "johnDoe" - }, - Name = "John Doe", - Password = "password", - Visibility = Types.Cloud.Identity.Enumerations.Visibility.Visible - }, true - }, - { - new() - { - - }, false - }, - - { - new() - { - IdentityService = Types.Cloud.Identity.Enumerations.IdentityService.LIN, - Identity = new() - { - Unique = "johnDoe" - }, - Name = "", - Password = "", - Visibility = Types.Cloud.Identity.Enumerations.Visibility.Visible - }, false - }, + Name = "Test Account", + Identity = null + }; + + // Act + var result = Account.Validate(account); + + // Assert + Assert.False(result.pass); + Assert.Equal("La cuenta debe de tener una identidad unica valida.", result.message); + } + + [Fact] + public void Validate_ShouldReturnFalse_WhenIdentityUniqueIsEmpty() + { + // Arrange + var account = new AccountModel + { + Name = "Test Account", + Identity = new IdentityModel { Unique = "" } + }; + + // Act + var result = Account.Validate(account); + + // Assert + Assert.False(result.pass); + Assert.Equal("La cuenta debe de tener una identidad unica valida.", result.message); + } + + [Fact] + public void Validate_ShouldReturnFalse_WhenIdentityUniqueContainsNonAlphanumeric() + { + // Arrange + var account = new AccountModel + { + Name = "Test Account", + Identity = new IdentityModel { Unique = "unique_user!" } + }; + + // Act + var result = Account.Validate(account); + + // Assert + Assert.False(result.pass); + Assert.Equal("La identidad de la cuenta no puede contener símbolos NO alfanuméricos.", result.message); + } + + [Fact] + public void Validate_ShouldReturnTrue_WhenAccountIsValid() + { + // Arrange + var account = new AccountModel + { + Name = "Test Account", + Identity = new IdentityModel { Unique = "uniqueuser" } }; + // Act + var result = Account.Validate(account); + + // Assert + Assert.True(result.pass); + Assert.Equal("", result.message); + } - foreach (var account in accounts) + [Fact] + public void Process_ShouldReturnProcessedAccountModel() + { + // Arrange + var account = new AccountModel { - (bool final, _) = LIN.Cloud.Identity.Services.Formats.Account.Validate(account.Key); - Assert.Equal(account.Value, final); - } + Name = " Test Account ", + Profile = new byte[] { 0x01, 0x02 }, + Password = "password", + Visibility = Visibility.Visible, + Identity = new IdentityModel + { + EffectiveTime = DateTime.Now, + ExpirationTime = DateTime.Now.AddYears(1), + Unique = "uniqueuser" + } + }; + + // Act + var processedAccount = Account.Process(account); + // Assert + Assert.Equal("Test Account", processedAccount.Name); + Assert.Equal(account.Profile, processedAccount.Profile); + Assert.NotEqual("password", processedAccount.Password); // Assuming encryption changes the password + Assert.Equal(Visibility.Visible, processedAccount.Visibility); + Assert.Equal("uniqueuser", processedAccount.Identity.Unique); + Assert.Equal(IdentityStatus.Enable, processedAccount.Identity.Status); + Assert.Equal(IdentityType.Account, processedAccount.Identity.Type); + Assert.Equal(account.Identity.EffectiveTime, processedAccount.Identity.EffectiveTime); + Assert.Equal(account.Identity.ExpirationTime, processedAccount.Identity.ExpirationTime); } -} \ No newline at end of file +} From fcd12f1d31ce97717f5248e6a49f33b93bd3e924 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 21 Sep 2024 11:48:52 -0500 Subject: [PATCH 098/178] =?UTF-8?q?FORMATO=20DE=20C=C3=93DIGO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 17 ------------ .../Services/Models/JwtModel.cs | 2 -- .../Services/Models/QueryAccountFilter.cs | 2 -- .../Services/Models/QueryIdentityFilter.cs | 2 -- .../Services/Realtime/PassKeyHub.cs | 27 +++++-------------- .../Services/Realtime/PassKeyHubActions.cs | 7 ----- .../Services/Realtime/PassKeyHubFunctions.cs | 3 --- LIN.Identity.Tests/Validations.cs | 12 ++++----- 8 files changed, 12 insertions(+), 60 deletions(-) diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 188cbfd..5ad52c7 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -6,7 +6,6 @@ namespace LIN.Cloud.Identity.Services.Iam; public class RolesIam(DataContext context, IIdentityService identityService) { - /// /// Obtener los roles de una identidad en una organización. /// @@ -27,21 +26,12 @@ where identities.Contains(rol.IdentityId) return roles; } - - } - - public static class ValidateRoles { - - - - - public static bool ValidateRead(IEnumerable roles) { List availed = @@ -76,11 +66,4 @@ public static bool ValidateAlterMembers(IEnumerable roles) return sets.Any(); } - - - - - - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Models/JwtModel.cs b/LIN.Cloud.Identity/Services/Models/JwtModel.cs index 73f553d..34a32dc 100644 --- a/LIN.Cloud.Identity/Services/Models/JwtModel.cs +++ b/LIN.Cloud.Identity/Services/Models/JwtModel.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Services.Models; - public class JwtModel { @@ -33,5 +32,4 @@ public class JwtModel /// public int ApplicationId { get; set; } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs b/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs index 1c624cb..295e733 100644 --- a/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs +++ b/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Services.Models; - public class QueryAccountFilter { public int AccountContext { get; set; } @@ -12,7 +11,6 @@ public class QueryAccountFilter } - public enum FindOn { StableAccounts, diff --git a/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs b/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs index cb28aa3..8839cac 100644 --- a/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs +++ b/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Services.Models; - public class QueryIdentityFilter { public FindOn FindOn { get; set; } @@ -8,7 +7,6 @@ public class QueryIdentityFilter } - public enum FindOnIdentities { Stable, diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 020bfe1..2e6b414 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -2,11 +2,9 @@ namespace LIN.Cloud.Identity.Services.Realtime; - public partial class PassKeyHub(Data.PassKeys passKeysData) : Hub { - /// /// Lista de intentos Passkey. /// String: Usuario. @@ -27,15 +25,17 @@ public partial class PassKeyHub(Data.PassKeys passKeysData) : Hub public static readonly string ResponseChannel = "#responses"; - - + /// + /// Evento cuando se desconecta. + /// public override Task OnDisconnectedAsync(Exception? exception) { - var e = Attempts.Values.Where(T => T.Where(T => T.HubKey == Context.ConnectionId).Any()).FirstOrDefault() ?? new(); + // Obtener el intento. + var attempt = Attempts.Values.Where(T => T.Where(T => T.HubKey == Context.ConnectionId).Any()).FirstOrDefault() ?? new(); - _ = e.Where(T => + _ = attempt.Where(T => { if (T.HubKey == Context.ConnectionId && T.Status == PassKeyStatus.Undefined) T.Status = PassKeyStatus.Failed; @@ -43,19 +43,10 @@ public override Task OnDisconnectedAsync(Exception? exception) return false; }); - return base.OnDisconnectedAsync(exception); } - - - - - - - - //=========== Dispositivos ===========// @@ -78,8 +69,6 @@ public async Task SendRequest(PassKeyModel modelo) } - - /// /// Recibe una respuesta de passkey /// @@ -237,8 +226,4 @@ public async Task ReceiveRequest(PassKeyModel modelo) } - - - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index 433b6ba..c51fad3 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -1,10 +1,8 @@ namespace LIN.Cloud.Identity.Services.Realtime; - public partial class PassKeyHub { - /// /// Agregar un dispositivo administrador. /// @@ -25,7 +23,6 @@ public async Task JoinAdmin(string token) } - /// /// Nuevo intento de inicio. /// @@ -63,9 +60,6 @@ public async Task JoinIntent(PassKeyModel attempt) else Attempts[attempt.User.ToLower()].Add(attempt); - - - // Yo await Groups.AddToGroupAsync(Context.ConnectionId, $"dbo.{Context.ConnectionId}"); @@ -73,5 +67,4 @@ public async Task JoinIntent(PassKeyModel attempt) } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubFunctions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubFunctions.cs index 45c1b60..d491595 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubFunctions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubFunctions.cs @@ -1,15 +1,12 @@ namespace LIN.Cloud.Identity.Services.Realtime; - public partial class PassKeyHub { - /// /// Construir el nombre de un grupo. /// /// Usuario. public string BuildGroupName(string user) => $"gr.{user.ToLower().Trim()}"; - } \ No newline at end of file diff --git a/LIN.Identity.Tests/Validations.cs b/LIN.Identity.Tests/Validations.cs index ec03ed8..4aa7166 100644 --- a/LIN.Identity.Tests/Validations.cs +++ b/LIN.Identity.Tests/Validations.cs @@ -18,11 +18,11 @@ public void Validate_ShouldReturnFalse_WhenNameIsEmpty() }; // Act - var result = Account.Validate(account); + var (pass, message) = Account.Validate(account); // Assert - Assert.False(result.pass); - Assert.Equal("La cuenta debe de tener un nombre valido.", result.message); + Assert.False(pass); + Assert.Equal("La cuenta debe de tener un nombre valido.", message); } [Fact] @@ -90,11 +90,11 @@ public void Validate_ShouldReturnTrue_WhenAccountIsValid() }; // Act - var result = Account.Validate(account); + var (pass, message) = Account.Validate(account); // Assert - Assert.True(result.pass); - Assert.Equal("", result.message); + Assert.True(pass); + Assert.Equal("", message); } [Fact] From 8977d23add37f995048cc6bc8cd4040b1c5376cb Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 21 Sep 2024 11:50:01 -0500 Subject: [PATCH 099/178] Formato --- LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs | 2 +- LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs | 3 +-- LIN.Cloud.Identity/Data/Accounts.cs | 2 +- LIN.Cloud.Identity/Usings.cs | 2 +- LIN.Identity.Tests/Data/Accounts.cs | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 0c750fb..7edb411 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -177,7 +177,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasOne(t => t.Identity) .WithMany() - .HasForeignKey(t=>t.IdentityId); + .HasForeignKey(t => t.IdentityId); modelBuilder.Entity() diff --git a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs index d5bb398..3a50773 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs @@ -1,5 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Contexts; -using LIN.Cloud.Identity.Persistence.Queries.Interfaces; +using LIN.Cloud.Identity.Persistence.Queries.Interfaces; using LIN.Types.Cloud.Identity.Enumerations; using LIN.Types.Cloud.Identity.Models; diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index 2e0dba9..c723f91 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -214,7 +214,7 @@ public async Task> Search(string pattern, QueryAcc List accountModels = await Builders.Account.Search(pattern, filters, context).Take(10).ToListAsync(); // Si no existe el modelo - if (accountModels == null ||!accountModels.Any()) + if (accountModels == null || !accountModels.Any()) return new(Responses.NotRows); return new(Responses.Success, accountModels); diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 78a835a..a46939a 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -2,6 +2,7 @@ global using Http.ResponsesList; // Tipos locales. global using LIN.Cloud.Identity.Persistence.Contexts; +global using LIN.Cloud.Identity.Persistence.Extensions; global using LIN.Cloud.Identity.Services.Auth; global using LIN.Cloud.Identity.Services.Filters; global using LIN.Cloud.Identity.Services.Iam; @@ -23,4 +24,3 @@ global using System.Security.Claims; // Framework. global using System.Text; -global using LIN.Cloud.Identity.Persistence.Extensions; \ No newline at end of file diff --git a/LIN.Identity.Tests/Data/Accounts.cs b/LIN.Identity.Tests/Data/Accounts.cs index cf62451..cf2fb20 100644 --- a/LIN.Identity.Tests/Data/Accounts.cs +++ b/LIN.Identity.Tests/Data/Accounts.cs @@ -192,7 +192,7 @@ public async Task Search_ShouldReturnNotRowsResponse_WhenNoAccountsMatchPattern( // Assert Assert.Equal(Responses.NotRows, response.Response); } - + [Fact] public async Task FindAll_ShouldReturnSuccessResponse_WhenAccountsExist() { @@ -233,7 +233,7 @@ public async Task FindAll_ShouldReturnNotRowsResponse_WhenAccountsDoNotExist() // Assert Assert.Equal(Responses.NotRows, response.Response); } - + [Fact] public async Task FindAllByIdentities_ShouldReturnSuccessResponse_WhenAccountsExist() { From 53b9bc0f0747ff11c6b49805e40a5fb15d32d580 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 21 Sep 2024 11:54:50 -0500 Subject: [PATCH 100/178] mejoras --- LIN.Cloud.Identity/appsettings.Development.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LIN.Cloud.Identity/appsettings.Development.json b/LIN.Cloud.Identity/appsettings.Development.json index f47ccdf..4822bfc 100644 --- a/LIN.Cloud.Identity/appsettings.Development.json +++ b/LIN.Cloud.Identity/appsettings.Development.json @@ -7,10 +7,10 @@ }, "AllowedHosts": "*", "jwt": { - "key": "drfghjiwuytr567uijnbvgfder56y7usdfghjwertyuisdfghjkjhgfdswertyujhgiolkjhgx2fdertyui2345fgh" + "key": "" }, "ConnectionStrings": { - "local": "Data Source=(local);Initial Catalog=Identity;Integrated Security=True;TrustServerCertificate=True", - "cloud": "workstation id=authdatadb.mssql.somee.com;packet size=4096;user id=linauth_SQLLogin_3;pwd=yvvav2rhyd;data source=authdatadb.mssql.somee.com;persist security info=False;initial catalog=authdatadb;TrustServerCertificate=True; Encrypt=false;" + "local": "", + "cloud": "" } } \ No newline at end of file From 9bf7486d5da5be72fd414506ba60ecb5a0df7645 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 21 Sep 2024 21:57:01 -0500 Subject: [PATCH 101/178] Mejoras --- LIN.Cloud.Identity/Data/GroupMembers.cs | 2 +- LIN.Cloud.Identity/Usings.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LIN.Cloud.Identity/Data/GroupMembers.cs b/LIN.Cloud.Identity/Data/GroupMembers.cs index 6ce53e2..e2eb0bf 100644 --- a/LIN.Cloud.Identity/Data/GroupMembers.cs +++ b/LIN.Cloud.Identity/Data/GroupMembers.cs @@ -156,7 +156,7 @@ public async Task> Search(string pattern, int gro // Consulta. var members = await (from g in context.GroupMembers where g.GroupId == @group - && g.Identity.Unique.Contains(pattern, StringComparison.CurrentCultureIgnoreCase) + && g.Identity.Unique.Contains(pattern.ToLower()) select g.Identity).ToListAsync(); diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index a46939a..9933aea 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -23,4 +23,4 @@ global using System.IO; global using System.Security.Claims; // Framework. -global using System.Text; +global using System.Text; \ No newline at end of file From 304e0b433b56b89b52e97b8ae9c348faad067575 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 6 Oct 2024 14:14:25 -0500 Subject: [PATCH 102/178] Update dependencies by LIN Nuget Manager --- .../LIN.Cloud.Identity.Persistence.csproj | 6 +----- LIN.Cloud.Identity.sln | 12 ------------ LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 4 +--- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 3 +-- 4 files changed, 3 insertions(+), 22 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 49901ed..3eecd85 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -8,13 +8,9 @@ + - - - - - diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index d314c33..ef20ec9 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -5,12 +5,8 @@ VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity", "LIN.Cloud.Identity\LIN.Cloud.Identity.csproj", "{D7EF6814-1C64-44BC-BEAF-87835D44EEF8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types.Cloud.Identity", "..\..\Tipos\LIN.Types.Cloud.Identity\LIN.Types.Cloud.Identity.csproj", "{EA3955F9-EF0C-48E4-A428-02739EF322E1}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http", "..\..\Tipos\Http\Http.csproj", "{26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Types", "..\..\Tipos\LIN.Types\LIN.Types.csproj", "{E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity.Persistence", "LIN.Cloud.Identity.Persistence\LIN.Cloud.Identity.Persistence.csproj", "{6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Identity.Tests", "LIN.Identity.Tests\LIN.Identity.Tests.csproj", "{157863A4-209B-42A0-AE80-F6C403692480}" @@ -25,18 +21,10 @@ Global {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.Build.0 = Release|Any CPU - {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA3955F9-EF0C-48E4-A428-02739EF322E1}.Release|Any CPU.Build.0 = Release|Any CPU {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Debug|Any CPU.Build.0 = Debug|Any CPU {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Release|Any CPU.ActiveCfg = Release|Any CPU {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Release|Any CPU.Build.0 = Release|Any CPU - {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E3EC1B54-10F8-4395-AE5B-0F1A4270E4DD}.Release|Any CPU.Build.0 = Release|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 80ff2ac..6a7ea04 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -8,7 +8,7 @@ - + @@ -18,8 +18,6 @@ - - diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 6de0cf1..119e000 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -26,7 +26,6 @@ - From c7d07d6a0cd9fb75e991dfa2a58e23115d1320f9 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 6 Oct 2024 20:46:13 -0500 Subject: [PATCH 103/178] update --- .../Areas/Accounts/AccountController.cs | 1 - .../Areas/Authentication/AuthenticationController.cs | 1 - .../Services/Filters/IdentityTokenAttribute.cs | 4 ---- .../Services/Middlewares/QuotaMiddleware.cs | 4 ---- LIN.Cloud.Identity/Services/Utils/IdentityService.cs | 11 ++++++++++- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 1a9fde0..4f9d320 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Areas.Accounts; - [Route("[controller]")] public class AccountController(Data.Accounts accountData) : ControllerBase { diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index d776b80..9b048e8 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -2,7 +2,6 @@ namespace LIN.Cloud.Identity.Areas.Authentication; - [Route("[controller]")] public class AuthenticationController(IAuthentication authentication, Data.Accounts accountData) : ControllerBase { diff --git a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs index ac6943c..90b8f34 100644 --- a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs +++ b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs @@ -1,11 +1,8 @@ namespace LIN.Cloud.Identity.Services.Filters; - public class IdentityTokenAttribute : ActionFilterAttribute { - - /// /// Filtro del token. /// @@ -41,5 +38,4 @@ await httpContext.Response.WriteAsJsonAsync(new ResponseBase() } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs index 0a1e1c9..77f3000 100644 --- a/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs +++ b/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs @@ -1,10 +1,8 @@ namespace LIN.Cloud.Identity.Services.Middlewares; - public class QuotaMiddleware : IMiddleware { - /// /// Solicites máximas permitidas al momento. /// @@ -17,8 +15,6 @@ public class QuotaMiddleware : IMiddleware private static volatile int ActualQuote = 0; - - /// /// Invocación del Middleware. /// diff --git a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs index 8eb5200..e95123c 100644 --- a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs +++ b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs @@ -1,9 +1,12 @@ namespace LIN.Cloud.Identity.Services.Utils; - public class IdentityService(DataContext context) : IIdentityService { + /// + /// Obtener la identidades asociadas a una identidad base. + /// + /// Identidad base. public async Task> GetIdentities(int identity) { List result = [identity]; @@ -11,6 +14,12 @@ public async Task> GetIdentities(int identity) return result; } + + /// + /// Obtener la identidades asociadas a una identidad base. + /// + /// Identidad base. + /// Identidades encontradas. private async Task GetIdentities(int identity, List ids) { // Consulta. From 2f057ebcce6d4f50cbd5db914d02e5cf68aa9715 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Wed, 9 Oct 2024 21:01:12 -0500 Subject: [PATCH 104/178] Update --- LIN.Cloud.Identity.sln | 6 ------ LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index ef20ec9..cdf9230 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity", "LIN.Cloud.Identity\LIN.Cloud.Identity.csproj", "{D7EF6814-1C64-44BC-BEAF-87835D44EEF8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Http", "..\..\Tipos\Http\Http.csproj", "{26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity.Persistence", "LIN.Cloud.Identity.Persistence\LIN.Cloud.Identity.Persistence.csproj", "{6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Identity.Tests", "LIN.Identity.Tests\LIN.Identity.Tests.csproj", "{157863A4-209B-42A0-AE80-F6C403692480}" @@ -21,10 +19,6 @@ Global {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.Build.0 = Release|Any CPU - {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Debug|Any CPU.Build.0 = Debug|Any CPU - {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Release|Any CPU.ActiveCfg = Release|Any CPU - {26F7A8CA-FFE7-4F03-B311-2A1C4EC3A158}.Release|Any CPU.Build.0 = Release|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 6a7ea04..8ecf2b2 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -10,6 +10,7 @@ + @@ -17,7 +18,6 @@ - From 73842fd616b614c4388fcdfa31f882a24784a52d Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 10:23:51 -0500 Subject: [PATCH 105/178] =?UTF-8?q?Integraci=C3=B3n=20de=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contexts/DataContext.cs | 25 +++++- .../Extensions/PersistenceExtensions.cs | 7 +- .../LIN.Cloud.Identity.Persistence.csproj | 5 +- LIN.Cloud.Identity.sln | 7 ++ .../Areas/Accounts/AccountLogs.cs | 26 +++++++ LIN.Cloud.Identity/Data/AccountLogs.cs | 78 +++++++++++++++++++ LIN.Cloud.Identity/Data/Builders/Account.cs | 6 -- LIN.Cloud.Identity/Data/Organizations.cs | 1 - LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 3 +- .../Services/Auth/Authentication.cs | 21 ++++- .../Services/Extensions/LocalServices.cs | 1 + .../Services/Formats/Account.cs | 1 - .../Services/Realtime/PassKeyHub.cs | 10 ++- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 5 +- 14 files changed, 173 insertions(+), 23 deletions(-) create mode 100644 LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs create mode 100644 LIN.Cloud.Identity/Data/AccountLogs.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 7edb411..db43135 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -4,11 +4,9 @@ namespace LIN.Cloud.Identity.Persistence.Contexts; - public class DataContext(DbContextOptions options) : DbContext(options) { - /// /// Tabla de identidades. /// @@ -57,13 +55,18 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet Applications { get; set; } - /// /// Allow apps. /// public DbSet AllowApps { get; set; } + /// + /// Logs de accounts. + /// + public DbSet AccountLogs { get; set; } + + /// /// Generación del modelo de base de datos. /// @@ -215,6 +218,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } + // Modelo: Account Logs. + { + modelBuilder.Entity() + .HasOne(t => t.Application) + .WithMany() + .HasForeignKey(t => t.ApplicationId) + .OnDelete(DeleteBehavior.NoAction); + + modelBuilder.Entity() + .HasOne(t => t.Account) + .WithMany() + .HasForeignKey(t => t.AccountId) + .OnDelete(DeleteBehavior.NoAction); + } + // Nombres de las tablas. modelBuilder.Entity().ToTable("identities"); modelBuilder.Entity().ToTable("accounts"); @@ -225,6 +243,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("access_keys"); modelBuilder.Entity().ToTable("allow_apps"); modelBuilder.Entity().ToTable("applications"); + modelBuilder.Entity().ToTable("account_logs"); // Base. base.OnModelCreating(modelBuilder); diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index c103842..82b9f07 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -16,9 +16,13 @@ public static class PersistenceExtensions /// Services. public static IServiceCollection AddPersistence(this IServiceCollection services, IConfigurationManager configuration) { + string? connectionName = "cloud"; +#if LOCAL + connectionName = "local"; +#endif services.AddDbContextPool(options => { - options.UseSqlServer(configuration.GetConnectionString("cloud")); + options.UseSqlServer(configuration.GetConnectionString(connectionName)); }); services.AddScoped(); @@ -35,7 +39,6 @@ public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) { try { - var scope = app.ApplicationServices.CreateScope(); var context = scope.ServiceProvider.GetService(); context?.Database.EnsureCreated(); diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 3eecd85..b3fb739 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -4,13 +4,14 @@ net8.0 enable enable + Debug;Release;Local - + - + diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index cdf9230..7b7fefe 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -12,19 +12,26 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Local|Any CPU = Local|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Local|Any CPU.ActiveCfg = Local|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Local|Any CPU.Build.0 = Local|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.Build.0 = Release|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Local|Any CPU.ActiveCfg = Local|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Local|Any CPU.Build.0 = Local|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.ActiveCfg = Release|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.Build.0 = Release|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.Build.0 = Debug|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Local|Any CPU.ActiveCfg = Local|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Local|Any CPU.Build.0 = Local|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Release|Any CPU.ActiveCfg = Release|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs new file mode 100644 index 0000000..3e3cdf5 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs @@ -0,0 +1,26 @@ +namespace LIN.Cloud.Identity.Areas.Accounts; + +[Route("account/logs")] +public class AccountLogsController(Data.AccountLogs accountData) : ControllerBase +{ + + /// + /// Obtiene los logs de una cuenta. + /// + /// Token. + [HttpGet] + [IdentityToken] + public async Task> ReadAll([FromHeader] string token) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Obtiene el usuario + var response = await accountData.ReadAll(tokenInfo.AccountId); + + return response; + + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/AccountLogs.cs b/LIN.Cloud.Identity/Data/AccountLogs.cs new file mode 100644 index 0000000..0ad95a5 --- /dev/null +++ b/LIN.Cloud.Identity/Data/AccountLogs.cs @@ -0,0 +1,78 @@ +namespace LIN.Cloud.Identity.Data; + +public class AccountLogs (DataContext context) +{ + + /// + /// Crear nuevo log. + /// + /// Modelo. + public async Task Create(AccountLog log) + { + + // Pre. + log.Id = 0; + + try + { + log.Account = new() + { + Id = log.AccountId + }; + context.Attach(log.Account); + log.Application = null; + log.ApplicationId = null; + + // Guardar la cuenta. + await context.AccountLogs.AddAsync(log); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + LastID = log.Id + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + + } + + + + public async Task> ReadAll(int accountId) + { + + try + { + + var x = await context.AccountLogs + .Where(t => t.AccountId == accountId) + .OrderByDescending(t => t.Time) + .Take(20) + .ToListAsync(); + + return new() + { + Models = x, + Response = Responses.Success + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Builders/Account.cs b/LIN.Cloud.Identity/Data/Builders/Account.cs index ef0e551..b81f6cf 100644 --- a/LIN.Cloud.Identity/Data/Builders/Account.cs +++ b/LIN.Cloud.Identity/Data/Builders/Account.cs @@ -252,12 +252,6 @@ private static IQueryable BuildModel(IQueryable quer ) ? account.Name : "Usuario privado", - Creation = (filters.IsAdmin - || account.Visibility == Visibility.Visible - || filters.AccountContext == account.Id) - || (context.GroupMembers.FirstOrDefault(t => t.Group.Members.Any(t => t.IdentityId == filters.IdentityContext)) != null) - ? account.Creation - : default, Identity = new() { Id = account.Identity.Id, diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index c852245..28f1f14 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -34,7 +34,6 @@ public async Task Create(OrganizationModel modelo) var account = new AccountModel() { Id = 0, - Creation = DateTime.Now, Visibility = Visibility.Hidden, Name = "Admin", Password = $"pwd@{DateTime.Now.Year}", diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 8ecf2b2..0e9ba60 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -5,12 +5,13 @@ enable enable false + Debug;Release;Local - + diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 1ccb65d..5b57c14 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -1,9 +1,8 @@ namespace LIN.Cloud.Identity.Services.Auth; -public class Authentication(Data.Accounts accountData) : Interfaces.IAuthentication +public class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs) : Interfaces.IAuthentication { - /// /// Usuario. /// @@ -59,10 +58,10 @@ public async Task Start() // Validar contraseña. bool password = ValidatePassword(); - // Si la contraseña es invalida. if (!password) return Responses.InvalidPassword; + await SaveLog(); return Responses.Success; } @@ -113,13 +112,27 @@ private bool ValidatePassword() + /// + /// Guardar log. + /// + private async Task SaveLog() + { + await accountLogs.Create(new() + { + AccountId = Account!.Id, + AuthenticationMethod = AuthenticationMethods.Password, + Time = DateTime.Now, + }); + } + + + /// /// Obtener el token. /// public string GenerateToken() => JwtService.Generate(Account!, 0); - /// /// Obtener el token. /// diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 73877b4..94baa26 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -22,6 +22,7 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Iam. services.AddScoped(); diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 249385a..005bfd8 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -52,7 +52,6 @@ public static AccountModel Process(AccountModel baseAccount) { Id = 0, IdentityService = IdentityService.LIN, - Creation = DateTime.Now, Name = baseAccount.Name.Trim(), Profile = baseAccount.Profile, Password = Global.Utilities.Cryptography.Encrypt(baseAccount.Password), diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 2e6b414..33f2db5 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -2,7 +2,7 @@ namespace LIN.Cloud.Identity.Services.Realtime; -public partial class PassKeyHub(Data.PassKeys passKeysData) : Hub +public partial class PassKeyHub(Data.PassKeys passKeysData, Data.AccountLogs accountLogs) : Hub { /// @@ -216,6 +216,14 @@ public async Task ReceiveRequest(PassKeyModel modelo) Key = "" }; + /// + await accountLogs.Create(new() + { + AccountId = info.AccountId, + AuthenticationMethod = AuthenticationMethods.Authenticator, + Time = DateTime.Now, + }); + // Respuesta al cliente await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync(ResponseChannel, pass); diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 119e000..16735d4 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -7,6 +7,7 @@ false true + Debug;Release;Local @@ -14,8 +15,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + From a4faf32d4d3e93b791b5ea5515fb8aab2f1d5a98 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 10:35:39 -0500 Subject: [PATCH 106/178] clean --- LIN.Cloud.Identity/Data/Identities.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/LIN.Cloud.Identity/Data/Identities.cs b/LIN.Cloud.Identity/Data/Identities.cs index 6c3de35..45ac61c 100644 --- a/LIN.Cloud.Identity/Data/Identities.cs +++ b/LIN.Cloud.Identity/Data/Identities.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Data; - public class Identities(DataContext context) { From 28bccad013156e1bf0ffedcab3b59411fa7b2fbd Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 11:05:39 -0500 Subject: [PATCH 107/178] Mejoras en el passkey hub --- .../Services/Realtime/PassKeyHub.cs | 160 +++++------------- 1 file changed, 41 insertions(+), 119 deletions(-) diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 33f2db5..fda8532 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Models; - -namespace LIN.Cloud.Identity.Services.Realtime; +namespace LIN.Cloud.Identity.Services.Realtime; public partial class PassKeyHub(Data.PassKeys passKeysData, Data.AccountLogs accountLogs) : Hub { @@ -77,11 +75,11 @@ public async Task ReceiveRequest(PassKeyModel modelo) try { - // Validación del token recibido - var info = JwtService.Validate(modelo.Token); + // Obtener información del token. + JwtModel accountJwt = JwtService.Validate(modelo.Token); - // No es valido el token - if (!info.IsAuthenticated || modelo.Status != PassKeyStatus.Success) + // Validar el token. + if (!accountJwt.IsAuthenticated || modelo.Status != PassKeyStatus.Success) { // Modelo de falla PassKeyModel badPass = new() @@ -90,25 +88,21 @@ public async Task ReceiveRequest(PassKeyModel modelo) User = modelo.User }; - // comunica la respuesta + // Enviar respuesta. await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync(ResponseChannel, badPass); return; } - // Obtiene el attempt. - List attempts = Attempts[modelo.User.ToLower()].Where(A => A.HubKey == modelo.HubKey).ToList(); - - // Elemento - var attempt = attempts.Where(A => A.HubKey == modelo.HubKey).FirstOrDefault(); + // Obtiene los intentos. + var attempt = (from intento in Attempts[modelo.User.ToLower()].Where(A => A.HubKey == modelo.HubKey) + where intento.HubKey == modelo.HubKey + select intento).FirstOrDefault(); - // Validación del intento - if (attempt == null) + // No se encontró. + if (attempt is null) return; - // Eliminar el attempt de la lista - attempts.Remove(attempt); - - // Cambiar el estado del intento + // Cambiar el estado del intento. attempt.Status = modelo.Status; // Si el tiempo de expiración ya paso @@ -117,121 +111,49 @@ public async Task ReceiveRequest(PassKeyModel modelo) attempt.Status = PassKeyStatus.Expired; attempt.Token = string.Empty; } - - //// Validación de la organización - //if (orgID > 0) - //{ - // // Obtiene la organización - // var organization = await Data.Organizations.Organizations.Read(orgID); - - // // Si tiene lista blanca - // //if (organization.Model.HaveWhiteList) - // //{ - // // //// Validación de la app - // // //var applicationOnOrg = await Data.Organizations.Applications.AppOnOrg(attempt.Application.Key, orgID); - - // // //// Si la app no existe o no esta activa - // // //if (applicationOnOrg.Response != Responses.Success) - // // //{ - // // // // Modelo de falla - // // // PassKeyModel badPass = new() - // // // { - // // // Status = PassKeyStatus.BlockedByOrg, - // // // User = modelo.User - // // // }; - - // // // // comunica la respuesta - // // // await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", badPass); - // // // return; - - // // //} - // //} - - - //} - - //// Aplicación - //var app = await Data.Applications.Read(attempt.Application.Key); - - //// Si la app no existe - //if (app.Response != Responses.Success) - //{ - // // Modelo de falla - // PassKeyModel badPass = new() - // { - // Status = PassKeyStatus.Failed, - // User = modelo.User - // }; - - // // comunica la respuesta - // await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync("#response", badPass); - // return; - //} - - //// Guarda el acceso. - //LoginLogModel loginLog = new() - //{ - // AccountID = userID, - // Application = new() - // { - // ID = app.Model.ID - // }, - // Date = DateTime.Now, - // Type = LoginTypes.Passkey, - // ID = 0 - //}; - - //_ = Data.Logins.Create(loginLog); - - - - _ = passKeysData.Create(new PassKeyDBModel - { - AccountId = info.AccountId, - Id = 0, - Time = DateTime.Now, - }); - - //// Nuevo token - var newToken = JwtService.Generate(new AccountModel() + else { - Id = info.AccountId, - Identity = new() + // Generar nuevo token. + string token = JwtService.Generate(new() { - Id = info.IdentityId, - Unique = info.Unique - }, - IdentityId = info.IdentityId - }, 0); - - // nuevo pass - var pass = new PassKeyModel() + Id = accountJwt.AccountId, + IdentityId = accountJwt.IdentityId, + Identity = new() + { + Id = accountJwt.IdentityId, + Unique = accountJwt.Unique + }, + }, 0); + + attempt.Token = token; + } + + // Respuesta passkey. + var responsePasskey = new PassKeyModel() { Expiración = modelo.Expiración, - Status = modelo.Status, - User = modelo.User, - Token = newToken, - Hora = modelo.Hora, - HubKey = "", - Key = "" + Status = attempt.Status, + User = attempt.User, + Token = attempt.Token, + Hora = DateTime.Now, + HubKey = string.Empty, + Key = string.Empty }; - /// + // Crear log. await accountLogs.Create(new() { - AccountId = info.AccountId, + AccountId = accountJwt.AccountId, AuthenticationMethod = AuthenticationMethods.Authenticator, Time = DateTime.Now, }); - // Respuesta al cliente - await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync(ResponseChannel, pass); + // Respuesta al cliente. + await Clients.Groups($"dbo.{modelo.HubKey}").SendAsync(ResponseChannel, responsePasskey); } - catch + catch (Exception) { } - } - } \ No newline at end of file From 1a65dbc6ef826a5c2c587ff34a8e159e6fa84a90 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 13:55:32 -0500 Subject: [PATCH 108/178] Mejoras de seguridad --- .../Areas/Accounts/AccountController.cs | 29 +++++---- .../Areas/Accounts/AccountLogs.cs | 5 +- .../AuthenticationController.cs | 12 ++-- .../Areas/Directories/DirectoryController.cs | 13 ++-- .../Areas/Groups/GroupsController.cs | 11 ++-- .../Areas/Groups/GroupsMembersController.cs | 24 +++---- .../Areas/Organizations/MemberController.cs | 62 ++++++++++++++++++- .../Organizations/OrganizationController.cs | 1 - LIN.Cloud.Identity/Data/DirectoryMembers.cs | 26 ++------ LIN.Cloud.Identity/Data/GroupMembers.cs | 2 +- LIN.Cloud.Identity/Data/Organizations.cs | 36 +++++++++++ LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 14 +++++ 12 files changed, 171 insertions(+), 64 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 4f9d320..05a1401 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -5,9 +5,10 @@ public class AccountController(Data.Accounts accountData) : ControllerBase { /// - /// Crear una cuenta LIN. + /// Crear cuenta de enlace LIN. /// /// Modelo de la cuenta. + /// Retorna el Id asignado a la cuenta. [HttpPost] public async Task Create([FromBody] AccountModel? modelo) { @@ -64,6 +65,7 @@ public async Task Create([FromBody] AccountModel? modelo) /// /// Id de la cuenta. /// Token de acceso. + /// Retorna el modelo de la cuenta. [HttpGet("read/id")] [IdentityToken] public async Task> Read([FromQuery] int id, [FromHeader] string token) @@ -104,8 +106,9 @@ public async Task> Read([FromQuery] int id, [F /// /// Obtener una cuenta. /// - /// Identidad única. + /// Unique de la identidad de la cuenta. /// Token de acceso. + /// Retorna el modelo de la cuenta. [HttpGet("read/user")] [IdentityToken] public async Task> Read([FromQuery] string user, [FromHeader] string token) @@ -145,10 +148,11 @@ public async Task> Read([FromQuery] string use /// - /// Obtener una cuenta según Id de identidad. + /// Obtener una cuenta. /// /// Id de la identidad. /// Token de acceso. + /// Retorna el modelo de la cuenta. [HttpGet("read/identity")] [IdentityToken] public async Task> ReadByIdentity([FromQuery] int id, [FromHeader] string token) @@ -187,10 +191,11 @@ public async Task> ReadByIdentity([FromQuery] /// - /// Obtener una cuenta según Id de identidad. + /// Obtener una lista de cuentas según las id de las identidades. /// - /// Id de la identidad. + /// Id de las identidades /// Token de acceso. + /// Retorna la lista de cuentas. [HttpPost("read/identity")] [IdentityToken] public async Task> ReadByIdentity([FromBody] List ids, [FromHeader] string token) @@ -214,10 +219,11 @@ public async Task> ReadByIdentity([FromBody] L /// - /// Obtiene una lista de diez (10) usuarios que coincidan con un patron. + /// Buscar cuentas por medio de un patrón de búsqueda. /// - /// Patron - /// Token de acceso + /// Patron de búsqueda. + /// Token de acceso. + /// Retorna las cuentas encontradas. [HttpGet("search")] [IdentityToken] public async Task> Search([FromQuery] string pattern, [FromHeader] string token) @@ -248,10 +254,11 @@ public async Task> Search([FromQuery] string p /// - /// Obtiene una lista cuentas. + /// Obtener la lista de cuentas. /// - /// IDs de las cuentas - /// Token de acceso + /// Lista de ids de las cuentas. + /// Token de acceso. + /// Retorna una lista de las cuentas encontradas. [HttpPost("findAll")] [IdentityToken] public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs index 3e3cdf5..8b27210 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs @@ -5,9 +5,10 @@ public class AccountLogsController(Data.AccountLogs accountData) : ControllerBas { /// - /// Obtiene los logs de una cuenta. + /// Obtener los logs asociados a una cuenta. /// - /// Token. + /// Token de acceso. + /// Lista de logs. [HttpGet] [IdentityToken] public async Task> ReadAll([FromHeader] string token) diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 9b048e8..75fcbd9 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -7,11 +7,12 @@ public class AuthenticationController(IAuthentication authentication, Data.Accou { /// - /// Inicia una sesión de usuario. + /// Iniciar sesión en una cuenta de usuario. /// - /// Usuario único. - /// Contraseña del usuario. - /// Key de aplicación. + /// Unique. + /// Contraseña. + /// Id de la aplicación. + /// Retorna el modelo de la cuenta y el token de acceso. [HttpGet("login")] public async Task> Login([FromQuery] string user, [FromQuery] string password, [FromHeader] string application) { @@ -77,9 +78,10 @@ public async Task> Login([FromQuery] string us /// - /// Inicia una sesión de usuario por medio del token. + /// Refrescar sesión en una cuenta de usuario. /// /// Token de acceso. + /// Retorna el modelo de la cuenta. [HttpGet("LoginWithToken")] [IdentityToken] public async Task> LoginWithToken([FromHeader] string token) diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index 37f96ba..7a15a45 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -1,13 +1,17 @@ -namespace LIN.Cloud.Identity.Areas.Directories; +using LIN.Cloud.Identity.Data; + +namespace LIN.Cloud.Identity.Areas.Directories; [Route("[controller]")] public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : ControllerBase { /// - /// Obtener los directorios asociados. + /// Obtener los integrantes del directorio general de la organización. /// /// Token de acceso. + /// Id de la organización. + /// Retorna la lista de integrantes./returns> [HttpGet("read/all")] [IdentityToken] public async Task> ReadAll([FromHeader] string token, [FromHeader] int organization) @@ -41,10 +45,11 @@ public async Task> ReadAll([FromHeader] string /// - /// Obtener los integrantes asociados a un directorio. + /// Obtener los integrantes de un grupo. /// /// Token de acceso. - /// ID del directorio. + /// Id del directorio. + /// Retorna la lista de integrantes del directorio. [HttpGet("read/members")] [IdentityToken] public async Task> ReadMembers([FromHeader] string token, [FromQuery] int directory) diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index bdcd758..fccde44 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -56,10 +56,11 @@ public async Task Create([FromBody] GroupModel group, [FromH /// - /// Obtener los grupos asociados a una organización. + /// Obtener todos los grupos de una organización. /// /// Token de acceso. /// Id de la organización. + /// Retorna la lista de grupos. [HttpGet("all")] [IdentityToken] public async Task> ReadAll([FromHeader] string token, [FromHeader] int organization) @@ -99,10 +100,11 @@ public async Task> ReadAll([FromHeader] string t /// - /// Obtener un grupo según el Id. + /// Obtener un grupo. /// /// Token de acceso. /// Id del grupo. + /// Retorna el modelo del grupo. [HttpGet] [IdentityToken] public async Task> ReadOne([FromHeader] string token, [FromHeader] int id) @@ -153,10 +155,11 @@ public async Task> ReadOne([FromHeader] string t /// - /// Obtener un grupo según el Id. + /// Obtener un grupo. /// /// Token de acceso. - /// Id del grupo. + /// Id de la identidad del grupo. + /// Retorna el modelo del grupo. [HttpGet("identity")] [IdentityToken] public async Task> ReadIdentity([FromHeader] string token, [FromHeader] int id) diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index 42e6e2b..b2c0e64 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -5,10 +5,11 @@ public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembe { /// - /// Crear nuevo integrante. + /// Agregar un integrante a un grupo. /// /// Token de acceso. - /// Modelo. + /// Modelo del integrante. + /// Retorna el id del nuevo integrante. [HttpPost] [IdentityToken] public async Task Create([FromHeader] string token, [FromBody] GroupMember model) @@ -63,11 +64,12 @@ public async Task Create([FromHeader] string token, [FromBod /// - /// Crear nuevos integrantes en un grupo. + /// Agregar integrantes a un grupo. /// /// Token de acceso. /// Id del grupo. - /// Identidades. + /// Lista de las identidades. + /// Retorna la respuesta del proceso. [HttpPost("list")] [IdentityToken] public async Task Create([FromHeader] string token, [FromHeader] int group, [FromBody] List ids) @@ -106,18 +108,10 @@ public async Task Create([FromHeader] string token, [FromHea ids = ids.Distinct().ToList(); // Valida si el usuario pertenece a la organización. - var idIsIn = await directoryMembersData.IamIn(ids, orgId.Model); - - // Si no existe. - if (idIsIn.Response != Responses.Success) - return new() - { - Message = $"Errores encontrados al validar si las identidades pertenecen a la organización {orgId.Model}.", - Response = Responses.Unauthorized - }; + var (successIds, failureIds) = await directoryMembersData.IamIn(ids, orgId.Model); // Crear el usuario. - var response = await groupMembers.Create(ids.Select(id => new GroupMember + var response = await groupMembers.Create(successIds.Select(id => new GroupMember { Group = new() { @@ -129,6 +123,8 @@ public async Task Create([FromHeader] string token, [FromHea } })); + response.Message = $"Se agregaron {successIds.Count} integrantes y se omitieron {failureIds.Count} debido a que no pertenecen a esta organización."; + // Retorna el resultado return response; diff --git a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs index 34f72b2..12173fe 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs @@ -2,11 +2,69 @@ namespace LIN.Cloud.Identity.Areas.Organizations; - [Route("orgs/members")] -public class MemberController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, RolesIam rolesIam) : ControllerBase +public class MemberController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : ControllerBase { + /// + /// Agregar una identidad externa a la organización. + /// + /// Token de acceso. + /// Id de la organización. + /// Lista de ids a agregar. + /// Retorna el resultado del proceso. + [HttpPost("invite")] + [IdentityToken] + public async Task Create([FromHeader] string token, [FromQuery] int organization, [FromBody] List ids) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Confirmar el rol. + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + + // Iam. + bool iam = ValidateRoles.ValidateInviteMembers(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes autorización para invitar entidades externas en esta organización.", + Response = Responses.Unauthorized + }; + + // Solo elementos distintos. + ids = ids.Distinct().ToList(); + + // Valida si el usuario pertenece a la organización. + var (existentes, noUpdated) = await directoryMembersData.IamIn(ids, organization); + + var directoryId = await organizationsData.ReadDirectory(organization); + + // Crear el usuario. + var response = await groupMembers.Create(noUpdated.Select(id => new GroupMember + { + Group = new() + { + Id = directoryId.Model, + }, + Identity = new() + { + Id = id + }, + Type = GroupMemberTypes.Guest + })); + + response.Message = $"Se agregaron {noUpdated.Count} integrantes como invitados y se omitieron {existentes.Count} debido a que ya pertenecen a esta organización."; + + // Retorna el resultado + return response; + + } + + /// /// Crea un nuevo miembro en una organización. /// diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 58b4194..2ec73c3 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Areas.Organizations; - [Route("[controller]")] public class OrganizationsController(Data.Organizations organizationsData, Data.DirectoryMembers directoryMembersData) : ControllerBase { diff --git a/LIN.Cloud.Identity/Data/DirectoryMembers.cs b/LIN.Cloud.Identity/Data/DirectoryMembers.cs index c50eb66..e7f955f 100644 --- a/LIN.Cloud.Identity/Data/DirectoryMembers.cs +++ b/LIN.Cloud.Identity/Data/DirectoryMembers.cs @@ -118,7 +118,7 @@ on org.DirectoryId equals gm.GroupId /// Identidades /// Id de la organización /// Contexto - public async Task> IamIn(List ids, int organization) + public async Task<(List success, List failure)> IamIn(List ids, int organization) { try @@ -132,30 +132,16 @@ on org.DirectoryId equals gm.GroupId where ids.Contains(gm.IdentityId) select gm.IdentityId).ToListAsync(); + // Lista. + List success = [.. query]; + List failure = [.. ids.Except(success)]; - // Si la cuenta no existe. - if (query == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Models = query - }; - + return (success, failure); } catch (Exception) { - return new() - { - Response = Responses.NotRows - }; } - + return ([], []); } diff --git a/LIN.Cloud.Identity/Data/GroupMembers.cs b/LIN.Cloud.Identity/Data/GroupMembers.cs index e2eb0bf..5f4ae77 100644 --- a/LIN.Cloud.Identity/Data/GroupMembers.cs +++ b/LIN.Cloud.Identity/Data/GroupMembers.cs @@ -57,7 +57,7 @@ public async Task Create(IEnumerable modelos) try { await context.Database.ExecuteSqlRawAsync(""" - INSERT INTO [dbo].[GROUPS_MEMBERS] + INSERT INTO [dbo].[group_members] ([IdentityId] ,[GroupId] ,[Type]) diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index 28f1f14..a7e0858 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -243,4 +243,40 @@ public async Task> GetDomain(int id) } + + public async Task> ReadDirectory(int id) + { + + try + { + + var org = await (from g in context.Organizations + where g.Id == id + select g.DirectoryId).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (org <= 0) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = org + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 5ad52c7..8070d46 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -66,4 +66,18 @@ public static bool ValidateAlterMembers(IEnumerable roles) return sets.Any(); } + + public static bool ValidateInviteMembers(IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager + ]; + + var sets = availed.Intersect(roles); + + return sets.Any(); + + } } \ No newline at end of file From e1c7f3fcc52b202ad3a27f98f293f569cfb5ee92 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 14:33:37 -0500 Subject: [PATCH 109/178] Expulsar integrantes --- .../Areas/Organizations/MemberController.cs | 44 +++++++++++++++++ LIN.Cloud.Identity/Data/DirectoryMembers.cs | 47 +++++++++++++++++++ LIN.Cloud.Identity/Data/GroupMembers.cs | 2 +- LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 14 ++++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs index 12173fe..ffa46fe 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs @@ -189,4 +189,48 @@ public async Task>> ReadAll([FromH } + + /// + /// Agregar una identidad externa a la organización. + /// + /// Token de acceso. + /// Id de la organización. + /// Lista de ids a agregar. + /// Retorna el resultado del proceso. + [HttpPost("expulse")] + [IdentityToken] + public async Task Expulse([FromHeader] string token, [FromQuery] int organization, [FromBody] List ids) + { + + // Token. + JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); + + // Confirmar el rol. + var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + + // Iam. + bool iam = ValidateRoles.ValidateDelete(roles); + + // Si no tiene permisos. + if (!iam) + return new() + { + Message = "No tienes autorización para eliminar entidades externas en esta organización.", + Response = Responses.Unauthorized + }; + + // Solo elementos distintos. + ids = ids.Distinct().ToList(); + + // Valida si el usuario pertenece a la organización. + var (existentes, _) = await directoryMembersData.IamIn(ids, organization); + + + var response = await directoryMembersData.Expulse(existentes, organization); + + // Retorna el resultado + return response; + + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/DirectoryMembers.cs b/LIN.Cloud.Identity/Data/DirectoryMembers.cs index e7f955f..f988a22 100644 --- a/LIN.Cloud.Identity/Data/DirectoryMembers.cs +++ b/LIN.Cloud.Identity/Data/DirectoryMembers.cs @@ -308,4 +308,51 @@ on gm.IdentityId equals a.IdentityId } + /// + /// Expulsar identidades de la organización. + /// + /// Lista de identidades. + /// Id de la organización. + /// Respuesta del proceso. + public async Task Expulse(List ids, int organization) + { + + try + { + + // Desactivar identidades (Solo creadas dentro de la propia organización). + var baseQuery = (from member in context.GroupMembers + where ids.Contains(member.IdentityId) + where member.Group.OwnerId == organization + select member); + + // Desactivar identidades (Solo creadas dentro de la propia organización). + await baseQuery.Where(m => m.Type != GroupMemberTypes.Guest).Select(m => m.Identity).ExecuteUpdateAsync(t => t.SetProperty(t => t.Status, IdentityStatus.Disable)); + + // Eliminar accesos (Tanto propios de la organización como los externos). + await baseQuery.ExecuteDeleteAsync(); + + // Eliminar roles asociados. + await (from rol in context.IdentityRoles + where ids.Contains(rol.IdentityId) + && rol.OrganizationId == organization + select rol).ExecuteDeleteAsync(); + + // Success. + return new() + { + Response = Responses.Success + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/GroupMembers.cs b/LIN.Cloud.Identity/Data/GroupMembers.cs index 5f4ae77..8d10dea 100644 --- a/LIN.Cloud.Identity/Data/GroupMembers.cs +++ b/LIN.Cloud.Identity/Data/GroupMembers.cs @@ -65,7 +65,7 @@ INSERT INTO [dbo].[group_members] ({0} ,{1} ,{2}) - """, member.Identity.Id, member.Group.Id, (int)GroupMemberTypes.User); + """, member.Identity.Id, member.Group.Id, (int)member.Type); } catch (Exception) diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 8070d46..9492eb9 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -80,4 +80,18 @@ public static bool ValidateInviteMembers(IEnumerable roles) return sets.Any(); } + + public static bool ValidateDelete(IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager + ]; + + var sets = availed.Intersect(roles); + + return sets.Any(); + + } } \ No newline at end of file From 4e70e925de416843e2841d929898b01ed5317610 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 14:43:15 -0500 Subject: [PATCH 110/178] update --- .../{MemberController.cs => OrganizationMembersController.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename LIN.Cloud.Identity/Areas/Organizations/{MemberController.cs => OrganizationMembersController.cs} (95%) diff --git a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs similarity index 95% rename from LIN.Cloud.Identity/Areas/Organizations/MemberController.cs rename to LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index ffa46fe..494eed0 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/MemberController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -3,7 +3,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [Route("orgs/members")] -public class MemberController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : ControllerBase +public class OrganizationMembersController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : ControllerBase { /// @@ -15,7 +15,7 @@ public class MemberController(Data.Organizations organizationsData, Data.Account /// Retorna el resultado del proceso. [HttpPost("invite")] [IdentityToken] - public async Task Create([FromHeader] string token, [FromQuery] int organization, [FromBody] List ids) + public async Task AddExternalMembers([FromHeader] string token, [FromQuery] int organization, [FromBody] List ids) { // Token. From df262244e443e44aa8f2b7484c06650e37065016 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 19:59:37 -0500 Subject: [PATCH 111/178] Mejoras --- .../Areas/Authentication/AllowAppsController.cs | 1 - LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs | 1 - LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs | 4 +--- LIN.Cloud.Identity/Data/PassKeys.cs | 3 --- 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs index 4891182..40d383f 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Areas.Authentication; - [Route("applications/allow")] public class AllowAppsController : ControllerBase { diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index efb7147..f740177 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -2,7 +2,6 @@ namespace LIN.Cloud.Identity.Areas.Authentication; - [Route("[controller]")] public class IntentsController(Data.PassKeys passkeyData) : ControllerBase { diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index 7a15a45..8391f27 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Data; - -namespace LIN.Cloud.Identity.Areas.Directories; +namespace LIN.Cloud.Identity.Areas.Directories; [Route("[controller]")] public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : ControllerBase diff --git a/LIN.Cloud.Identity/Data/PassKeys.cs b/LIN.Cloud.Identity/Data/PassKeys.cs index 9ab6062..9ff5cf3 100644 --- a/LIN.Cloud.Identity/Data/PassKeys.cs +++ b/LIN.Cloud.Identity/Data/PassKeys.cs @@ -6,9 +6,6 @@ namespace LIN.Cloud.Identity.Data; public class PassKeys(DataContext context) { - - - public async Task Create(PassKeyDBModel modelo) { // Pre. From 695cc0f30f75d2b9d2a9e3b2db25001d2015d69f Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 20:06:03 -0500 Subject: [PATCH 112/178] mejoras --- LIN.Cloud.Identity/Services/Auth/Authentication.cs | 5 ----- LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs | 3 --- 2 files changed, 8 deletions(-) diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 5b57c14..e973d08 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -41,7 +41,6 @@ public void SetCredentials(string username, string password, string appCode) } - /// /// Iniciar el proceso. /// @@ -67,7 +66,6 @@ public async Task Start() } - /// /// Iniciar el proceso. /// @@ -90,7 +88,6 @@ private async Task GetAccount() } - /// /// Validar la contraseña. /// @@ -111,7 +108,6 @@ private bool ValidatePassword() } - /// /// Guardar log. /// @@ -126,7 +122,6 @@ await accountLogs.Create(new() } - /// /// Obtener el token. /// diff --git a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs index 535380c..6a59283 100644 --- a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs +++ b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs @@ -1,10 +1,8 @@ namespace LIN.Cloud.Identity.Services.Middlewares; - public class IPMiddleware : IMiddleware { - /// /// Invocación del Middleware. /// @@ -38,5 +36,4 @@ await context.Response.WriteAsJsonAsync(new ResponseBase } - } \ No newline at end of file From 3cada1d413feda3f686dad73fa3b1354a538385f Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 22:13:49 -0500 Subject: [PATCH 113/178] =?UTF-8?q?Refactorizaci=C3=B3n=20de=20autenticaci?= =?UTF-8?q?=C3=B3n=20y=20controladores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Eliminado constructor y método `OnStable` de `AccountFindable`. - `AccountController` ahora hereda de `AuthenticationController`. - Reemplazado `JwtModel` por `AuthenticationInformation` en `AccountController`. - Añadida clase `AuthenticationController` con propiedad `AuthenticationInformation`. - `AccountLogsController` ahora hereda de `AuthenticationController`. - `IdentityTokenAttribute` ahora agrega información de autenticación en `HttpContext.Items`. --- .../Queries/AccountFindable.cs | 3 -- .../Areas/Accounts/AccountController.cs | 47 +++++-------------- .../Areas/Accounts/AccountLogs.cs | 7 +-- .../Areas/AuthenticationController.cs | 11 +++++ .../Filters/IdentityTokenAttribute.cs | 1 + 5 files changed, 27 insertions(+), 42 deletions(-) create mode 100644 LIN.Cloud.Identity/Areas/AuthenticationController.cs diff --git a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs index 3a50773..e23a34a 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs @@ -4,12 +4,9 @@ namespace LIN.Cloud.Identity.Persistence.Queries; - public class AccountFindable(Contexts.DataContext context) : IFindable { - - /// /// Buscar en la cuentas estables. /// diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 05a1401..7d25e14 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Accounts; [Route("[controller]")] -public class AccountController(Data.Accounts accountData) : ControllerBase +public class AccountController(Data.Accounts accountData) : AuthenticationController { /// @@ -78,15 +78,12 @@ public async Task> Read([FromQuery] int id, [F Message = "Uno o varios parámetros son inválidos." }; - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene el usuario. var response = await accountData.Read(id, new() { - AccountContext = tokenInfo.AccountId, + AccountContext = AuthenticationInformation.AccountId, FindOn = FindOn.StableAccounts, - IdentityContext = tokenInfo.IdentityId, + IdentityContext = AuthenticationInformation.IdentityId, IsAdmin = false }); @@ -121,15 +118,12 @@ public async Task> Read([FromQuery] string use Message = "Uno o varios parámetros son inválidos." }; - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene el usuario. var response = await accountData.Read(user, new() { - AccountContext = tokenInfo.AccountId, + AccountContext = AuthenticationInformation.AccountId, FindOn = FindOn.StableAccounts, - IdentityContext = tokenInfo.IdentityId, + IdentityContext = AuthenticationInformation.IdentityId, IsAdmin = false }); @@ -165,15 +159,12 @@ public async Task> ReadByIdentity([FromQuery] Message = "Uno o varios parámetros son inválidos." }; - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene el usuario. var response = await accountData.ReadByIdentity(id, new() { - AccountContext = tokenInfo.AccountId, + AccountContext = AuthenticationInformation.AccountId, FindOn = FindOn.StableAccounts, - IdentityContext = tokenInfo.IdentityId, + IdentityContext = AuthenticationInformation.IdentityId, IsAdmin = false }); @@ -200,17 +191,13 @@ public async Task> ReadByIdentity([FromQuery] [IdentityToken] public async Task> ReadByIdentity([FromBody] List ids, [FromHeader] string token) { - - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene el usuario var response = await accountData.FindAllByIdentities(ids, new() { - AccountContext = tokenInfo.AccountId, + AccountContext = AuthenticationInformation.AccountId, FindOn = FindOn.StableAccounts, IsAdmin = false, - IdentityContext = tokenInfo.IdentityId, + IdentityContext = AuthenticationInformation.IdentityId, }); return response; @@ -236,17 +223,13 @@ public async Task> Search([FromQuery] string p Message = "Uno o varios parámetros son inválidos." }; - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - - // Obtiene el usuario var response = await accountData.Search(pattern, new() { - AccountContext = tokenInfo.AccountId, + AccountContext = AuthenticationInformation.AccountId, FindOn = FindOn.StableAccounts, IsAdmin = false, - IdentityContext = tokenInfo.IdentityId, + IdentityContext = AuthenticationInformation.IdentityId, }); return response; @@ -263,17 +246,13 @@ public async Task> Search([FromQuery] string p [IdentityToken] public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) { - - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene el usuario var response = await accountData.FindAll(ids, new() { - AccountContext = tokenInfo.AccountId, + AccountContext = AuthenticationInformation.AccountId, FindOn = FindOn.StableAccounts, IsAdmin = false, - IdentityContext = tokenInfo.IdentityId, + IdentityContext = AuthenticationInformation.IdentityId, }); return response; diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs index 8b27210..52a609f 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Accounts; [Route("account/logs")] -public class AccountLogsController(Data.AccountLogs accountData) : ControllerBase +public class AccountLogsController(Data.AccountLogs accountData) : AuthenticationController { /// @@ -14,11 +14,8 @@ public class AccountLogsController(Data.AccountLogs accountData) : ControllerBas public async Task> ReadAll([FromHeader] string token) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene el usuario - var response = await accountData.ReadAll(tokenInfo.AccountId); + var response = await accountData.ReadAll(AuthenticationInformation.AccountId); return response; diff --git a/LIN.Cloud.Identity/Areas/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/AuthenticationController.cs new file mode 100644 index 0000000..f0527d9 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/AuthenticationController.cs @@ -0,0 +1,11 @@ +namespace LIN.Cloud.Identity.Areas; + +public class AuthenticationController : ControllerBase +{ + + /// + /// Información de autenticación. + /// + public JwtModel AuthenticationInformation => base.HttpContext.Items["authentication"] as JwtModel ?? new(); + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs index 90b8f34..cb39dc8 100644 --- a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs +++ b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs @@ -34,6 +34,7 @@ await httpContext.Response.WriteAsJsonAsync(new ResponseBase() // Agrega la información del token. context.HttpContext.Items.Add(value.ToString(), tokenInfo); + context.HttpContext.Items.Add("authentication", tokenInfo); await base.OnActionExecutionAsync(context, next); } From bb15ead7290fd90a5106733d13383d2c877582a3 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 12 Oct 2024 22:34:38 -0500 Subject: [PATCH 114/178] Se agregaron mas test --- LIN.Cloud.Identity/Data/Groups.cs | 9 +- .../Services/Utils/IdentityService.cs | 1 - LIN.Identity.Tests/Data/Groups.cs | 100 ++++++++++++++++++ 3 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 LIN.Identity.Tests/Data/Groups.cs diff --git a/LIN.Cloud.Identity/Data/Groups.cs b/LIN.Cloud.Identity/Data/Groups.cs index 3d7fc6d..d0e2c7c 100644 --- a/LIN.Cloud.Identity/Data/Groups.cs +++ b/LIN.Cloud.Identity/Data/Groups.cs @@ -223,18 +223,11 @@ public async Task> ReadAll(int organization) OwnerId = g.OwnerId }).ToListAsync(); - // Si la cuenta no existe. - if (groups == null) - return new() - { - Response = Responses.NotRows - }; - // Success. return new() { Response = Responses.Success, - Models = groups + Models = groups ?? [] }; } diff --git a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs index e95123c..8dff2d5 100644 --- a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs +++ b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs @@ -34,7 +34,6 @@ private async Task GetIdentities(int identity, List ids) select member.Group.IdentityId).ToList(), }; - // Si hay elementos. if (query.Any()) { diff --git a/LIN.Identity.Tests/Data/Groups.cs b/LIN.Identity.Tests/Data/Groups.cs new file mode 100644 index 0000000..d6116fb --- /dev/null +++ b/LIN.Identity.Tests/Data/Groups.cs @@ -0,0 +1,100 @@ +using LIN.Cloud.Identity.Data; +using LIN.Cloud.Identity.Persistence.Contexts; +using LIN.Types.Cloud.Identity.Models; +using LIN.Types.Responses; +using Microsoft.EntityFrameworkCore; + +namespace LIN.Identity.Tests.Data; + +public class GroupsTests +{ + private static DataContext GetInMemoryDbContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: "TestDatabase") + .Options; + + return new DataContext(options); + } + + [Fact] + public async Task ReadByIdentity_ShouldReturnSuccessResponse_WhenGroupExists() + { + // Arrange + var context = GetInMemoryDbContext(); + var groups = new Groups(context); + var groupModel = new GroupModel + { + Name = "Test Group", + Identity = new IdentityModel { Unique = "unique", Id = 0 } + }; + + context.Groups.Add(groupModel); + await context.SaveChangesAsync(); + + // Act + var response = await groups.ReadByIdentity(1); + + // Assert + Assert.Equal(Responses.Success, response.Response); + Assert.NotNull(response.Model); + Assert.Equal("Test Group", response.Model.Name); + } + + [Fact] + public async Task ReadByIdentity_ShouldReturnNotRowsResponse_WhenGroupDoesNotExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var groups = new Groups(context); + + // Act + var response = await groups.ReadByIdentity(999); + + // Assert + Assert.Equal(Responses.NotRows, response.Response); + } + + [Fact] + public async Task ReadAll_ShouldReturnSuccessResponse_WhenGroupsExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var groups = new Groups(context); + var groupModel = new GroupModel + { + OwnerId = 1, + Name = "Test Group", + Identity = new() + { + Id = 1, + Unique = "unique" + } + }; + + context.Groups.Add(groupModel); + await context.SaveChangesAsync(); + + // Act + var response = await groups.ReadAll(1); + + // Assert + Assert.Equal(Responses.Success, response.Response); + Assert.NotEmpty(response.Models); + } + + [Fact] + public async Task ReadAll_ShouldReturnNotRowsResponse_WhenNoGroupsExist() + { + // Arrange + var context = GetInMemoryDbContext(); + var groups = new Groups(context); + + // Act + var response = await groups.ReadAll(999); + + // Assert + Assert.Empty(response.Models); + } + +} \ No newline at end of file From 68d274e75fb6ae2d6f46b83fd33bd0cc34fd53ec Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 13 Oct 2024 22:00:47 -0500 Subject: [PATCH 115/178] Mejoras generales --- .../LIN.Cloud.Identity.Persistence.csproj | 3 +- LIN.Cloud.Identity.sln | 21 ++++++++ .../OrganizationMembersController.cs | 1 - LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 3 +- LIN.Cloud.Identity/Program.cs | 5 -- .../Services/Middlewares/IPMiddleware.cs | 39 --------------- .../Middlewares/MiddlewareExtensions.cs | 38 -------------- .../Services/Middlewares/QuotaMiddleware.cs | 50 ------------------- LIN.Cloud.Identity/Usings.cs | 1 - LIN.Cloud.Identity/wwwroot/swagger/somee.css | 19 +++++++ LIN.Identity.Tests/Data/Groups.cs | 28 ----------- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 1 + 12 files changed, 45 insertions(+), 164 deletions(-) delete mode 100644 LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs delete mode 100644 LIN.Cloud.Identity/Services/Middlewares/MiddlewareExtensions.cs delete mode 100644 LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs create mode 100644 LIN.Cloud.Identity/wwwroot/swagger/somee.css diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index b3fb739..74f94ba 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -5,6 +5,7 @@ enable enable Debug;Release;Local + AnyCPU;x86 @@ -14,4 +15,4 @@ - + \ No newline at end of file diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index 7b7fefe..a43dc30 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -12,28 +12,49 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 Local|Any CPU = Local|Any CPU + Local|x86 = Local|x86 Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|x86.ActiveCfg = Debug|x86 + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|x86.Build.0 = Debug|x86 {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Local|Any CPU.ActiveCfg = Local|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Local|Any CPU.Build.0 = Local|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Local|x86.ActiveCfg = Local|x86 + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Local|x86.Build.0 = Local|x86 {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.Build.0 = Release|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|x86.ActiveCfg = Release|x86 + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|x86.Build.0 = Release|x86 {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|x86.ActiveCfg = Debug|x86 + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|x86.Build.0 = Debug|x86 {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Local|Any CPU.ActiveCfg = Local|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Local|Any CPU.Build.0 = Local|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Local|x86.ActiveCfg = Local|x86 + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Local|x86.Build.0 = Local|x86 {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.ActiveCfg = Release|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.Build.0 = Release|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|x86.ActiveCfg = Release|x86 + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|x86.Build.0 = Release|x86 {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.Build.0 = Debug|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Debug|x86.ActiveCfg = Debug|x86 + {157863A4-209B-42A0-AE80-F6C403692480}.Debug|x86.Build.0 = Debug|x86 {157863A4-209B-42A0-AE80-F6C403692480}.Local|Any CPU.ActiveCfg = Local|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Local|Any CPU.Build.0 = Local|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Local|x86.ActiveCfg = Local|x86 + {157863A4-209B-42A0-AE80-F6C403692480}.Local|x86.Build.0 = Local|x86 {157863A4-209B-42A0-AE80-F6C403692480}.Release|Any CPU.ActiveCfg = Release|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Release|Any CPU.Build.0 = Release|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Release|x86.ActiveCfg = Release|x86 + {157863A4-209B-42A0-AE80-F6C403692480}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index 494eed0..ee5ad79 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -225,7 +225,6 @@ public async Task Expulse([FromHeader] string token, [FromQuer // Valida si el usuario pertenece a la organización. var (existentes, _) = await directoryMembersData.IamIn(ids, organization); - var response = await directoryMembersData.Expulse(existentes, organization); // Retorna el resultado diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 0e9ba60..608a7e1 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -6,12 +6,13 @@ enable false Debug;Release;Local + AnyCPU;x86 - + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 0cd1baa..7885777 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -9,8 +9,6 @@ builder.Services.AddSignalR(); builder.Services.AddLINHttp(); -// Servicios propios. -builder.Services.AddIP(); builder.Services.AddLocalServices(); // Servicio de autenticación. @@ -19,9 +17,6 @@ var app = builder.Build(); -// Middlewares personalizados. -app.UseIP(); - app.UseLINHttp(); // Base de datos. diff --git a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs deleted file mode 100644 index 6a59283..0000000 --- a/LIN.Cloud.Identity/Services/Middlewares/IPMiddleware.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Middlewares; - -public class IPMiddleware : IMiddleware -{ - - /// - /// Invocación del Middleware. - /// - /// Contexto HTTP. - /// Pipeline - public async Task InvokeAsync(HttpContext context, RequestDelegate next) - { - // Obtener la IP. - var ip = context.Connection.RemoteIpAddress; - - // Validar la IP. - if (ip == null) - { - context.Response.StatusCode = 400; - await context.Response.WriteAsJsonAsync(new ResponseBase - { - Message = "La IP del cliente no pudo ser resuelta.", - Response = Responses.Unauthorized - }); - return; - } - - // Item de IP. - context.Items.Add("IP", ip); - - // Headers. - context.Response.Headers.Append("client-ip", ip.MapToIPv4().ToString()); - - // Pipeline. - await next(context); - - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Middlewares/MiddlewareExtensions.cs b/LIN.Cloud.Identity/Services/Middlewares/MiddlewareExtensions.cs deleted file mode 100644 index 5a896d0..0000000 --- a/LIN.Cloud.Identity/Services/Middlewares/MiddlewareExtensions.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Middlewares; - - -public static class MiddlewareExtensions -{ - - /// - /// Agregar servicio. - /// - public static IServiceCollection AddIP(this IServiceCollection builder) => builder.AddSingleton(); - - - /// - /// Activar el servicio. - /// - public static IApplicationBuilder UseIP(this IApplicationBuilder builder) => builder.UseMiddleware(); - - - /// - /// Agregar servicio. - /// - public static IServiceCollection AddQuota(this IServiceCollection builder) => builder.AddSingleton(); - - - /// - /// Activar el servicio. - /// - public static IApplicationBuilder UseQuota(this IApplicationBuilder builder, int max) - { - if (max <= 0) - max = 1; - - QuotaMiddleware.MaxQuote = max; - return builder.UseMiddleware(); - } - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs b/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs deleted file mode 100644 index 77f3000..0000000 --- a/LIN.Cloud.Identity/Services/Middlewares/QuotaMiddleware.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Middlewares; - -public class QuotaMiddleware : IMiddleware -{ - - /// - /// Solicites máximas permitidas al momento. - /// - public static int MaxQuote { get; set; } - - - /// - /// Solicitudes que se están procesando actualmente. - /// - private static volatile int ActualQuote = 0; - - - /// - /// Invocación del Middleware. - /// - /// Contexto HTTP. - /// Pipeline - public async Task InvokeAsync(HttpContext context, RequestDelegate next) - { - - // Comprobar el limite de cuota. - if (ActualQuote >= MaxQuote) - { - context.Response.StatusCode = 503; - await context.Response.WriteAsJsonAsync(new ResponseBase - { - Message = $"Actualmente estamos al limite de solicitudes.", - Response = Responses.UnavailableService - }); - return; - } - - // Incrementar. - ActualQuote++; - - // Ejecución del flujo (Pipeline). - await next(context); - - // Decrementar. - ActualQuote--; - - } - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 9933aea..eae5a05 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -6,7 +6,6 @@ global using LIN.Cloud.Identity.Services.Auth; global using LIN.Cloud.Identity.Services.Filters; global using LIN.Cloud.Identity.Services.Iam; -global using LIN.Cloud.Identity.Services.Middlewares; global using LIN.Cloud.Identity.Services.Models; global using LIN.Types.Cloud.Identity.Enumerations; global using LIN.Types.Cloud.Identity.Models; diff --git a/LIN.Cloud.Identity/wwwroot/swagger/somee.css b/LIN.Cloud.Identity/wwwroot/swagger/somee.css new file mode 100644 index 0000000..4596980 --- /dev/null +++ b/LIN.Cloud.Identity/wwwroot/swagger/somee.css @@ -0,0 +1,19 @@ +a[href*="http://somee.com"] { + visibility: hidden !important; + display: none !important; +} + +div[style="opacity: 0.9; z-index: 2147483647; position: fixed; left: 0px; bottom: 0px; height: 65px; right: 0px; display: block; width: 100%; background-color: #202020; margin: 0px; padding: 0px;"] { + visibility: hidden !important; + display: none !important; +} + +div[style="height: 65px;"] { + visibility: hidden !important; + display: none !important; +} + +div[onmouseover="S_ssac();"][onmouseout="D_ssac();"] { + visibility: hidden !important; + display: none !important; + } \ No newline at end of file diff --git a/LIN.Identity.Tests/Data/Groups.cs b/LIN.Identity.Tests/Data/Groups.cs index d6116fb..30eb583 100644 --- a/LIN.Identity.Tests/Data/Groups.cs +++ b/LIN.Identity.Tests/Data/Groups.cs @@ -55,34 +55,6 @@ public async Task ReadByIdentity_ShouldReturnNotRowsResponse_WhenGroupDoesNotExi Assert.Equal(Responses.NotRows, response.Response); } - [Fact] - public async Task ReadAll_ShouldReturnSuccessResponse_WhenGroupsExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var groups = new Groups(context); - var groupModel = new GroupModel - { - OwnerId = 1, - Name = "Test Group", - Identity = new() - { - Id = 1, - Unique = "unique" - } - }; - - context.Groups.Add(groupModel); - await context.SaveChangesAsync(); - - // Act - var response = await groups.ReadAll(1); - - // Assert - Assert.Equal(Responses.Success, response.Response); - Assert.NotEmpty(response.Models); - } - [Fact] public async Task ReadAll_ShouldReturnNotRowsResponse_WhenNoGroupsExist() { diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 16735d4..2f301bf 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -8,6 +8,7 @@ false true Debug;Release;Local + AnyCPU;x86 From b97149663a98b45c2f34f183304716c3325360b8 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 14 Oct 2024 01:41:44 -0500 Subject: [PATCH 116/178] Swagger token map. Policies, Create and validate --- .../Contexts/DataContext.cs | 23 ++++ .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../Areas/Accounts/AccountController.cs | 23 ++-- .../Areas/Accounts/AccountLogs.cs | 5 +- .../AuthenticationController.cs | 84 ++++++++++++-- .../Areas/Authentication/IntentsController.cs | 19 +-- .../Areas/AuthenticationBaseController.cs | 17 +++ .../Areas/AuthenticationController.cs | 11 -- .../Areas/Directories/DirectoryController.cs | 19 +-- .../Areas/Groups/GroupsController.cs | 35 ++---- .../Areas/Groups/GroupsMembersController.cs | 59 +++------- .../Areas/Organizations/IdentityController.cs | 26 ++--- .../Organizations/OrganizationController.cs | 16 +-- .../OrganizationMembersController.cs | 32 ++--- .../Areas/Policies/PoliciesController.cs | 79 +++++++++++++ LIN.Cloud.Identity/Data/AccountLogs.cs | 6 +- LIN.Cloud.Identity/Data/Policies.cs | 109 ++++++++++++++++++ LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 6 +- LIN.Cloud.Identity/Program.cs | 5 +- .../Properties/launchSettings.json | 5 +- .../Services/Auth/Authentication.cs | 18 ++- .../Auth/Interfaces/IAuthentication.cs | 10 +- .../Auth/Models/AuthenticationSettings.cs | 7 ++ .../Services/Extensions/LocalServices.cs | 1 + LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 1 - .../Services/Realtime/PassKeyHubActions.cs | 2 +- 26 files changed, 406 insertions(+), 214 deletions(-) create mode 100644 LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs delete mode 100644 LIN.Cloud.Identity/Areas/AuthenticationController.cs create mode 100644 LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs create mode 100644 LIN.Cloud.Identity/Data/Policies.cs create mode 100644 LIN.Cloud.Identity/Services/Auth/Models/AuthenticationSettings.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index db43135..58dfd40 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -67,12 +67,23 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet AccountLogs { get; set; } + /// + /// Políticas. + /// + public DbSet Policies { get; set; } + + /// /// Generación del modelo de base de datos. /// protected override void OnModelCreating(ModelBuilder modelBuilder) { + // Modelo: Políticas. + { + + } + // Modelo: Identity. { modelBuilder.Entity() @@ -233,6 +244,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.NoAction); } + + modelBuilder.Entity() + .HasOne(t => t.OwnerIdentity) + .WithMany() + .HasForeignKey(t => t.OwnerIdentityId) + .OnDelete(DeleteBehavior.NoAction); + + modelBuilder.Entity() + .HasMany(t => t.ApplyFor) + .WithMany(); + // Nombres de las tablas. modelBuilder.Entity().ToTable("identities"); modelBuilder.Entity().ToTable("accounts"); @@ -244,6 +266,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("allow_apps"); modelBuilder.Entity().ToTable("applications"); modelBuilder.Entity().ToTable("account_logs"); + modelBuilder.Entity().ToTable("policies"); // Base. base.OnModelCreating(modelBuilder); diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 74f94ba..b4f7052 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -10,7 +10,7 @@ - + diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 7d25e14..85a2992 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Accounts; [Route("[controller]")] -public class AccountController(Data.Accounts accountData) : AuthenticationController +public class AccountController(Data.Accounts accountData) : AuthenticationBaseController { /// @@ -64,11 +64,10 @@ public async Task Create([FromBody] AccountModel? modelo) /// Obtener una cuenta. /// /// Id de la cuenta. - /// Token de acceso. /// Retorna el modelo de la cuenta. [HttpGet("read/id")] [IdentityToken] - public async Task> Read([FromQuery] int id, [FromHeader] string token) + public async Task> Read([FromQuery] int id) { // Id es invalido. @@ -104,15 +103,14 @@ public async Task> Read([FromQuery] int id, [F /// Obtener una cuenta. /// /// Unique de la identidad de la cuenta. - /// Token de acceso. /// Retorna el modelo de la cuenta. [HttpGet("read/user")] [IdentityToken] - public async Task> Read([FromQuery] string user, [FromHeader] string token) + public async Task> Read([FromQuery] string user) { // Usuario es invalido. - if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(token)) + if (string.IsNullOrWhiteSpace(user)) return new(Responses.InvalidParam) { Message = "Uno o varios parámetros son inválidos." @@ -145,11 +143,10 @@ public async Task> Read([FromQuery] string use /// Obtener una cuenta. /// /// Id de la identidad. - /// Token de acceso. /// Retorna el modelo de la cuenta. [HttpGet("read/identity")] [IdentityToken] - public async Task> ReadByIdentity([FromQuery] int id, [FromHeader] string token) + public async Task> ReadByIdentity([FromQuery] int id) { // Id es invalido. @@ -185,11 +182,10 @@ public async Task> ReadByIdentity([FromQuery] /// Obtener una lista de cuentas según las id de las identidades. /// /// Id de las identidades - /// Token de acceso. /// Retorna la lista de cuentas. [HttpPost("read/identity")] [IdentityToken] - public async Task> ReadByIdentity([FromBody] List ids, [FromHeader] string token) + public async Task> ReadByIdentity([FromBody] List ids) { // Obtiene el usuario var response = await accountData.FindAllByIdentities(ids, new() @@ -201,7 +197,6 @@ public async Task> ReadByIdentity([FromBody] L }); return response; - } @@ -209,11 +204,10 @@ public async Task> ReadByIdentity([FromBody] L /// Buscar cuentas por medio de un patrón de búsqueda. /// /// Patron de búsqueda. - /// Token de acceso. /// Retorna las cuentas encontradas. [HttpGet("search")] [IdentityToken] - public async Task> Search([FromQuery] string pattern, [FromHeader] string token) + public async Task> Search([FromQuery] string pattern) { // Comprobación @@ -240,11 +234,10 @@ public async Task> Search([FromQuery] string p /// Obtener la lista de cuentas. /// /// Lista de ids de las cuentas. - /// Token de acceso. /// Retorna una lista de las cuentas encontradas. [HttpPost("findAll")] [IdentityToken] - public async Task> ReadAll([FromBody] List ids, [FromHeader] string token) + public async Task> ReadAll([FromBody] List ids) { // Obtiene el usuario var response = await accountData.FindAll(ids, new() diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs index 52a609f..575178b 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs @@ -1,17 +1,16 @@ namespace LIN.Cloud.Identity.Areas.Accounts; [Route("account/logs")] -public class AccountLogsController(Data.AccountLogs accountData) : AuthenticationController +public class AccountLogsController(Data.AccountLogs accountData) : AuthenticationBaseController { /// /// Obtener los logs asociados a una cuenta. /// - /// Token de acceso. /// Lista de logs. [HttpGet] [IdentityToken] - public async Task> ReadAll([FromHeader] string token) + public async Task> ReadAll() { // Obtiene el usuario diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 75fcbd9..252754a 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -3,7 +3,7 @@ namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class AuthenticationController(IAuthentication authentication, Data.Accounts accountData) : ControllerBase +public class AuthenticationController(IAuthentication authentication, Data.Accounts accountData, Data.Policies policyData) : AuthenticationBaseController { /// @@ -80,18 +80,13 @@ public async Task> Login([FromQuery] string us /// /// Refrescar sesión en una cuenta de usuario. /// - /// Token de acceso. /// Retorna el modelo de la cuenta. [HttpGet("LoginWithToken")] [IdentityToken] - public async Task> LoginWithToken([FromHeader] string token) + public async Task> LoginWithToken() { - - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene el usuario. - var response = await accountData.Read(tokenInfo.AccountId, new QueryAccountFilter() + var response = await accountData.Read(AuthenticationInformation.AccountId, new QueryAccountFilter() { IsAdmin = true, FindOn = FindOn.StableAccounts @@ -100,9 +95,80 @@ public async Task> LoginWithToken([FromHeader] if (response.Response != Responses.Success) return new(response.Response); - response.Token = token; + response.Token = Token; return response; } + + /// + /// Iniciar sesión y validar una política. + /// + /// Usuario. + /// Contraseña. + /// Id de la política. + [HttpGet("validate/policy")] + public async Task ValidatePolicy([FromQuery] string user, [FromQuery] string password, [FromHeader] string policy) + { + + // Validación de parámetros. + if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(policy)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son invalido." + }; + + // Establecer credenciales. + authentication.SetCredentials(user, password, string.Empty); + + // Respuesta. + var response = await authentication.Start(new() + { + Log = false + }); + + // Validación al obtener el usuario + switch (response) + { + // Correcto + case Responses.Success: + break; + + // No existe esta cuenta. + case Responses.NotExistAccount: + return new() + { + Response = Responses.Unauthorized, + Message = "No existe esta cuenta." + }; + + // Contraseña invalida. + case Responses.InvalidPassword: + return new() + { + Response = Responses.Unauthorized, + Message = "Contraseña incorrecta." + }; + + // Incorrecto + default: + return new() + { + Response = Responses.Unauthorized, + Message = "Hubo un error grave." + }; + } + + // Validar política. + var isAllow = await policyData.HasFor(authentication.GetData().IdentityId, policy); + + // Respuesta. + var http = new ResponseBase + { + Response = isAllow.Response + }; + + return http; + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index f740177..78d1e41 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -3,26 +3,21 @@ namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class IntentsController(Data.PassKeys passkeyData) : ControllerBase +public class IntentsController(Data.PassKeys passkeyData) : AuthenticationBaseController { /// /// Obtiene la lista de intentos de llaves de paso están activos. /// - /// Token de acceso. [HttpGet] [IdentityToken] - public HttpReadAllResponse GetAll([FromHeader] string token) + public HttpReadAllResponse GetAll() { try { - - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Cuenta var account = (from a in PassKeyHub.Attempts - where a.Key.Equals(tokenInfo.Unique, StringComparison.CurrentCultureIgnoreCase) + where a.Key.Equals(AuthenticationInformation.Unique, StringComparison.CurrentCultureIgnoreCase) select a).FirstOrDefault().Value ?? []; // Hora actual @@ -50,17 +45,13 @@ where I.Expiración > timeNow /// /// Obtiene la lista de intentos de llaves de paso están activos. /// - /// Token de acceso. [HttpGet("count")] [IdentityToken] - public async Task> Count([FromHeader] string token) + public async Task> Count() { try { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - - var x = await passkeyData.Count(tokenInfo.AccountId); + var x = await passkeyData.Count(AuthenticationInformation.AccountId); // Retorna return new(Responses.Success, x.Model); diff --git a/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs b/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs new file mode 100644 index 0000000..57b1fff --- /dev/null +++ b/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs @@ -0,0 +1,17 @@ +namespace LIN.Cloud.Identity.Areas; + +public class AuthenticationBaseController : ControllerBase +{ + + /// + /// Información de autenticación. + /// + public JwtModel AuthenticationInformation => base.HttpContext.Items["authentication"] as JwtModel ?? new(); + + + /// + /// Obtener el token desde el header. + /// + public string Token => base.HttpContext.Request.Headers["token"].ToString() ?? string.Empty; + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/AuthenticationController.cs deleted file mode 100644 index f0527d9..0000000 --- a/LIN.Cloud.Identity/Areas/AuthenticationController.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LIN.Cloud.Identity.Areas; - -public class AuthenticationController : ControllerBase -{ - - /// - /// Información de autenticación. - /// - public JwtModel AuthenticationInformation => base.HttpContext.Items["authentication"] as JwtModel ?? new(); - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index 8391f27..3e16c8d 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -1,23 +1,18 @@ namespace LIN.Cloud.Identity.Areas.Directories; [Route("[controller]")] -public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : ControllerBase +public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : AuthenticationBaseController { /// /// Obtener los integrantes del directorio general de la organización. /// - /// Token de acceso. /// Id de la organización. /// Retorna la lista de integrantes./returns> [HttpGet("read/all")] [IdentityToken] - public async Task> ReadAll([FromHeader] string token, [FromHeader] int organization) + public async Task> ReadAll([FromHeader] int organization) { - - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Validar organización. if (organization <= 0) return new() @@ -27,7 +22,7 @@ public async Task> ReadAll([FromHeader] string }; // Obtiene el usuario. - var response = await directoryMembersData.Read(tokenInfo.IdentityId, organization); + var response = await directoryMembersData.Read(AuthenticationInformation.IdentityId, organization); // Si es erróneo if (response.Response != Responses.Success) @@ -45,17 +40,13 @@ public async Task> ReadAll([FromHeader] string /// /// Obtener los integrantes de un grupo. /// - /// Token de acceso. /// Id del directorio. /// Retorna la lista de integrantes del directorio. [HttpGet("read/members")] [IdentityToken] - public async Task> ReadMembers([FromHeader] string token, [FromQuery] int directory) + public async Task> ReadMembers([FromQuery] int directory) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupsData.GetOwner(directory); @@ -69,7 +60,7 @@ public async Task> ReadMembers([FromHeader] str // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index fccde44..79dc540 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -1,24 +1,20 @@ namespace LIN.Cloud.Identity.Areas.Groups; [Route("[controller]")] -public class GroupsController(Data.Groups groupData, RolesIam rolesIam) : ControllerBase +public class GroupsController(Data.Groups groupData, RolesIam rolesIam) : AuthenticationBaseController { /// /// Crear nuevo grupo. /// /// Modelo del grupo. - /// Token de acceso. [HttpPost] [IdentityToken] - public async Task Create([FromBody] GroupModel group, [FromHeader] string token) + public async Task Create([FromBody] GroupModel group) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, group.OwnerId ?? 0); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, group.OwnerId ?? 0); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -58,19 +54,14 @@ public async Task Create([FromBody] GroupModel group, [FromH /// /// Obtener todos los grupos de una organización. /// - /// Token de acceso. /// Id de la organización. /// Retorna la lista de grupos. [HttpGet("all")] [IdentityToken] - public async Task> ReadAll([FromHeader] string token, [FromHeader] int organization) + public async Task> ReadAll([FromHeader] int organization) { - - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -102,17 +93,13 @@ public async Task> ReadAll([FromHeader] string t /// /// Obtener un grupo. /// - /// Token de acceso. /// Id del grupo. /// Retorna el modelo del grupo. [HttpGet] [IdentityToken] - public async Task> ReadOne([FromHeader] string token, [FromHeader] int id) + public async Task> ReadOne([FromHeader] int id) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupData.GetOwner(id); @@ -125,7 +112,7 @@ public async Task> ReadOne([FromHeader] string t }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -157,17 +144,13 @@ public async Task> ReadOne([FromHeader] string t /// /// Obtener un grupo. /// - /// Token de acceso. /// Id de la identidad del grupo. /// Retorna el modelo del grupo. [HttpGet("identity")] [IdentityToken] - public async Task> ReadIdentity([FromHeader] string token, [FromHeader] int id) + public async Task> ReadIdentity([FromHeader] int id) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupData.GetOwnerByIdentity(id); @@ -180,7 +163,7 @@ public async Task> ReadIdentity([FromHeader] str }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index b2c0e64..9dcfec8 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -1,23 +1,19 @@ namespace LIN.Cloud.Identity.Areas.Groups; [Route("Groups/members")] -public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : Controller +public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : AuthenticationBaseController { /// /// Agregar un integrante a un grupo. /// - /// Token de acceso. /// Modelo del integrante. /// Retorna el id del nuevo integrante. [HttpPost] [IdentityToken] - public async Task Create([FromHeader] string token, [FromBody] GroupMember model) + public async Task Create([FromBody] GroupMember model) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupsData.GetOwner(model.GroupId); @@ -30,7 +26,7 @@ public async Task Create([FromHeader] string token, [FromBod }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -66,18 +62,14 @@ public async Task Create([FromHeader] string token, [FromBod /// /// Agregar integrantes a un grupo. /// - /// Token de acceso. /// Id del grupo. /// Lista de las identidades. /// Retorna la respuesta del proceso. [HttpPost("list")] [IdentityToken] - public async Task Create([FromHeader] string token, [FromHeader] int group, [FromBody] List ids) + public async Task Create([FromHeader] int group, [FromBody] List ids) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupsData.GetOwner(group); @@ -91,7 +83,7 @@ public async Task Create([FromHeader] string token, [FromHea // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -134,16 +126,12 @@ public async Task Create([FromHeader] string token, [FromHea /// /// Obtener los integrantes asociados a un grupo. /// - /// Token de acceso. /// ID del grupo. [HttpGet("read/all")] [IdentityToken] - public async Task> ReadMembers([FromHeader] string token, [FromQuery] int group) + public async Task> ReadMembers([FromQuery] int group) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupsData.GetOwner(group); @@ -156,7 +144,7 @@ public async Task> ReadMembers([FromHeader] str }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -189,17 +177,12 @@ public async Task> ReadMembers([FromHeader] str /// /// Buscar en los integrantes de un grupo. /// - /// Token de acceso. /// Grupo. /// Patron de búsqueda. [HttpGet("search")] [IdentityToken] - public async Task> Search([FromHeader] string token, [FromHeader] int group, [FromQuery] string pattern) + public async Task> Search([FromHeader] int group, [FromQuery] string pattern) { - - // Información del token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupsData.GetOwner(group); @@ -212,7 +195,7 @@ public async Task> Search([FromHeader] string }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -246,17 +229,13 @@ public async Task> Search([FromHeader] string /// /// Buscar en los grupos de un grupo. /// - /// Token de acceso. /// Grupo. /// Patron de búsqueda. [HttpGet("search/groups")] [IdentityToken] - public async Task> SearchOnGroups([FromHeader] string token, [FromHeader] int group, [FromQuery] string pattern) + public async Task> SearchOnGroups([FromHeader] int group, [FromQuery] string pattern) { - // Información del token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupsData.GetOwner(group); @@ -270,7 +249,7 @@ public async Task> SearchOnGroups([FromHeader // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -303,16 +282,12 @@ public async Task> SearchOnGroups([FromHeader /// /// Eliminar un integrante /// - /// Token de acceso. /// ID del grupo. [HttpDelete("remove")] [IdentityToken] - public async Task DeleteMembers([FromHeader] string token, [FromQuery] int identity, [FromQuery] int group) + public async Task DeleteMembers([FromQuery] int identity, [FromQuery] int group) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtener la organización. var orgId = await groupsData.GetOwner(group); @@ -325,7 +300,7 @@ public async Task DeleteMembers([FromHeader] string token, [Fr }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, orgId.Model); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -357,18 +332,14 @@ public async Task DeleteMembers([FromHeader] string token, [Fr /// /// Obtener los grupos a los que una identidad pertenece. /// - /// Token de acceso. /// ID del grupo. [HttpGet("read/on/all")] [IdentityToken] - public async Task> OnMembers([FromHeader] string token, [FromQuery] int organization, [FromQuery] int identity) + public async Task> OnMembers([FromQuery] int organization, [FromQuery] int identity) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 8750e91..8a8eeb5 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -1,24 +1,20 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [Route("[controller]")] -public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData, RolesIam rolesIam) : ControllerBase +public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData, RolesIam rolesIam) : AuthenticationBaseController { /// /// Crear nuevo grupo. /// /// Modelo del grupo. - /// Token de acceso. [HttpPost] [IdentityToken] - public async Task Create([FromBody] IdentityRolesModel rolModel, [FromHeader] string token) + public async Task Create([FromBody] IdentityRolesModel rolModel) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, rolModel.OrganizationId); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, rolModel.OrganizationId); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -65,19 +61,15 @@ public async Task Create([FromBody] IdentityRolesModel rolMode /// /// Obtener los roles asociados a una identidad. /// - /// Token de acceso. /// Identidad /// Id de la organización. [HttpGet("roles/all")] [IdentityToken] - public async Task> ReadAll([FromHeader] string token, [FromHeader] int identity, [FromHeader] int organization) + public async Task> ReadAll([FromHeader] int identity, [FromHeader] int organization) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -118,20 +110,16 @@ public async Task> ReadAll([FromHeader] /// /// Eliminar los roles asociados a una identidad. /// - /// Token de acceso. /// Identidad /// Id de la organización. /// Rol. [HttpDelete("roles")] [IdentityToken] - public async Task ReadAll([FromHeader] string token, [FromHeader] int identity, [FromHeader] int organization, [FromHeader] Roles rol) + public async Task ReadAll([FromHeader] int identity, [FromHeader] int organization, [FromHeader] Roles rol) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 2ec73c3..57946bd 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [Route("[controller]")] -public class OrganizationsController(Data.Organizations organizationsData, Data.DirectoryMembers directoryMembersData) : ControllerBase +public class OrganizationsController(Data.Organizations organizationsData, Data.DirectoryMembers directoryMembersData) : AuthenticationBaseController { /// @@ -58,16 +58,13 @@ public async Task Create([FromBody] OrganizationModel modelo /// Token de acceso [HttpGet("read/id")] [IdentityToken] - public async Task> ReadOneByID([FromQuery] int id, [FromHeader] string token) + public async Task> ReadOneByID([FromQuery] int id) { // Parámetros if (id <= 0) return new(Responses.InvalidParam); - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene la organización var response = await organizationsData.Read(id); @@ -83,7 +80,7 @@ public async Task> ReadOneByID([FromQuery if (!response.Model.IsPublic) { - var iamIn = await directoryMembersData.IamIn(tokenInfo.IdentityId, response.Model.Id); + var iamIn = await directoryMembersData.IamIn(AuthenticationInformation.IdentityId, response.Model.Id); if (iamIn.Response != Responses.Success) return new ReadOneResponse() @@ -114,14 +111,11 @@ public async Task> ReadOneByID([FromQuery /// Token de acceso [HttpGet("read/all")] [IdentityToken] - public async Task> ReadAll([FromHeader] string token) + public async Task> ReadAll() { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Obtiene la organización - var response = await organizationsData.ReadAll(tokenInfo.IdentityId); + var response = await organizationsData.ReadAll(AuthenticationInformation.IdentityId); return response; diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index ee5ad79..a9d7f3e 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -3,26 +3,22 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [Route("orgs/members")] -public class OrganizationMembersController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : ControllerBase +public class OrganizationMembersController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : AuthenticationBaseController { /// /// Agregar una identidad externa a la organización. /// - /// Token de acceso. /// Id de la organización. /// Lista de ids a agregar. /// Retorna el resultado del proceso. [HttpPost("invite")] [IdentityToken] - public async Task AddExternalMembers([FromHeader] string token, [FromQuery] int organization, [FromBody] List ids) + public async Task AddExternalMembers([FromQuery] int organization, [FromBody] List ids) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateInviteMembers(roles); @@ -73,7 +69,7 @@ public async Task AddExternalMembers([FromHeader] string tok /// Id de la organización. [HttpPost] [IdentityToken] - public async Task Create([FromBody] AccountModel modelo, [FromHeader] string token, [FromHeader] int organization) + public async Task Create([FromBody] AccountModel modelo, [FromHeader] int organization) { // Validar el modelo. @@ -84,9 +80,6 @@ public async Task Create([FromBody] AccountModel modelo, [Fr Message = "Uno o varios parámetros inválidos." }; - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Ajustar el modelo. modelo.Visibility = Visibility.Hidden; modelo.Password = $"pwd@{DateTime.Now.Year}"; @@ -116,7 +109,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr modelo.Identity.Unique = $"{modelo.Identity.Unique}@{orgIdentity.Model.Unique}"; // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -153,14 +146,11 @@ public async Task Create([FromBody] AccountModel modelo, [Fr /// Token de acceso [HttpGet] [IdentityToken] - public async Task>> ReadAll([FromHeader] string token, [FromHeader] int organization) + public async Task>> ReadAll([FromHeader] int organization) { - // Información del token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); ; - // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -193,20 +183,16 @@ public async Task>> ReadAll([FromH /// /// Agregar una identidad externa a la organización. /// - /// Token de acceso. /// Id de la organización. /// Lista de ids a agregar. /// Retorna el resultado del proceso. [HttpPost("expulse")] [IdentityToken] - public async Task Expulse([FromHeader] string token, [FromQuery] int organization, [FromBody] List ids) + public async Task Expulse([FromQuery] int organization, [FromBody] List ids) { - // Token. - JwtModel tokenInfo = HttpContext.Items[token] as JwtModel ?? new(); - // Confirmar el rol. - var roles = await rolesIam.RolesOn(tokenInfo.IdentityId, organization); + var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateDelete(roles); diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs new file mode 100644 index 0000000..0f80df6 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -0,0 +1,79 @@ +namespace LIN.Cloud.Identity.Areas.Policies; + +[Route("[controller]")] +public class PoliciesController(Data.Policies policiesData, Data.Groups groups, RolesIam iam) : AuthenticationBaseController +{ + + /// + /// Crear nueva política. + /// + /// Modelo de la identidad. + [HttpPost] + [IdentityToken] + public async Task Create([FromBody] PolicyModel modelo) + { + + // Si ya tiene una identidad. + if (modelo.OwnerIdentityId > 0) + { + // Obtener detalles. + var owner = await groups.GetOwnerByIdentity(modelo.OwnerIdentityId); + + if (owner.Response != Responses.Success) + return new(Responses.NotRows) { Message = $"No se encontró la organización del grupo con identidad {modelo.OwnerIdentityId}" }; + + // Validar roles. + var roles = await iam.RolesOn(AuthenticationInformation.IdentityId, owner.Model); + + bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); + + if (!hasPermission) + return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{owner.Model}." }; + + } + else + { + // Establecer propietario al usuario que realiza la solicitud. + modelo.OwnerIdentityId = AuthenticationInformation.IdentityId; + } + + // Formatear. + modelo.OwnerIdentity = new() + { + Id = modelo.OwnerIdentityId + }; + + modelo.ApplyFor = []; + + var response = await policiesData.Create(modelo); + return response; + } + + + /// + /// Validar si tiene autorización. + /// + /// Id de la política. + [HttpGet("isAllow")] + [IdentityToken] + public async Task IsAllow([FromQuery] string policy) + { + var response = await policiesData.HasFor(AuthenticationInformation.IdentityId, policy); + return response; + } + + + /// + /// Validar si tiene acceso a una política. + /// + /// Id de la política. + /// Id de la identidad. + [HttpGet("isAllow/identity")] + [IdentityToken] + public async Task IsAllow([FromQuery] string policy, [FromHeader] int identity) + { + var response = await policiesData.HasFor(identity, policy); + return response; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/AccountLogs.cs b/LIN.Cloud.Identity/Data/AccountLogs.cs index 0ad95a5..7a628cb 100644 --- a/LIN.Cloud.Identity/Data/AccountLogs.cs +++ b/LIN.Cloud.Identity/Data/AccountLogs.cs @@ -1,8 +1,8 @@ namespace LIN.Cloud.Identity.Data; -public class AccountLogs (DataContext context) +public class AccountLogs(DataContext context) { - + /// /// Crear nuevo log. /// @@ -45,7 +45,7 @@ public async Task Create(AccountLog log) } - + public async Task> ReadAll(int accountId) { diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs new file mode 100644 index 0000000..b78a0a3 --- /dev/null +++ b/LIN.Cloud.Identity/Data/Policies.cs @@ -0,0 +1,109 @@ +namespace LIN.Cloud.Identity.Data; + +public class Policies(DataContext context, Services.Utils.IIdentityService identityService) +{ + + /// + /// Crear nueva política. + /// + /// Modelo. + /// Retorna el id. + public async Task Create(PolicyModel modelo) + { + + try + { + + modelo.Id = Guid.NewGuid(); + + // Attach. + context.Attach(modelo.OwnerIdentity); + + // Guardar la cuenta. + await context.Policies.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + LastUnique = modelo.Id.ToString() + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ResourceExist + }; + } + + } + + + /// + /// Obtener las políticas donde el usuario es dueño. + /// + /// Id de la identidad. + /// Retorna la lista de políticas. + public async Task> ReadAllOwn(int id) + { + + // Ejecución + try + { + + // Políticas. + var policies = await (from policy in context.Policies + where policy.OwnerIdentityId == id + select policy).ToListAsync(); + + return new(Responses.Success, policies); + + } + catch (Exception) + { + } + return new(); + } + + + /// + /// Validar si una identidad y sus padres tienen acceso a una política. + /// + /// Id de la identidad base. + /// Id de la política. + public async Task HasFor(int id, string policyId) + { + + // Ejecución + try + { + + // Convertir el id. + var policyResult = Guid.TryParse(policyId, out Guid result); + + // Si hubo un error. + if (!policyResult) + return new(Responses.InvalidParam); + + // Obtener identidades base. + var ids = await identityService.GetIdentities(id); + + // Políticas. + var have = await (from policy in context.Policies + where policy.Id == result + && policy.ApplyFor.Any(t => ids.Contains(t.Id)) + select policy).AnyAsync(); + + // Respuesta. + return new(have ? Responses.Success : Responses.Unauthorized); + + } + catch (Exception) + { + } + return new(); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 608a7e1..d91907d 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -12,11 +12,7 @@ - - - - - + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 7885777..47e7e7c 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -7,7 +7,10 @@ // Servicios de contenedor. builder.Services.AddSignalR(); -builder.Services.AddLINHttp(); +builder.Services.AddLINHttp(true, (options) => +{ + options.OperationFilter>("token"); +}); builder.Services.AddLocalServices(); diff --git a/LIN.Cloud.Identity/Properties/launchSettings.json b/LIN.Cloud.Identity/Properties/launchSettings.json index 102c98a..932be78 100644 --- a/LIN.Cloud.Identity/Properties/launchSettings.json +++ b/LIN.Cloud.Identity/Properties/launchSettings.json @@ -14,10 +14,7 @@ }, "IIS Express": { "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "launchBrowser": true }, "WSL": { "commandName": "WSL2", diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index e973d08..fc6f704 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -1,4 +1,6 @@ -namespace LIN.Cloud.Identity.Services.Auth; +using LIN.Cloud.Identity.Services.Auth.Models; + +namespace LIN.Cloud.Identity.Services.Auth; public class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs) : Interfaces.IAuthentication { @@ -27,6 +29,12 @@ public class Authentication(Data.Accounts accountData, Data.AccountLogs accountL public AccountModel? Account { get; set; } = null; + /// + /// Ajustes. + /// + private AuthenticationSettings Settings { get; set; } = new(); + + /// /// Establecer credenciales. /// @@ -44,9 +52,12 @@ public void SetCredentials(string username, string password, string appCode) /// /// Iniciar el proceso. /// - public async Task Start() + public async Task Start(AuthenticationSettings? settings = null) { + // Validar. + Settings = settings ?? new(); + // Obtener la cuenta. var account = await GetAccount(); @@ -60,7 +71,8 @@ public async Task Start() if (!password) return Responses.InvalidPassword; - await SaveLog(); + if (Settings.Log) + await SaveLog(); return Responses.Success; } diff --git a/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs b/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs index 51eb0fd..abb168c 100644 --- a/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs @@ -1,14 +1,14 @@ -namespace LIN.Cloud.Identity.Services.Auth.Interfaces; +using LIN.Cloud.Identity.Services.Auth.Models; + +namespace LIN.Cloud.Identity.Services.Auth.Interfaces; public interface IAuthentication { - /// /// Iniciar el proceso. /// - public Task Start(); - + public Task Start(AuthenticationSettings? settings = null); /// @@ -20,14 +20,12 @@ public interface IAuthentication public void SetCredentials(string username, string password, string appCode); - /// /// Obtener el token. /// public string GenerateToken(); - /// /// Obtener la data. /// diff --git a/LIN.Cloud.Identity/Services/Auth/Models/AuthenticationSettings.cs b/LIN.Cloud.Identity/Services/Auth/Models/AuthenticationSettings.cs new file mode 100644 index 0000000..6d51bc1 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Auth/Models/AuthenticationSettings.cs @@ -0,0 +1,7 @@ +namespace LIN.Cloud.Identity.Services.Auth.Models; + +public class AuthenticationSettings +{ + public bool Log { get; set; } = true; + public bool ValidateApp { get; set; } = true; +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 94baa26..8e1617c 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -23,6 +23,7 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Iam. services.AddScoped(); diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 9492eb9..454f7bf 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -2,7 +2,6 @@ namespace LIN.Cloud.Identity.Services.Iam; - public class RolesIam(DataContext context, IIdentityService identityService) { diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index c51fad3..e56ac46 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -6,7 +6,7 @@ public partial class PassKeyHub /// /// Agregar un dispositivo administrador. /// - /// Token de acceso. + public async Task JoinAdmin(string token) { From 55af9069438c838d4e43bf71e393f2fa6b18cb63 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 14 Oct 2024 01:46:57 -0500 Subject: [PATCH 117/178] Code format --- .../Contexts/DataContext.cs | 17 ----- .../Models/PassKeyDBModel.cs | 13 ---- LIN.Cloud.Identity/Data/PassKeys.cs | 76 ++++--------------- 3 files changed, 16 insertions(+), 90 deletions(-) delete mode 100644 LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 58dfd40..a88d53c 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -43,12 +43,6 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet IdentityRoles { get; set; } - /// - /// PassKeys. - /// - public DbSet PassKeys { get; set; } - - /// /// Aplicaciones. /// @@ -117,16 +111,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } - // Modelo: PassKey. - { - - modelBuilder.Entity() - .HasOne(t => t.Account) - .WithMany() - .HasForeignKey(y => y.AccountId) - .OnDelete(DeleteBehavior.NoAction); - } - // Modelo: GroupModel. { @@ -262,7 +246,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("identity_roles"); modelBuilder.Entity().ToTable("group_members"); modelBuilder.Entity().ToTable("organizations"); - modelBuilder.Entity().ToTable("access_keys"); modelBuilder.Entity().ToTable("allow_apps"); modelBuilder.Entity().ToTable("applications"); modelBuilder.Entity().ToTable("account_logs"); diff --git a/LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs b/LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs deleted file mode 100644 index 554198e..0000000 --- a/LIN.Cloud.Identity.Persistence/Models/PassKeyDBModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using LIN.Types.Cloud.Identity.Models; - -namespace LIN.Cloud.Identity.Persistence.Models; - -public class PassKeyDBModel -{ - - public int Id { get; set; } - public DateTime Time { get; set; } - public int AccountId { get; set; } - public AccountModel Account { get; set; } = null!; - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/PassKeys.cs b/LIN.Cloud.Identity/Data/PassKeys.cs index 9ff5cf3..f466892 100644 --- a/LIN.Cloud.Identity/Data/PassKeys.cs +++ b/LIN.Cloud.Identity/Data/PassKeys.cs @@ -1,80 +1,36 @@ -using LIN.Cloud.Identity.Persistence.Models; - -namespace LIN.Cloud.Identity.Data; - +namespace LIN.Cloud.Identity.Data; public class PassKeys(DataContext context) { - public async Task Create(PassKeyDBModel modelo) - { - // Pre. - modelo.Id = 0; - - try - { - - // Guardar la identidad. - await context.PassKeys.AddAsync(modelo); - context.SaveChanges(); - - return new() - { - Response = Responses.Success, - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - + /// + /// Contar los logs de autenticación del dia. + /// + /// Id de la cuenta. public async Task> Count(int id) { - try { - - + // Tiempo. var time = DateTime.Now; - - var c = await (from a in context.PassKeys - where a.AccountId == id - where a.Time.Year == time.Year - && a.Time.Month == time.Month - && a.Time.Day == time.Day - select a).CountAsync(); - - + // Contar. + int count = await (from a in context.AccountLogs + where a.AccountId == id + && a.AuthenticationMethod == AuthenticationMethods.Authenticator + where a.Time.Year == time.Year + && a.Time.Month == time.Month + && a.Time.Day == time.Day + select a).CountAsync(); // Success. - return new() - { - Response = Responses.Success, - Model = c - }; - + return new(Responses.Success, count); } catch (Exception) { - return new() - { - Response = Responses.NotRows - }; + return new(Responses.NotRows); } } - - - } \ No newline at end of file From 14fa8ac75b7e03e658974445505a18be867c702c Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 14 Oct 2024 02:09:51 -0500 Subject: [PATCH 118/178] Obtener politicas --- .../Areas/Policies/PoliciesController.cs | 35 +++++++++-- LIN.Cloud.Identity/Data/Organizations.cs | 35 +++++++++++ LIN.Cloud.Identity/Data/Policies.cs | 62 +++++++++++++++++++ 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index 0f80df6..60d1eea 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -1,7 +1,8 @@ namespace LIN.Cloud.Identity.Areas.Policies; +[IdentityToken] [Route("[controller]")] -public class PoliciesController(Data.Policies policiesData, Data.Groups groups, RolesIam iam) : AuthenticationBaseController +public class PoliciesController(Data.Policies policiesData, Data.Groups groups, Data.Organizations organizations, RolesIam iam) : AuthenticationBaseController { /// @@ -9,7 +10,6 @@ public class PoliciesController(Data.Policies policiesData, Data.Groups groups, /// /// Modelo de la identidad. [HttpPost] - [IdentityToken] public async Task Create([FromBody] PolicyModel modelo) { @@ -30,6 +30,9 @@ public async Task Create([FromBody] PolicyModel modelo) if (!hasPermission) return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{owner.Model}." }; + var identityDirectory = await organizations.ReadDirectoryIdentity(owner.Model); + + } else { @@ -50,13 +53,34 @@ public async Task Create([FromBody] PolicyModel modelo) } + /// + /// Obtener políticas asociadas a una cuenta. + /// + [HttpGet("all")] + public async Task> All() + { + var response = await policiesData.ReadAllOwn(AuthenticationInformation.IdentityId); + return response; + } + + + /// + /// Obtener políticas asociadas a una organización. + /// + [HttpGet("organization/all")] + public async Task> OrganizationAll([FromHeader] int organization) + { + var response = await policiesData.ReadAll(organization); + return response; + } + + /// /// Validar si tiene autorización. /// /// Id de la política. [HttpGet("isAllow")] - [IdentityToken] - public async Task IsAllow([FromQuery] string policy) + public async Task IsAllow([FromQuery] string policy) { var response = await policiesData.HasFor(AuthenticationInformation.IdentityId, policy); return response; @@ -69,8 +93,7 @@ public async Task IsAllow([FromQuery] string policy) /// Id de la política. /// Id de la identidad. [HttpGet("isAllow/identity")] - [IdentityToken] - public async Task IsAllow([FromQuery] string policy, [FromHeader] int identity) + public async Task IsAllow([FromQuery] string policy, [FromHeader] int identity) { var response = await policiesData.HasFor(identity, policy); return response; diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index a7e0858..e33ae70 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -279,4 +279,39 @@ public async Task> ReadDirectory(int id) } + public async Task> ReadDirectoryIdentity(int id) + { + + try + { + + var org = await (from g in context.Organizations + where g.Id == id + select g.Directory.IdentityId).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (org <= 0) + return new() + { + Response = Responses.NotRows + }; + + // Success. + return new() + { + Response = Responses.Success, + Model = org + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs index b78a0a3..ab88560 100644 --- a/LIN.Cloud.Identity/Data/Policies.cs +++ b/LIN.Cloud.Identity/Data/Policies.cs @@ -68,6 +68,34 @@ public async Task> ReadAllOwn(int id) } + /// + /// Obtener las políticas asociadas a una organización. + /// + /// Id de la organización. + public async Task> ReadAll(int id) + { + + // Ejecución + try + { + + // Políticas. + var policies = await (from policy in context.Policies + join gr in context.Groups + on id equals gr.OwnerId + where policy.OwnerIdentityId == gr.IdentityId + select policy).Distinct().ToListAsync(); + + return new(Responses.Success, policies); + + } + catch (Exception) + { + } + return new(); + } + + /// /// Validar si una identidad y sus padres tienen acceso a una política. /// @@ -106,4 +134,38 @@ public async Task HasFor(int id, string policyId) return new(); } + + /// + /// Eliminar una política. + /// + /// Id de la política. + public async Task Remove(string policyId) + { + + // Ejecución + try + { + + // Convertir el id. + var policyResult = Guid.TryParse(policyId, out Guid result); + + // Si hubo un error. + if (!policyResult) + return new(Responses.InvalidParam); + + // Políticas. + var deleted = await (from policy in context.Policies + where policy.Id == result + select policy).ExecuteDeleteAsync(); + + // Respuesta. + return new(Responses.Success); + + } + catch (Exception) + { + } + return new(); + } + } \ No newline at end of file From 6d4fb9f06b5c249f12a44ba7f3836862d3033e86 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 14 Oct 2024 02:21:39 -0500 Subject: [PATCH 119/178] Nuevas rutas --- .../Areas/Policies/PoliciesController.cs | 39 +++++++++++++++++++ LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 33 +++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index 60d1eea..d671597 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -99,4 +99,43 @@ public async Task IsAllow([FromQuery] string policy, [FromHead return response; } + + /// + /// Eliminar política. + /// + /// Id de la política. + [HttpDelete] + public async Task Delete([FromQuery] string policy) + { + + // Validar Iam. + var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); + + if (iamResult != Types.Enumerations.IamLevels.Privileged) + return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para eliminar la política." }; + + var response = await policiesData.Remove(policy); + return response; + } + + + /// + /// Agregar integrantes a una política. + /// + /// Id de la política. + /// Id de la identidad a agregar. + [HttpPost("complacent")] + public async Task Put([FromQuery] string policy, [FromHeader] int identity) + { + + // Validar Iam. + var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); + + if (iamResult != Types.Enumerations.IamLevels.Privileged) + return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para agregar integrantes a la política." }; + + throw new Exception("Not implemented yet"); + } + + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 454f7bf..3e5ec5d 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -1,8 +1,9 @@ using LIN.Cloud.Identity.Services.Utils; +using LIN.Types.Enumerations; namespace LIN.Cloud.Identity.Services.Iam; -public class RolesIam(DataContext context, IIdentityService identityService) +public class RolesIam(DataContext context, Data.Groups groups, IIdentityService identityService) { /// @@ -25,6 +26,36 @@ where identities.Contains(rol.IdentityId) return roles; } + + + public async Task IamPolicy(int identity, string policy) + { + + // Is manager. + var isOwner = await (from pol in context.Policies + where pol.OwnerIdentityId == identity + && pol.Id == Guid.Parse(policy) + select pol).AnyAsync(); + + if (isOwner) + return IamLevels.Privileged; + + // Validar en la organización. + var olk = await (from pol in context.Policies + where pol.Id == Guid.Parse(policy) + select pol.OwnerIdentityId).FirstOrDefaultAsync(); + + // Owner. + var organizationId = await groups.GetOwnerByIdentity(olk); + + var roles = await RolesOn(identity, organizationId.Model); + + if (ValidateRoles.ValidateAlterMembers(roles)) + return IamLevels.Privileged; + + return IamLevels.NotAccess; + + } } From fa88971c8a48f9f3f4e414f637dfec8aafef5a05 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 14 Oct 2024 11:43:54 -0500 Subject: [PATCH 120/178] Controladores --- .../Contexts/DataContext.cs | 27 +++++- .../LIN.Cloud.Identity.Persistence.csproj | 3 +- .../Areas/Accounts/AccountLogs.cs | 5 +- .../AuthenticationController.cs | 6 +- .../Areas/Authentication/IntentsController.cs | 20 +--- .../Areas/Directories/DirectoryController.cs | 4 +- .../Areas/Groups/GroupsController.cs | 5 +- .../Areas/Groups/GroupsMembersController.cs | 8 +- .../Areas/Organizations/IdentityController.cs | 4 +- .../Organizations/OrganizationController.cs | 2 - .../OrganizationMembersController.cs | 7 +- .../Policies/PoliciesComplacentController.cs | 77 +++++++++++++++ .../Areas/Policies/PoliciesController.cs | 50 +--------- LIN.Cloud.Identity/Data/Policies.cs | 96 ++++++++++++++++++- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 3 +- .../Services/Realtime/PassKeyHub.cs | 2 +- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 2 - 17 files changed, 216 insertions(+), 105 deletions(-) create mode 100644 LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index a88d53c..be805b5 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -67,6 +67,12 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet Policies { get; set; } + /// + /// Identidades en Políticas. + /// + public DbSet IdentityOnPolicies { get; set; } + + /// /// Generación del modelo de base de datos. /// @@ -237,7 +243,26 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity() .HasMany(t => t.ApplyFor) - .WithMany(); + .WithOne() + .HasForeignKey(t=>t.PolicyId); + + + modelBuilder.Entity() + .Property(e => e.Id) + .IsRequired(); + + + modelBuilder.Entity() + .HasOne(t => t.Policy) + .WithMany(t=>t.ApplyFor) + .HasForeignKey(t => t.PolicyId); + + modelBuilder.Entity() + .HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t => t.IdentityId); + + modelBuilder.Entity().HasKey(t => new { t.PolicyId, t.IdentityId }); // Nombres de las tablas. modelBuilder.Entity().ToTable("identities"); diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index b4f7052..b09ad7d 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -5,12 +5,11 @@ enable enable Debug;Release;Local - AnyCPU;x86 - + diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs index 575178b..b2e22c9 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs @@ -1,5 +1,6 @@ namespace LIN.Cloud.Identity.Areas.Accounts; +[IdentityToken] [Route("account/logs")] public class AccountLogsController(Data.AccountLogs accountData) : AuthenticationBaseController { @@ -9,15 +10,11 @@ public class AccountLogsController(Data.AccountLogs accountData) : Authenticatio /// /// Lista de logs. [HttpGet] - [IdentityToken] public async Task> ReadAll() { - // Obtiene el usuario var response = await accountData.ReadAll(AuthenticationInformation.AccountId); - return response; - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 252754a..e171eca 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -28,7 +28,11 @@ public async Task> Login([FromQuery] string us authentication.SetCredentials(user, password, application); // Respuesta. - var response = await authentication.Start(); + var response = await authentication.Start(new() + { + ValidateApp = true, + Log = true + }); // Validación al obtener el usuario switch (response) diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 78d1e41..2b05bfb 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -2,6 +2,7 @@ namespace LIN.Cloud.Identity.Areas.Authentication; +[IdentityToken] [Route("[controller]")] public class IntentsController(Data.PassKeys passkeyData) : AuthenticationBaseController { @@ -10,7 +11,6 @@ public class IntentsController(Data.PassKeys passkeyData) : AuthenticationBaseCo /// Obtiene la lista de intentos de llaves de paso están activos. /// [HttpGet] - [IdentityToken] public HttpReadAllResponse GetAll() { try @@ -46,23 +46,13 @@ where I.Expiración > timeNow /// Obtiene la lista de intentos de llaves de paso están activos. /// [HttpGet("count")] - [IdentityToken] public async Task> Count() { - try - { - var x = await passkeyData.Count(AuthenticationInformation.AccountId); + // Contar. + var countResponse = await passkeyData.Count(AuthenticationInformation.AccountId); - // Retorna - return new(Responses.Success, x.Model); - } - catch (Exception) - { - return new(Responses.Undefined) - { - Message = "Hubo un error al obtener los intentos de passkey" - }; - } + // Retorna + return countResponse; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index 3e16c8d..c15c709 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -1,5 +1,6 @@ namespace LIN.Cloud.Identity.Areas.Directories; +[IdentityToken] [Route("[controller]")] public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : AuthenticationBaseController { @@ -10,7 +11,6 @@ public class DirectoryController(Data.DirectoryMembers directoryMembersData, Dat /// Id de la organización. /// Retorna la lista de integrantes./returns> [HttpGet("read/all")] - [IdentityToken] public async Task> ReadAll([FromHeader] int organization) { // Validar organización. @@ -33,7 +33,6 @@ public async Task> ReadAll([FromHeader] int org // Retorna el resultado return response; - } @@ -43,7 +42,6 @@ public async Task> ReadAll([FromHeader] int org /// Id del directorio. /// Retorna la lista de integrantes del directorio. [HttpGet("read/members")] - [IdentityToken] public async Task> ReadMembers([FromQuery] int directory) { diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index 79dc540..eaf3ce0 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -1,5 +1,6 @@ namespace LIN.Cloud.Identity.Areas.Groups; +[IdentityToken] [Route("[controller]")] public class GroupsController(Data.Groups groupData, RolesIam rolesIam) : AuthenticationBaseController { @@ -9,7 +10,6 @@ public class GroupsController(Data.Groups groupData, RolesIam rolesIam) : Authen /// /// Modelo del grupo. [HttpPost] - [IdentityToken] public async Task Create([FromBody] GroupModel group) { @@ -57,7 +57,6 @@ public async Task Create([FromBody] GroupModel group) /// Id de la organización. /// Retorna la lista de grupos. [HttpGet("all")] - [IdentityToken] public async Task> ReadAll([FromHeader] int organization) { // Confirmar el rol. @@ -96,7 +95,6 @@ public async Task> ReadAll([FromHeader] int orga /// Id del grupo. /// Retorna el modelo del grupo. [HttpGet] - [IdentityToken] public async Task> ReadOne([FromHeader] int id) { @@ -147,7 +145,6 @@ public async Task> ReadOne([FromHeader] int id) /// Id de la identidad del grupo. /// Retorna el modelo del grupo. [HttpGet("identity")] - [IdentityToken] public async Task> ReadIdentity([FromHeader] int id) { diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index 9dcfec8..dfa0186 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -1,5 +1,6 @@ namespace LIN.Cloud.Identity.Areas.Groups; +[IdentityToken] [Route("Groups/members")] public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : AuthenticationBaseController { @@ -10,7 +11,6 @@ public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembe /// Modelo del integrante. /// Retorna el id del nuevo integrante. [HttpPost] - [IdentityToken] public async Task Create([FromBody] GroupMember model) { @@ -66,7 +66,6 @@ public async Task Create([FromBody] GroupMember model) /// Lista de las identidades. /// Retorna la respuesta del proceso. [HttpPost("list")] - [IdentityToken] public async Task Create([FromHeader] int group, [FromBody] List ids) { @@ -128,7 +127,6 @@ public async Task Create([FromHeader] int group, [FromBody] /// /// ID del grupo. [HttpGet("read/all")] - [IdentityToken] public async Task> ReadMembers([FromQuery] int group) { @@ -180,7 +178,6 @@ public async Task> ReadMembers([FromQuery] int /// Grupo. /// Patron de búsqueda. [HttpGet("search")] - [IdentityToken] public async Task> Search([FromHeader] int group, [FromQuery] string pattern) { // Obtener la organización. @@ -232,7 +229,6 @@ public async Task> Search([FromHeader] int gr /// Grupo. /// Patron de búsqueda. [HttpGet("search/groups")] - [IdentityToken] public async Task> SearchOnGroups([FromHeader] int group, [FromQuery] string pattern) { @@ -284,7 +280,6 @@ public async Task> SearchOnGroups([FromHeader /// /// ID del grupo. [HttpDelete("remove")] - [IdentityToken] public async Task DeleteMembers([FromQuery] int identity, [FromQuery] int group) { @@ -334,7 +329,6 @@ public async Task DeleteMembers([FromQuery] int identity, [Fro /// /// ID del grupo. [HttpGet("read/on/all")] - [IdentityToken] public async Task> OnMembers([FromQuery] int organization, [FromQuery] int identity) { diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 8a8eeb5..baf957e 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -1,5 +1,6 @@ namespace LIN.Cloud.Identity.Areas.Organizations; +[IdentityToken] [Route("[controller]")] public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData, RolesIam rolesIam) : AuthenticationBaseController { @@ -9,7 +10,6 @@ public class IdentityController(Data.DirectoryMembers directoryMembersData, Data /// /// Modelo del grupo. [HttpPost] - [IdentityToken] public async Task Create([FromBody] IdentityRolesModel rolModel) { @@ -64,7 +64,6 @@ public async Task Create([FromBody] IdentityRolesModel rolMode /// Identidad /// Id de la organización. [HttpGet("roles/all")] - [IdentityToken] public async Task> ReadAll([FromHeader] int identity, [FromHeader] int organization) { @@ -114,7 +113,6 @@ public async Task> ReadAll([FromHeader] /// Id de la organización. /// Rol. [HttpDelete("roles")] - [IdentityToken] public async Task ReadAll([FromHeader] int identity, [FromHeader] int organization, [FromHeader] Roles rol) { diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 57946bd..12fba88 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -55,7 +55,6 @@ public async Task Create([FromBody] OrganizationModel modelo /// Obtiene una organización por medio del Id. /// /// ID de la organización - /// Token de acceso [HttpGet("read/id")] [IdentityToken] public async Task> ReadOneByID([FromQuery] int id) @@ -108,7 +107,6 @@ public async Task> ReadOneByID([FromQuery /// /// Obtiene las organizaciones donde un usuario es miembro. /// - /// Token de acceso [HttpGet("read/all")] [IdentityToken] public async Task> ReadAll() diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index a9d7f3e..fdfa4c9 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -2,6 +2,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; +[IdentityToken] [Route("orgs/members")] public class OrganizationMembersController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : AuthenticationBaseController { @@ -13,7 +14,6 @@ public class OrganizationMembersController(Data.Organizations organizationsData, /// Lista de ids a agregar. /// Retorna el resultado del proceso. [HttpPost("invite")] - [IdentityToken] public async Task AddExternalMembers([FromQuery] int organization, [FromBody] List ids) { @@ -65,10 +65,8 @@ public async Task AddExternalMembers([FromQuery] int organiz /// Crea un nuevo miembro en una organización. /// /// Modelo de la cuenta. - /// Token de acceso de un administrador. /// Id de la organización. [HttpPost] - [IdentityToken] public async Task Create([FromBody] AccountModel modelo, [FromHeader] int organization) { @@ -143,9 +141,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr /// /// Obtiene la lista de integrantes asociados a una organización. /// - /// Token de acceso [HttpGet] - [IdentityToken] public async Task>> ReadAll([FromHeader] int organization) { @@ -187,7 +183,6 @@ public async Task>> ReadAll([FromH /// Lista de ids a agregar. /// Retorna el resultado del proceso. [HttpPost("expulse")] - [IdentityToken] public async Task Expulse([FromQuery] int organization, [FromBody] List ids) { diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs new file mode 100644 index 0000000..be9adba --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs @@ -0,0 +1,77 @@ +namespace LIN.Cloud.Identity.Areas.Policies; + +[IdentityToken] +[Route("policies/complacent")] +public class PoliciesComplacentController(Data.Policies policiesData, RolesIam iam) : AuthenticationBaseController +{ + + /// + /// Agregar integrantes a una política. + /// + /// Id de la política. + /// Id de la identidad a agregar. + [HttpPost] + public async Task AddMember([FromQuery] string policy, [FromHeader] int identity) + { + + // Validar Iam. + var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); + + if (iamResult != Types.Enumerations.IamLevels.Privileged) + return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para agregar integrantes a la política." }; + + // Agregar integrante + var response = await policiesData.AddMember(identity, policy); + + return response; + } + + + /// + /// Eliminar integrantes a una política. + /// + /// Id de la política. + /// Id de la identidad a agregar. + [HttpDelete] + public async Task DeleteMember([FromQuery] string policy, [FromHeader] int identity) + { + + // Validar Iam. + var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); + + if (iamResult != Types.Enumerations.IamLevels.Privileged) + return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para eliminar integrantes de la política." }; + + // Agregar integrante + var response = await policiesData.RemoveMember(identity, policy); + + return response; + } + + + /// + /// Validar si tiene autorización. + /// + /// Id de la política. + [HttpGet] + public async Task IsAllow([FromQuery] string policy) + { + var response = await policiesData.HasFor(AuthenticationInformation.IdentityId, policy); + return response; + } + + + /// + /// Validar si tiene acceso a una política. + /// + /// Id de la política. + /// Id de la identidad. + [HttpGet("identity")] + public async Task IsAllow([FromQuery] string policy, [FromHeader] int identity) + { + var response = await policiesData.HasFor(identity, policy); + return response; + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index d671597..742e20f 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -2,7 +2,7 @@ [IdentityToken] [Route("[controller]")] -public class PoliciesController(Data.Policies policiesData, Data.Groups groups, Data.Organizations organizations, RolesIam iam) : AuthenticationBaseController +public class PoliciesController(Data.Policies policiesData, Data.Groups groups, RolesIam iam) : AuthenticationBaseController { /// @@ -30,9 +30,6 @@ public async Task Create([FromBody] PolicyModel modelo) if (!hasPermission) return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{owner.Model}." }; - var identityDirectory = await organizations.ReadDirectoryIdentity(owner.Model); - - } else { @@ -75,31 +72,6 @@ public async Task> OrganizationAll([FromHeader] } - /// - /// Validar si tiene autorización. - /// - /// Id de la política. - [HttpGet("isAllow")] - public async Task IsAllow([FromQuery] string policy) - { - var response = await policiesData.HasFor(AuthenticationInformation.IdentityId, policy); - return response; - } - - - /// - /// Validar si tiene acceso a una política. - /// - /// Id de la política. - /// Id de la identidad. - [HttpGet("isAllow/identity")] - public async Task IsAllow([FromQuery] string policy, [FromHeader] int identity) - { - var response = await policiesData.HasFor(identity, policy); - return response; - } - - /// /// Eliminar política. /// @@ -118,24 +90,4 @@ public async Task Delete([FromQuery] string policy) return response; } - - /// - /// Agregar integrantes a una política. - /// - /// Id de la política. - /// Id de la identidad a agregar. - [HttpPost("complacent")] - public async Task Put([FromQuery] string policy, [FromHeader] int identity) - { - - // Validar Iam. - var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); - - if (iamResult != Types.Enumerations.IamLevels.Privileged) - return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para agregar integrantes a la política." }; - - throw new Exception("Not implemented yet"); - } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs index ab88560..5b93269 100644 --- a/LIN.Cloud.Identity/Data/Policies.cs +++ b/LIN.Cloud.Identity/Data/Policies.cs @@ -121,7 +121,7 @@ public async Task HasFor(int id, string policyId) // Políticas. var have = await (from policy in context.Policies where policy.Id == result - && policy.ApplyFor.Any(t => ids.Contains(t.Id)) + && policy.ApplyFor.Any(t => ids.Contains(t.IdentityId)) select policy).AnyAsync(); // Respuesta. @@ -153,9 +153,99 @@ public async Task Remove(string policyId) if (!policyResult) return new(Responses.InvalidParam); + // Eliminar vinculos a políticas. + var deleted = await (from policy in context.IdentityOnPolicies + where policy.PolicyId == result + select policy).ExecuteDeleteAsync(); + // Políticas. - var deleted = await (from policy in context.Policies - where policy.Id == result + deleted = await (from policy in context.Policies + where policy.Id == result + select policy).ExecuteDeleteAsync(); + + // Respuesta. + return new(Responses.Success); + + } + catch (Exception) + { + } + return new(); + } + + + /// + /// Agregar una identidad a una política. + /// + /// Id de la identidad base. + /// Id de la política. + public async Task AddMember(int id, string policyId) + { + + // Ejecución + try + { + + // Convertir el id. + var policyResult = Guid.TryParse(policyId, out Guid result); + + // Si hubo un error. + if (!policyResult) + return new(Responses.InvalidParam); + + IdentityAllowedOnPolicyModel allow = new() + { + Policy = new() + { + Id = result + }, + Identity = new() + { + Id = id + } + }; + + context.Attach(allow.Policy); + context.Attach(allow.Identity); + + await context.IdentityOnPolicies.AddAsync(allow); + + context.SaveChanges(); + + // Respuesta. + return new(Responses.Success); + + } + catch (Exception) + { + } + return new(); + } + + + /// + /// Eliminar una identidad a una política. + /// + /// Id de la identidad base. + /// Id de la política. + public async Task RemoveMember(int id, string policyId) + { + + // Ejecución + try + { + + // Convertir el id. + var policyResult = Guid.TryParse(policyId, out Guid result); + + // Si hubo un error. + if (!policyResult) + return new(Responses.InvalidParam); + + // Eliminar vínculos a políticas. + var deleted = await (from policy in context.IdentityOnPolicies + where policy.PolicyId == result + && policy.IdentityId == id select policy).ExecuteDeleteAsync(); // Respuesta. diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index d91907d..086ff45 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -6,13 +6,12 @@ enable false Debug;Release;Local - AnyCPU;x86 - + diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index fda8532..81e6dbf 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -1,6 +1,6 @@ namespace LIN.Cloud.Identity.Services.Realtime; -public partial class PassKeyHub(Data.PassKeys passKeysData, Data.AccountLogs accountLogs) : Hub +public partial class PassKeyHub(Data.AccountLogs accountLogs) : Hub { /// diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 2f301bf..542df7a 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -4,11 +4,9 @@ net8.0 enable enable - false true Debug;Release;Local - AnyCPU;x86 From 3543c2cb7a3ea0d24d768ca39637ad0a8db751b3 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 14 Oct 2024 13:42:01 -0500 Subject: [PATCH 121/178] Solucion de errores --- .../Extensions/EntityFramework.cs | 55 ++++++++++++++ .../Policies/PoliciesComplacentController.cs | 20 +++++ .../Areas/Policies/PoliciesController.cs | 28 ++++++- LIN.Cloud.Identity/Data/Organizations.cs | 7 +- LIN.Cloud.Identity/Data/Policies.cs | 36 ++++++++- LIN.Cloud.Identity/Services/Iam/RolesIam.cs | 74 +++++++++++++++++++ 6 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 LIN.Cloud.Identity.Persistence/Extensions/EntityFramework.cs diff --git a/LIN.Cloud.Identity.Persistence/Extensions/EntityFramework.cs b/LIN.Cloud.Identity.Persistence/Extensions/EntityFramework.cs new file mode 100644 index 0000000..740e75c --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Extensions/EntityFramework.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore; + +namespace LIN.Cloud.Identity.Persistence.Extensions; + +public static class EntityFramework +{ + /// + /// Adjunta una entidad al contexto si no está siendo rastreada. + /// Si ya está siendo rastreada, actualiza sus valores con los de la entidad proporcionada. + /// + /// El tipo de la entidad. + /// El contexto de la base de datos. + /// La entidad a adjuntar o actualizar. + public static TEntity AttachOrUpdate(this DbContext context, TEntity entity) where TEntity : class + { + + // Obtiene la clave primaria de la entidad + var key = context.Model.FindEntityType(typeof(TEntity))?.FindPrimaryKey(); + if (key == null) + { + throw new InvalidOperationException($"La entidad {typeof(TEntity).Name} no tiene una clave primaria definida."); + } + + // Construye un objeto anónimo con los valores de la clave primaria + var keyValues = key.Properties.Select(p => + typeof(TEntity).GetProperty(p.Name).GetValue(entity)).ToArray(); + + // Busca si la entidad ya está siendo rastreada en el contexto + var localEntity = context.Set().Local.FirstOrDefault(e => + { + bool isMatch = true; + for (int i = 0; i < key.Properties.Count; i++) + { + var property = key.Properties[i]; + var value1 = typeof(TEntity).GetProperty(property.Name).GetValue(e); + var value2 = keyValues[i]; + if (!object.Equals(value1, value2)) + { + isMatch = false; + break; + } + } + return isMatch; + }); + + if (localEntity != null) + { + return localEntity; + } + + // Si no está siendo rastreada, la adjunta al contexto + context.Attach(entity); + return entity; + } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs index be9adba..b356348 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs @@ -5,6 +5,26 @@ public class PoliciesComplacentController(Data.Policies policiesData, RolesIam iam) : AuthenticationBaseController { + + [HttpGet("applicable")] + public async Task Applicants([FromHeader] int identity) + { + + if (identity != AuthenticationInformation.IdentityId) + { + var iamResult = await iam.IamIdentity(AuthenticationInformation.IdentityId, identity); + if (iamResult != Types.Enumerations.IamLevels.Privileged) + return new ResponseBase(Responses.Unauthorized) + { + Message = "No tienes permisos para ver los solicitantes de la política." + }; + } + + var response = await policiesData.ApplicablePolicies(identity); + return response; + } + + /// /// Agregar integrantes a una política. /// diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index 742e20f..fbf02ec 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -2,7 +2,7 @@ [IdentityToken] [Route("[controller]")] -public class PoliciesController(Data.Policies policiesData, Data.Groups groups, RolesIam iam) : AuthenticationBaseController +public class PoliciesController(Data.Policies policiesData, Data.Groups groups, RolesIam iam, Data.Organizations organizations) : AuthenticationBaseController { /// @@ -10,11 +10,11 @@ public class PoliciesController(Data.Policies policiesData, Data.Groups groups, /// /// Modelo de la identidad. [HttpPost] - public async Task Create([FromBody] PolicyModel modelo) + public async Task Create([FromBody] PolicyModel modelo, [FromHeader] int? organization, [FromHeader] bool assign) { // Si ya tiene una identidad. - if (modelo.OwnerIdentityId > 0) + if (modelo.OwnerIdentityId > 0 && (organization is null || organization <= 0)) { // Obtener detalles. var owner = await groups.GetOwnerByIdentity(modelo.OwnerIdentityId); @@ -31,6 +31,20 @@ public async Task Create([FromBody] PolicyModel modelo) return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{owner.Model}." }; } + else if (organization is not null && organization > 0) + { + // Validar roles. + var roles = await iam.RolesOn(AuthenticationInformation.IdentityId, organization.Value); + + bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); + + if (!hasPermission) + return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{organization}." }; + + // + var directoryIdentity = await organizations.ReadDirectoryIdentity(organization.Value); + modelo.OwnerIdentityId = directoryIdentity.Model; + } else { // Establecer propietario al usuario que realiza la solicitud. @@ -44,6 +58,12 @@ public async Task Create([FromBody] PolicyModel modelo) }; modelo.ApplyFor = []; + if (assign) + modelo.ApplyFor = [new() { + Identity = new(){ + Id = modelo.OwnerIdentityId + } + }]; var response = await policiesData.Create(modelo); return response; @@ -81,7 +101,7 @@ public async Task Delete([FromQuery] string policy) { // Validar Iam. - var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); + var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); if (iamResult != Types.Enumerations.IamLevels.Privileged) return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para eliminar la política." }; diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index e33ae70..6bc1819 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Data; -public class Organizations(DataContext context) +public class Organizations(DataContext context, RolesIam iam) { @@ -314,4 +314,9 @@ public async Task> ReadDirectoryIdentity(int id) } + + + + + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs index 5b93269..30dcda5 100644 --- a/LIN.Cloud.Identity/Data/Policies.cs +++ b/LIN.Cloud.Identity/Data/Policies.cs @@ -10,7 +10,6 @@ public class Policies(DataContext context, Services.Utils.IIdentityService ident /// Retorna el id. public async Task Create(PolicyModel modelo) { - try { @@ -19,6 +18,12 @@ public async Task Create(PolicyModel modelo) // Attach. context.Attach(modelo.OwnerIdentity); + foreach (var e in modelo.ApplyFor) + { + e.Identity = context.AttachOrUpdate(e.Identity); + e.Policy = modelo; + } + // Guardar la cuenta. await context.Policies.AddAsync(modelo); context.SaveChanges(); @@ -96,6 +101,35 @@ on id equals gr.OwnerId } + /// + /// Obtener las políticas aplicables a una identidad. + /// + /// Id de la identidad. + public async Task> ApplicablePolicies(int id) + { + + // Ejecución + try + { + + // Obtener las identidades + var identities = await identityService.GetIdentities(id); + + // Políticas. + var policies = await (from policy in context.IdentityOnPolicies + where identities.Contains(policy.IdentityId) + select policy.Policy).Distinct().ToListAsync(); + + return new(Responses.Success, policies); + + } + catch (Exception) + { + } + return new(); + } + + /// /// Validar si una identidad y sus padres tienen acceso a una política. /// diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs index 3e5ec5d..71e02ef 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/RolesIam.cs @@ -56,6 +56,45 @@ public async Task IamPolicy(int identity, string policy) return IamLevels.NotAccess; } + + + + + + public async Task IamIdentity(int identity1, int identity2) + { + + var organizations = await (from z in context.Groups + where z.Members.Any(x => x.IdentityId == identity1) + && z.Members.Any(x => x.IdentityId == identity2) + select z.Owner!.Id).Distinct().ToListAsync(); + + // Si es un grupo. + var organization = await groups.GetOwnerByIdentity(identity2); + + if (organization.Response == Responses.Success) + { + organizations.Add(organization.Model); + organizations = organizations.Distinct().ToList(); + } + + bool have = false; + + foreach (var e in organizations) + { + var x = await RolesOn(identity1, e); + + if (ValidateRoles.ValidateReadSecure(x)) + { + have = true; + break; + } + } + + + return have ? IamLevels.Privileged : IamLevels.NotAccess; + } + } @@ -81,6 +120,24 @@ public static bool ValidateRead(IEnumerable roles) } + public static bool ValidateReadSecure(IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager, + Roles.AccountOperator, + Roles.Regular, + Roles.SecurityViewer + ]; + + + var sets = availed.Intersect(roles); + + return sets.Any(); + + } + public static bool ValidateAlterMembers(IEnumerable roles) { @@ -124,4 +181,21 @@ public static bool ValidateDelete(IEnumerable roles) return sets.Any(); } + + + public static bool ValidateReadPolicies(IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager, + Roles.AccountOperator, + Roles.SecurityViewer, + ]; + + var sets = availed.Intersect(roles); + + return sets.Any(); + + } } \ No newline at end of file From 6d3db2172643469e9d043bd8be74568e6f2e15e3 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 14 Oct 2024 16:13:04 -0500 Subject: [PATCH 122/178] Mejoras de IAM --- .../Areas/Directories/DirectoryController.cs | 4 +- .../Areas/Groups/GroupsController.cs | 10 ++-- .../Areas/Groups/GroupsMembersController.cs | 16 +++--- .../Areas/Organizations/IdentityController.cs | 8 +-- .../OrganizationMembersController.cs | 10 ++-- .../Policies/PoliciesComplacentController.cs | 6 +-- .../Areas/Policies/PoliciesController.cs | 8 +-- LIN.Cloud.Identity/Data/Organizations.cs | 4 +- .../Services/Extensions/LocalServices.cs | 2 +- LIN.Cloud.Identity/Services/Iam/IamPolicy.cs | 42 +++++++++++++++ .../Services/Iam/{RolesIam.cs => IamRoles.cs} | 53 +++++++------------ LIN.Cloud.Identity/Usings.cs | 3 +- 12 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 LIN.Cloud.Identity/Services/Iam/IamPolicy.cs rename LIN.Cloud.Identity/Services/Iam/{RolesIam.cs => IamRoles.cs} (78%) diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index c15c709..d7f9848 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -2,7 +2,7 @@ [IdentityToken] [Route("[controller]")] -public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, RolesIam rolesIam) : AuthenticationBaseController +public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, IamRoles rolesIam) : AuthenticationBaseController { /// @@ -58,7 +58,7 @@ public async Task> ReadMembers([FromQuery] int // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index eaf3ce0..4f831f4 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -2,7 +2,7 @@ [IdentityToken] [Route("[controller]")] -public class GroupsController(Data.Groups groupData, RolesIam rolesIam) : AuthenticationBaseController +public class GroupsController(Data.Groups groupData, IamRoles rolesIam) : AuthenticationBaseController { /// @@ -14,7 +14,7 @@ public async Task Create([FromBody] GroupModel group) { // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, group.OwnerId ?? 0); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, group.OwnerId ?? 0); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -60,7 +60,7 @@ public async Task Create([FromBody] GroupModel group) public async Task> ReadAll([FromHeader] int organization) { // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -110,7 +110,7 @@ public async Task> ReadOne([FromHeader] int id) }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -160,7 +160,7 @@ public async Task> ReadIdentity([FromHeader] int }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index dfa0186..5be4661 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -2,7 +2,7 @@ [IdentityToken] [Route("Groups/members")] -public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : AuthenticationBaseController +public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, IamRoles rolesIam) : AuthenticationBaseController { /// @@ -26,7 +26,7 @@ public async Task Create([FromBody] GroupMember model) }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -82,7 +82,7 @@ public async Task Create([FromHeader] int group, [FromBody] // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -142,7 +142,7 @@ public async Task> ReadMembers([FromQuery] int }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -192,7 +192,7 @@ public async Task> Search([FromHeader] int gr }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -245,7 +245,7 @@ public async Task> SearchOnGroups([FromHeader // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -295,7 +295,7 @@ public async Task DeleteMembers([FromQuery] int identity, [Fro }; // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -333,7 +333,7 @@ public async Task> OnMembers([FromQuery] int org { // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index baf957e..b4638e6 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -2,7 +2,7 @@ [IdentityToken] [Route("[controller]")] -public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData, RolesIam rolesIam) : AuthenticationBaseController +public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData, IamRoles rolesIam) : AuthenticationBaseController { /// @@ -14,7 +14,7 @@ public async Task Create([FromBody] IdentityRolesModel rolMode { // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, rolModel.OrganizationId); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, rolModel.OrganizationId); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -68,7 +68,7 @@ public async Task> ReadAll([FromHeader] { // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -117,7 +117,7 @@ public async Task ReadAll([FromHeader] int identity, [FromHead { // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index fdfa4c9..8eff39f 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [IdentityToken] [Route("orgs/members")] -public class OrganizationMembersController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, RolesIam rolesIam) : AuthenticationBaseController +public class OrganizationMembersController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, IamRoles rolesIam) : AuthenticationBaseController { /// @@ -18,7 +18,7 @@ public async Task AddExternalMembers([FromQuery] int organiz { // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateInviteMembers(roles); @@ -107,7 +107,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr modelo.Identity.Unique = $"{modelo.Identity.Unique}@{orgIdentity.Model.Unique}"; // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -146,7 +146,7 @@ public async Task>> ReadAll([FromH { // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -187,7 +187,7 @@ public async Task Expulse([FromQuery] int organization, [FromB { // Confirmar el rol. - var roles = await rolesIam.RolesOn(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateDelete(roles); diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs index b356348..ec5d848 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs @@ -2,7 +2,7 @@ [IdentityToken] [Route("policies/complacent")] -public class PoliciesComplacentController(Data.Policies policiesData, RolesIam iam) : AuthenticationBaseController +public class PoliciesComplacentController(Data.Policies policiesData, IamRoles iam, IamPolicy iamPolicy) : AuthenticationBaseController { @@ -35,7 +35,7 @@ public async Task AddMember([FromQuery] string policy, [FromHe { // Validar Iam. - var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); + var iamResult = await iamPolicy.Validate(AuthenticationInformation.IdentityId, policy); if (iamResult != Types.Enumerations.IamLevels.Privileged) return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para agregar integrantes a la política." }; @@ -57,7 +57,7 @@ public async Task DeleteMember([FromQuery] string policy, [Fro { // Validar Iam. - var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); + var iamResult = await iamPolicy.Validate(AuthenticationInformation.IdentityId, policy); if (iamResult != Types.Enumerations.IamLevels.Privileged) return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para eliminar integrantes de la política." }; diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index fbf02ec..04c27e0 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -2,7 +2,7 @@ [IdentityToken] [Route("[controller]")] -public class PoliciesController(Data.Policies policiesData, Data.Groups groups, RolesIam iam, Data.Organizations organizations) : AuthenticationBaseController +public class PoliciesController(Data.Policies policiesData, Data.Groups groups, IamRoles iam, Data.Organizations organizations, IamPolicy iamPolicy) : AuthenticationBaseController { /// @@ -23,7 +23,7 @@ public async Task Create([FromBody] PolicyModel modelo, [Fro return new(Responses.NotRows) { Message = $"No se encontró la organización del grupo con identidad {modelo.OwnerIdentityId}" }; // Validar roles. - var roles = await iam.RolesOn(AuthenticationInformation.IdentityId, owner.Model); + var roles = await iam.Validate(AuthenticationInformation.IdentityId, owner.Model); bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); @@ -34,7 +34,7 @@ public async Task Create([FromBody] PolicyModel modelo, [Fro else if (organization is not null && organization > 0) { // Validar roles. - var roles = await iam.RolesOn(AuthenticationInformation.IdentityId, organization.Value); + var roles = await iam.Validate(AuthenticationInformation.IdentityId, organization.Value); bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); @@ -101,7 +101,7 @@ public async Task Delete([FromQuery] string policy) { // Validar Iam. - var iamResult = await iam.IamPolicy(AuthenticationInformation.IdentityId, policy); + var iamResult = await iamPolicy.Validate(AuthenticationInformation.IdentityId, policy); if (iamResult != Types.Enumerations.IamLevels.Privileged) return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para eliminar la política." }; diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index 6bc1819..97f0948 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Data; -public class Organizations(DataContext context, RolesIam iam) +public class Organizations(DataContext context, IamRoles iam) { @@ -64,7 +64,7 @@ public async Task Create(OrganizationModel modelo) context.SaveChanges(); - // RolesIam. + // IamRoles. var rol = new IdentityRolesModel { Identity = account.Identity, diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 8e1617c..ec0b3df 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -26,7 +26,7 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddScoped(); // Iam. - services.AddScoped(); + services.AddScoped(); // Allow. services.AddScoped(); diff --git a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs new file mode 100644 index 0000000..bbf67fb --- /dev/null +++ b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs @@ -0,0 +1,42 @@ +namespace LIN.Cloud.Identity.Services.Iam; + +public class IamPolicy(DataContext context, Data.Groups groups, IamRoles rolesIam) +{ + + /// + /// Validar el nivel de acceso a una política. + /// + /// Id de la identidad. + /// Id de la política. + public async Task Validate(int identity, string policy) + { + + // Si la identidad es la administradora de la política. + var isOwner = await (from pol in context.Policies + where pol.OwnerIdentityId == identity + && pol.Id == Guid.Parse(policy) + select pol).AnyAsync(); + + // Es privilegiado. + if (isOwner) + return IamLevels.Privileged; + + // Obtener la identidad del dueño de la política. + var olk = await (from pol in context.Policies + where pol.Id == Guid.Parse(policy) + select pol.OwnerIdentityId).FirstOrDefaultAsync(); + + // Obtener la organización. + var organizationId = await groups.GetOwnerByIdentity(olk); + + // Obtener roles de la identidad sobre la organización. + var roles = await rolesIam.Validate(identity, organizationId.Model); + + // Tiene permisos para modificar la política. + if (ValidateRoles.ValidateAlterPolicies(roles)) + return IamLevels.Privileged; + + return IamLevels.NotAccess; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs b/LIN.Cloud.Identity/Services/Iam/IamRoles.cs similarity index 78% rename from LIN.Cloud.Identity/Services/Iam/RolesIam.cs rename to LIN.Cloud.Identity/Services/Iam/IamRoles.cs index 71e02ef..096f95c 100644 --- a/LIN.Cloud.Identity/Services/Iam/RolesIam.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamRoles.cs @@ -1,9 +1,8 @@ using LIN.Cloud.Identity.Services.Utils; -using LIN.Types.Enumerations; namespace LIN.Cloud.Identity.Services.Iam; -public class RolesIam(DataContext context, Data.Groups groups, IIdentityService identityService) +public class IamRoles(DataContext context, Data.Groups groups, IIdentityService identityService) { /// @@ -11,7 +10,7 @@ public class RolesIam(DataContext context, Data.Groups groups, IIdentityService /// /// Id de la identidad. /// Id de la organización. - public async Task> RolesOn(int identity, int organization) + public async Task> Validate(int identity, int organization) { // Identidades. @@ -28,37 +27,6 @@ where identities.Contains(rol.IdentityId) } - public async Task IamPolicy(int identity, string policy) - { - - // Is manager. - var isOwner = await (from pol in context.Policies - where pol.OwnerIdentityId == identity - && pol.Id == Guid.Parse(policy) - select pol).AnyAsync(); - - if (isOwner) - return IamLevels.Privileged; - - // Validar en la organización. - var olk = await (from pol in context.Policies - where pol.Id == Guid.Parse(policy) - select pol.OwnerIdentityId).FirstOrDefaultAsync(); - - // Owner. - var organizationId = await groups.GetOwnerByIdentity(olk); - - var roles = await RolesOn(identity, organizationId.Model); - - if (ValidateRoles.ValidateAlterMembers(roles)) - return IamLevels.Privileged; - - return IamLevels.NotAccess; - - } - - - public async Task IamIdentity(int identity1, int identity2) @@ -82,7 +50,7 @@ where z.Members.Any(x => x.IdentityId == identity1) foreach (var e in organizations) { - var x = await RolesOn(identity1, e); + var x = await Validate(identity1, e); if (ValidateRoles.ValidateReadSecure(x)) { @@ -139,6 +107,21 @@ public static bool ValidateReadSecure(IEnumerable roles) } + public static bool ValidateAlterPolicies(IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager + ]; + + var sets = availed.Intersect(roles); + + return sets.Any(); + + } + + public static bool ValidateAlterMembers(IEnumerable roles) { List availed = diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index eae5a05..5672740 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -22,4 +22,5 @@ global using System.IO; global using System.Security.Claims; // Framework. -global using System.Text; \ No newline at end of file +global using System.Text; +global using LIN.Types.Enumerations; \ No newline at end of file From b196ff68e7dfd8dbb56961bdb39d42b274cce3a0 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Tue, 15 Oct 2024 22:31:39 -0500 Subject: [PATCH 123/178] Update --- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 4 ++-- LIN.Cloud.Identity/Program.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 086ff45..50feb18 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,9 +9,9 @@ - + - + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 47e7e7c..0498728 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -2,6 +2,7 @@ using LIN.Cloud.Identity.Services.Auth.Interfaces; using LIN.Cloud.Identity.Services.Extensions; using LIN.Cloud.Identity.Services.Realtime; +using Microsoft.AspNetCore.Http.HttpResults; var builder = WebApplication.CreateBuilder(args); @@ -14,12 +15,15 @@ builder.Services.AddLocalServices(); + // Servicio de autenticación. builder.Services.AddScoped(); builder.Services.AddPersistence(builder.Configuration); var app = builder.Build(); + + app.UseLINHttp(); // Base de datos. @@ -31,4 +35,11 @@ app.UseAuthorization(); app.MapControllers(); +builder.Services.AddDatabaseAction(() => +{ + var context = app.Services.GetRequiredService(); + context.Accounts.Where(x => x.Id == 0).FirstOrDefaultAsync(); + return "Success"; +}); + app.Run(); \ No newline at end of file From 7ee0135ff16cbacdbb1b746f15cf77e1bc994a6e Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Thu, 17 Oct 2024 21:39:39 -0500 Subject: [PATCH 124/178] hotfix --- LIN.Cloud.Identity/Program.cs | 2 -- LIN.Cloud.Identity/Services/Extensions/LocalServices.cs | 1 + LIN.Cloud.Identity/Services/Iam/IamPolicy.cs | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 0498728..ae6b72a 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -22,8 +22,6 @@ var app = builder.Build(); - - app.UseLINHttp(); // Base de datos. diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index ec0b3df..ba3e759 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -27,6 +27,7 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic // Iam. services.AddScoped(); + services.AddScoped(); // Allow. services.AddScoped(); diff --git a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs index bbf67fb..fc50038 100644 --- a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs @@ -22,12 +22,12 @@ public async Task Validate(int identity, string policy) return IamLevels.Privileged; // Obtener la identidad del dueño de la política. - var olk = await (from pol in context.Policies - where pol.Id == Guid.Parse(policy) - select pol.OwnerIdentityId).FirstOrDefaultAsync(); + var ownerPolicy = await (from pol in context.Policies + where pol.Id == Guid.Parse(policy) + select pol.OwnerIdentityId).FirstOrDefaultAsync(); // Obtener la organización. - var organizationId = await groups.GetOwnerByIdentity(olk); + var organizationId = await groups.GetOwnerByIdentity(ownerPolicy); // Obtener roles de la identidad sobre la organización. var roles = await rolesIam.Validate(identity, organizationId.Model); From 189d5dd37ccf9573c0a0d91a7aee698d4265c128 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 20 Oct 2024 01:04:00 -0500 Subject: [PATCH 125/178] Mejoras de seguridad y bases --- .github/workflows/dotnet-desktop.yml | 115 ------- .../Contexts/DataContext.cs | 315 ++++++++---------- .../Extensions/PersistenceExtensions.cs | 5 + .../LIN.Cloud.Identity.Persistence.csproj | 4 +- LIN.Cloud.Identity.sln | 28 ++ .../Areas/AuthenticationBaseController.cs | 4 +- .../Areas/Policies/PoliciesController.cs | 9 + .../Areas/Policies/PolicyRequirements.cs | 29 ++ LIN.Cloud.Identity/Data/Policies.cs | 89 ++++- .../Data/PoliciesRequirement.cs | 90 +++++ LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- .../Services/Extensions/LocalServices.cs | 2 + LIN.Cloud.Identity/Services/Iam/IamPolicy.cs | 31 ++ LIN.Identity.Tests/LIN.Identity.Tests.csproj | 2 +- 14 files changed, 426 insertions(+), 299 deletions(-) delete mode 100644 .github/workflows/dotnet-desktop.yml create mode 100644 LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs create mode 100644 LIN.Cloud.Identity/Data/PoliciesRequirement.cs diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml deleted file mode 100644 index 22ec423..0000000 --- a/.github/workflows/dotnet-desktop.yml +++ /dev/null @@ -1,115 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow will build, test, sign and package a WPF or Windows Forms desktop application -# built on .NET Core. -# To learn how to migrate your existing application to .NET Core, -# refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework -# -# To configure this workflow: -# -# 1. Configure environment variables -# GitHub sets default environment variables for every workflow run. -# Replace the variables relative to your project in the "env" section below. -# -# 2. Signing -# Generate a signing certificate in the Windows Application -# Packaging Project or add an existing signing certificate to the project. -# Next, use PowerShell to encode the .pfx file using Base64 encoding -# by running the following Powershell script to generate the output string: -# -# $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte -# [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt' -# -# Open the output file, SigningCertificate_Encoded.txt, and copy the -# string inside. Then, add the string to the repo as a GitHub secret -# and name it "Base64_Encoded_Pfx." -# For more information on how to configure your signing certificate for -# this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing -# -# Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key". -# See "Build the Windows Application Packaging project" below to see how the secret is used. -# -# For more information on GitHub Actions, refer to https://github.com/features/actions -# For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications, -# refer to https://github.com/microsoft/github-actions-for-desktop-apps - -name: .NET Core Desktop - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - - build: - - strategy: - matrix: - configuration: [Debug, Release] - - runs-on: windows-latest # For a list of available runner types, refer to - # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on - - env: - Solution_Name: your-solution-name # Replace with your solution name, i.e. MyWpfApp.sln. - Test_Project_Path: your-test-project-path # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj. - Wap_Project_Directory: your-wap-project-directory-name # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package. - Wap_Project_Path: your-wap-project-path # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj. - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - # Install the .NET Core workload - - name: Install .NET Core - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild - - name: Setup MSBuild.exe - uses: microsoft/setup-msbuild@v2 - - # Execute all unit tests in the solution - - name: Execute unit tests - run: dotnet test - - # Restore the application to populate the obj folder with RuntimeIdentifiers - - name: Restore the application - run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration - env: - Configuration: ${{ matrix.configuration }} - - # Decode the base 64 encoded pfx and save the Signing_Certificate - - name: Decode the pfx - run: | - $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") - $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx - [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte) - - # Create the app package by building and packaging the Windows Application Packaging project - - name: Create the app package - run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }} - env: - Appx_Bundle: Always - Appx_Bundle_Platforms: x86|x64 - Appx_Package_Build_Mode: StoreUpload - Configuration: ${{ matrix.configuration }} - - # Remove the pfx - - name: Remove the pfx - run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx - - # Upload the MSIX package: https://github.com/marketplace/actions/upload-a-build-artifact - - name: Upload build artifacts - uses: actions/upload-artifact@v3 - with: - name: MSIX Package - path: ${{ env.Wap_Project_Directory }}\AppPackages diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index be805b5..d0ec5fc 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -1,5 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Models; -using LIN.Types.Cloud.Identity.Models; +using LIN.Types.Cloud.Identity.Models; using Microsoft.EntityFrameworkCore; namespace LIN.Cloud.Identity.Persistence.Contexts; @@ -74,211 +73,175 @@ public class DataContext(DbContextOptions options) : DbContext(opti /// - /// Generación del modelo de base de datos. + /// Requerimientos de políticas. + /// + public DbSet PolicyRequirements { get; set; } + + + /// + /// Crear el modelo en BD. /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - - // Modelo: Políticas. + // Identity Model + modelBuilder.Entity(entity => { - - } + entity.ToTable("identities"); + entity.HasIndex(t => t.Unique).IsUnique(); + }); - // Modelo: Identity. + // Account Model + modelBuilder.Entity(entity => { - modelBuilder.Entity() - .HasIndex(t => t.Unique) - .IsUnique(); - } + entity.ToTable("accounts"); + entity.HasIndex(t => t.IdentityId).IsUnique(); + }); - // Modelo: Account. + // Organization Model + modelBuilder.Entity(entity => { - modelBuilder.Entity() - .HasIndex(t => t.IdentityId) - .IsUnique(); - } - - // Modelo: Account. + entity.ToTable("organizations"); + entity.HasOne(o => o.Directory) + .WithOne() + .HasForeignKey(o => o.DirectoryId) + .OnDelete(DeleteBehavior.NoAction); + }); + + // Group Model + modelBuilder.Entity(entity => { - - modelBuilder.Entity() - .HasOne(o => o.Directory) - .WithOne() - .HasForeignKey(o => o.DirectoryId) - .OnDelete(DeleteBehavior.NoAction); - - - modelBuilder.Entity() - .HasOne(g => g.Owner) - .WithMany(o => o.OwnedGroups) - .HasForeignKey(g => g.OwnerId) - .OnDelete(DeleteBehavior.NoAction); - - - } - - // Modelo: GroupModel. + entity.ToTable("groups"); + entity.HasOne(g => g.Owner) + .WithMany(o => o.OwnedGroups) + .HasForeignKey(g => g.OwnerId) + .OnDelete(DeleteBehavior.NoAction); + + entity.HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t => t.IdentityId) + .OnDelete(DeleteBehavior.NoAction); + }); + + // Group Member Model + modelBuilder.Entity(entity => { + entity.ToTable("group_members"); + entity.HasKey(t => new { t.IdentityId, t.GroupId }); - modelBuilder.Entity() - .HasOne(t => t.Identity) - .WithMany() - .HasForeignKey(t => t.IdentityId) - .OnDelete(DeleteBehavior.NoAction); - + entity.HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t => t.IdentityId) + .OnDelete(DeleteBehavior.NoAction); - } + entity.HasOne(t => t.Group) + .WithMany(t => t.Members) + .HasForeignKey(t => t.GroupId); + }); - // Modelo: GroupMemberModel. + // Identity Roles Model + modelBuilder.Entity(entity => { + entity.ToTable("identity_roles"); + entity.HasKey(t => new { t.Rol, t.IdentityId, t.OrganizationId }); - modelBuilder.Entity() - .HasKey(t => new - { - t.IdentityId, - t.GroupId - }); + entity.HasOne(t => t.Identity) + .WithMany(t => t.Roles) + .HasForeignKey(t => t.IdentityId); - modelBuilder.Entity() - .HasOne(t => t.Identity) - .WithMany() - .HasForeignKey(y => y.IdentityId) - .OnDelete(DeleteBehavior.NoAction); - - modelBuilder.Entity() - .HasOne(t => t.Group) - .WithMany(t => t.Members) - .HasForeignKey(y => y.GroupId); + entity.HasOne(t => t.Organization) + .WithMany() + .HasForeignKey(t => t.OrganizationId); + }); + // Application Model + modelBuilder.Entity(entity => + { + entity.ToTable("applications"); + entity.HasIndex(t => t.IdentityId).IsUnique(); + entity.HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t => t.IdentityId); - } + entity.HasOne(t => t.Owner) + .WithMany() + .HasForeignKey(t => t.OwnerId) + .OnDelete(DeleteBehavior.NoAction); + }); - // Modelo: IdentityRolesModel. + // Allow Apps Model + modelBuilder.Entity(entity => { - modelBuilder.Entity() - .HasOne(t => t.Identity) - .WithMany(t => t.Roles) - .HasForeignKey(y => y.IdentityId); - - modelBuilder.Entity() - .HasOne(t => t.Organization) - .WithMany() - .HasForeignKey(y => y.OrganizationId); - - modelBuilder.Entity() - .HasKey(t => new - { - t.Rol, - t.IdentityId, - t.OrganizationId - }); - - } - - // Modelo: Application. + entity.ToTable("allow_apps"); + entity.HasKey(t => new { t.ApplicationId, t.IdentityId }); + + entity.HasOne(t => t.Application) + .WithMany() + .HasForeignKey(t => t.ApplicationId) + .OnDelete(DeleteBehavior.NoAction); + + entity.HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t => t.IdentityId) + .OnDelete(DeleteBehavior.NoAction); + }); + + // Account Logs Model + modelBuilder.Entity(entity => { - modelBuilder.Entity() - .HasOne(t => t.Identity) - .WithMany() - .HasForeignKey(t => t.IdentityId); + entity.ToTable("account_logs"); + entity.HasOne(t => t.Application) + .WithMany() + .HasForeignKey(t => t.ApplicationId) + .OnDelete(DeleteBehavior.NoAction); + + entity.HasOne(t => t.Account) + .WithMany() + .HasForeignKey(t => t.AccountId) + .OnDelete(DeleteBehavior.NoAction); + }); + + // Policy Model + modelBuilder.Entity(entity => + { + entity.ToTable("policies"); + entity.HasOne(t => t.OwnerIdentity) + .WithMany() + .HasForeignKey(t => t.OwnerIdentityId) + .OnDelete(DeleteBehavior.NoAction); + entity.HasMany(t => t.ApplyFor) + .WithOne() + .HasForeignKey(t => t.PolicyId); - modelBuilder.Entity() - .HasIndex(t => t.IdentityId) - .IsUnique(); + entity.Property(e => e.Id).IsRequired(); + }); - modelBuilder.Entity() - .HasOne(t => t.Owner) - .WithMany() - .HasForeignKey(t => t.OwnerId) - .OnDelete(DeleteBehavior.NoAction); + // Identity Allowed on Policy Model + modelBuilder.Entity(entity => + { + entity.HasKey(t => new { t.PolicyId, t.IdentityId }); - } + entity.HasOne(t => t.Policy) + .WithMany(t => t.ApplyFor) + .HasForeignKey(t => t.PolicyId); - // Modelo: Allow Apps. - { - modelBuilder.Entity() - .HasOne(t => t.Application) - .WithMany() - .HasForeignKey(t => t.ApplicationId) - .OnDelete(DeleteBehavior.NoAction); - - modelBuilder.Entity() - .HasOne(t => t.Identity) - .WithMany() - .HasForeignKey(t => t.IdentityId) - .OnDelete(DeleteBehavior.NoAction); - - modelBuilder.Entity() - .HasKey(t => new - { - t.ApplicationId, - t.IdentityId - }); - - } - - // Modelo: Account Logs. + entity.HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t => t.IdentityId); + }); + + // Policy Requirement Model + modelBuilder.Entity(entity => { - modelBuilder.Entity() - .HasOne(t => t.Application) - .WithMany() - .HasForeignKey(t => t.ApplicationId) - .OnDelete(DeleteBehavior.NoAction); - - modelBuilder.Entity() - .HasOne(t => t.Account) - .WithMany() - .HasForeignKey(t => t.AccountId) - .OnDelete(DeleteBehavior.NoAction); - } - - - modelBuilder.Entity() - .HasOne(t => t.OwnerIdentity) - .WithMany() - .HasForeignKey(t => t.OwnerIdentityId) - .OnDelete(DeleteBehavior.NoAction); - - modelBuilder.Entity() - .HasMany(t => t.ApplyFor) - .WithOne() - .HasForeignKey(t=>t.PolicyId); - - - modelBuilder.Entity() - .Property(e => e.Id) - .IsRequired(); - - - modelBuilder.Entity() - .HasOne(t => t.Policy) - .WithMany(t=>t.ApplyFor) - .HasForeignKey(t => t.PolicyId); - - modelBuilder.Entity() - .HasOne(t => t.Identity) - .WithMany() - .HasForeignKey(t => t.IdentityId); - - modelBuilder.Entity().HasKey(t => new { t.PolicyId, t.IdentityId }); - - // Nombres de las tablas. - modelBuilder.Entity().ToTable("identities"); - modelBuilder.Entity().ToTable("accounts"); - modelBuilder.Entity().ToTable("groups"); - modelBuilder.Entity().ToTable("identity_roles"); - modelBuilder.Entity().ToTable("group_members"); - modelBuilder.Entity().ToTable("organizations"); - modelBuilder.Entity().ToTable("allow_apps"); - modelBuilder.Entity().ToTable("applications"); - modelBuilder.Entity().ToTable("account_logs"); - modelBuilder.Entity().ToTable("policies"); + entity.ToTable("policy_requirements"); + entity.HasOne(t => t.Policy) + .WithMany() + .HasForeignKey(t => t.PolicyId); + }); // Base. base.OnModelCreating(modelBuilder); } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 82b9f07..6adcb5f 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -19,7 +19,12 @@ public static IServiceCollection AddPersistence(this IServiceCollection services string? connectionName = "cloud"; #if LOCAL connectionName = "local"; +#elif DEBUG_DEV + connectionName = "cloud-dev"; +#elif RELEASE_DEV + connectionName = "cloud-dev"; #endif + services.AddDbContextPool(options => { options.UseSqlServer(configuration.GetConnectionString(connectionName)); diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index b09ad7d..d9125e4 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -4,12 +4,12 @@ net8.0 enable enable - Debug;Release;Local + Debug;Release;Local;Release-dev;Debug-dev - + diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index a43dc30..53232ae 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -13,16 +13,24 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x86 = Debug|x86 + Debug-dev|Any CPU = Debug-dev|Any CPU + Debug-dev|x86 = Debug-dev|x86 Local|Any CPU = Local|Any CPU Local|x86 = Local|x86 Release|Any CPU = Release|Any CPU Release|x86 = Release|x86 + Release-dev|Any CPU = Release-dev|Any CPU + Release-dev|x86 = Release-dev|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|x86.ActiveCfg = Debug|x86 {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug|x86.Build.0 = Debug|x86 + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug-dev|Any CPU.ActiveCfg = Debug-dev|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug-dev|Any CPU.Build.0 = Debug-dev|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug-dev|x86.ActiveCfg = Debug-dev|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Debug-dev|x86.Build.0 = Debug-dev|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Local|Any CPU.ActiveCfg = Local|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Local|Any CPU.Build.0 = Local|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Local|x86.ActiveCfg = Local|x86 @@ -31,10 +39,18 @@ Global {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|Any CPU.Build.0 = Release|Any CPU {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|x86.ActiveCfg = Release|x86 {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release|x86.Build.0 = Release|x86 + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release-dev|Any CPU.ActiveCfg = Release-dev|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release-dev|Any CPU.Build.0 = Release-dev|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release-dev|x86.ActiveCfg = Release-dev|Any CPU + {D7EF6814-1C64-44BC-BEAF-87835D44EEF8}.Release-dev|x86.Build.0 = Release-dev|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|x86.ActiveCfg = Debug|x86 {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug|x86.Build.0 = Debug|x86 + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug-dev|Any CPU.ActiveCfg = Debug-dev|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug-dev|Any CPU.Build.0 = Debug-dev|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug-dev|x86.ActiveCfg = Debug-dev|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Debug-dev|x86.Build.0 = Debug-dev|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Local|Any CPU.ActiveCfg = Local|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Local|Any CPU.Build.0 = Local|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Local|x86.ActiveCfg = Local|x86 @@ -43,10 +59,18 @@ Global {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|Any CPU.Build.0 = Release|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|x86.ActiveCfg = Release|x86 {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release|x86.Build.0 = Release|x86 + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release-dev|Any CPU.ActiveCfg = Release-dev|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release-dev|Any CPU.Build.0 = Release-dev|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release-dev|x86.ActiveCfg = Release-dev|Any CPU + {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release-dev|x86.Build.0 = Release-dev|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.Build.0 = Debug|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Debug|x86.ActiveCfg = Debug|x86 {157863A4-209B-42A0-AE80-F6C403692480}.Debug|x86.Build.0 = Debug|x86 + {157863A4-209B-42A0-AE80-F6C403692480}.Debug-dev|Any CPU.ActiveCfg = Debug-dev|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Debug-dev|Any CPU.Build.0 = Debug-dev|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Debug-dev|x86.ActiveCfg = Debug-dev|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Debug-dev|x86.Build.0 = Debug-dev|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Local|Any CPU.ActiveCfg = Local|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Local|Any CPU.Build.0 = Local|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Local|x86.ActiveCfg = Local|x86 @@ -55,6 +79,10 @@ Global {157863A4-209B-42A0-AE80-F6C403692480}.Release|Any CPU.Build.0 = Release|Any CPU {157863A4-209B-42A0-AE80-F6C403692480}.Release|x86.ActiveCfg = Release|x86 {157863A4-209B-42A0-AE80-F6C403692480}.Release|x86.Build.0 = Release|x86 + {157863A4-209B-42A0-AE80-F6C403692480}.Release-dev|Any CPU.ActiveCfg = Release-dev|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Release-dev|Any CPU.Build.0 = Release-dev|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Release-dev|x86.ActiveCfg = Release-dev|Any CPU + {157863A4-209B-42A0-AE80-F6C403692480}.Release-dev|x86.Build.0 = Release-dev|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs b/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs index 57b1fff..b1e4fc6 100644 --- a/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs +++ b/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs @@ -6,12 +6,12 @@ public class AuthenticationBaseController : ControllerBase /// /// Información de autenticación. /// - public JwtModel AuthenticationInformation => base.HttpContext.Items["authentication"] as JwtModel ?? new(); + public JwtModel AuthenticationInformation => HttpContext.Items["authentication"] as JwtModel ?? new(); /// /// Obtener el token desde el header. /// - public string Token => base.HttpContext.Request.Headers["token"].ToString() ?? string.Empty; + public string Token => HttpContext.Request.Headers["token"].ToString() ?? string.Empty; } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index 04c27e0..a7e44bf 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -110,4 +110,13 @@ public async Task Delete([FromQuery] string policy) return response; } + + + [HttpGet] + public async Task> Read([FromQuery] string policy) + { + var response = await policiesData.Read(Guid.Parse(policy)); + return response; + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs b/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs new file mode 100644 index 0000000..2f2c6a0 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs @@ -0,0 +1,29 @@ +namespace LIN.Cloud.Identity.Areas.Policies; + +[IdentityToken] +[Route("[controller]")] +public class PolicyRequirementsController(Data.PoliciesRequirement policiesData, IamPolicy iamPolicy) : AuthenticationBaseController +{ + + /// + /// Crear nueva política. + /// + /// Modelo de la identidad. + [HttpPost] + public async Task Create([FromBody] PolicyRequirementModel modelo) + { + + // Validar roles. + var roles = await iamPolicy.Validate(AuthenticationInformation.IdentityId, modelo.PolicyId.ToString()); + + if (roles != IamLevels.Privileged) + return new(Responses.Unauthorized) { Message = $"No tienes permisos c" }; + + // Forma + var response = await policiesData.Create(modelo); + return response; + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs index 30dcda5..fcd9842 100644 --- a/LIN.Cloud.Identity/Data/Policies.cs +++ b/LIN.Cloud.Identity/Data/Policies.cs @@ -1,6 +1,6 @@ namespace LIN.Cloud.Identity.Data; -public class Policies(DataContext context, Services.Utils.IIdentityService identityService) +public class Policies(DataContext context, Data.PoliciesRequirement policiesRequirement, Services.Utils.IIdentityService identityService, IamPolicy iamPolicy) { /// @@ -72,6 +72,46 @@ public async Task> ReadAllOwn(int id) return new(); } + + /// + /// Obtener una política. + /// + /// Id. + public async Task> Read(Guid guid) + { + + // Ejecución + try + { + + // Políticas. + var policie = await (from policy in context.Policies + where policy.Id == guid + select new PolicyModel + { + Description = policy.Description, + Id = policy.Id, + Name = policy.Name, + OwnerIdentity = new() + { + Id = policy.OwnerIdentityId, + Unique = policy.OwnerIdentity.Unique, + Type = policy.OwnerIdentity.Type + } + }).FirstOrDefaultAsync(); + + if (policie == null) + return new(Responses.NotRows); + + return new(Responses.Success, policie); + + } + catch (Exception) + { + } + return new(); + } + /// /// Obtener las políticas asociadas a una organización. @@ -89,7 +129,18 @@ public async Task> ReadAll(int id) join gr in context.Groups on id equals gr.OwnerId where policy.OwnerIdentityId == gr.IdentityId - select policy).Distinct().ToListAsync(); + select new PolicyModel + { + Description = policy.Description, + Id = policy.Id, + Name = policy.Name, + OwnerIdentity = new() + { + Id = policy.OwnerIdentityId, + Unique = policy.OwnerIdentity.Unique, + Type = policy.OwnerIdentity.Type + } + }).Distinct().ToListAsync(); return new(Responses.Success, policies); @@ -158,6 +209,40 @@ public async Task HasFor(int id, string policyId) && policy.ApplyFor.Any(t => ids.Contains(t.IdentityId)) select policy).AnyAsync(); + if (!have) + return new(Responses.Unauthorized); + + // Validar requerimientos. + var requirements = await policiesRequirement.ReadAll(result); + + DateTime now = DateTime.Now; + + foreach (var requirement in requirements.Models) + { + if (requirement.Type == PolicyRequirementTypes.Time) + { + + var x = System.Text.Json.JsonSerializer.Deserialize(requirement.Requirement ?? ""); + + + + var start = TimeSpan.FromTicks(x.start); + var end = TimeSpan.FromTicks(x.end); + //var days = TimeSpan.FromTicks((requirement.Requirement as dynamic).days); + + bool valid = iamPolicy.PolicyRequirement(start, end); + } + else if (requirement.Type == PolicyRequirementTypes.TFA) + { + + } + else if (requirement.Type == PolicyRequirementTypes.PasswordTime) + { + + } + } + + // Respuesta. return new(have ? Responses.Success : Responses.Unauthorized); diff --git a/LIN.Cloud.Identity/Data/PoliciesRequirement.cs b/LIN.Cloud.Identity/Data/PoliciesRequirement.cs new file mode 100644 index 0000000..0da8f6c --- /dev/null +++ b/LIN.Cloud.Identity/Data/PoliciesRequirement.cs @@ -0,0 +1,90 @@ +namespace LIN.Cloud.Identity.Data; + +public class PoliciesRequirement(DataContext context) +{ + + public async Task Create(PolicyRequirementModel modelo) + { + try + { + + modelo.Policy = new() + { + Id = modelo.PolicyId + }; + + // Attach. + context.Attach(modelo.Policy); + + if (modelo.Requirement is not string) + modelo.Requirement = System.Text.Json.JsonSerializer.Serialize(modelo.Requirement); + + // Guardar la cuenta. + await context.PolicyRequirements.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + LastUnique = modelo.Id.ToString() + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ResourceExist + }; + } + + } + + + public async Task> ReadAll(Guid policy) + { + + // Ejecución + try + { + + // Políticas. + var qw = (from policyRequirement in context.PolicyRequirements + select policyRequirement).ToQueryString(); + + var policies = await (from policyRequirement in context.PolicyRequirements + where policyRequirement.PolicyId == policy + select policyRequirement).Distinct().ToListAsync(); + + return new(Responses.Success, policies); + + } + catch (Exception) + { + } + return new(); + } + + + public async Task Remove(int policyRequirement) + { + + // Ejecución + try + { + + var deleted = await (from policy in context.PolicyRequirements + where policy.Id == policyRequirement + select policy).ExecuteDeleteAsync(); + + // Respuesta. + return new(Responses.Success); + + } + catch (Exception) + { + } + return new(); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 50feb18..7113429 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -5,7 +5,7 @@ enable enable false - Debug;Release;Local + Debug;Release;Local;Release-dev;Debug-dev diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index ba3e759..7083373 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -14,6 +14,7 @@ public static class LocalServices public static IServiceCollection AddLocalServices(this IServiceCollection services) { + // Servicios de datos. services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -24,6 +25,7 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Iam. services.AddScoped(); diff --git a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs index fc50038..37fbe4a 100644 --- a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs @@ -39,4 +39,35 @@ public async Task Validate(int identity, string policy) return IamLevels.NotAccess; } + + + public bool PolicyRequirement(TimeSpan start, TimeSpan end) + { + // Definir los días válidos (lunes = 1, domingo = 7) + DayOfWeek[] diasValidos = { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, + DayOfWeek.Thursday, DayOfWeek.Friday }; + + // Obtener la hora y día actuales + DateTime ahora = DateTime.Now; + TimeSpan horaActual = ahora.TimeOfDay; + DayOfWeek diaActual = ahora.DayOfWeek; + + // Verificar si el día actual está en los días válidos + bool esDiaValido = Array.Exists(diasValidos, dia => dia == diaActual); + + // Verificar si la hora actual está en el rango + bool estaEnRango = horaActual >= start && horaActual <= end; + + // Resultado final + if (esDiaValido && estaEnRango) + { + return true; + } + else + { + return false; + } + } + + } \ No newline at end of file diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 542df7a..4239f08 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -6,7 +6,7 @@ enable false true - Debug;Release;Local + Debug;Release;Local;Release-dev;Debug-dev From 8b5c29b1dca2ca6888b86d0efb53841985a2b139 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Wed, 23 Oct 2024 20:45:42 -0500 Subject: [PATCH 126/178] =?UTF-8?q?Refactorizaci=C3=B3n=20de=20Policies=20?= =?UTF-8?q?y=20adici=C3=B3n=20de=20PolicyService?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se ha añadido una referencia al paquete `Newtonsoft.Json` en el archivo `LIN.Cloud.Identity.csproj` para manejar la serialización y deserialización de JSON. En `Policies.cs`, se ha actualizado la clase `Policies` para utilizar `PolicyService` en lugar de `IamPolicy` para la validación de políticas. Además, se ha añadido un nuevo método `Read` para obtener una política específica por su `guid`. Se ha eliminado el código que validaba los requerimientos de políticas directamente en la clase `Policies` y se ha reemplazado con una llamada al método `Validate` de `PolicyService`, simplificando así la lógica en `Policies`. Se ha añadido una nueva clase `PolicyService` en `PolicyService.cs`, que contiene métodos para validar los requerimientos de políticas: `Validate` (validación general), `Time` (validación de tiempo), `PasswordTime` (validación de tiempo de cambio de contraseña) y `TFA` (validación de doble factor de autenticación). En `LocalServices.cs`, se ha registrado `PolicyService` como un servicio para que pueda ser inyectado y utilizado en otras partes de la aplicación. --- LIN.Cloud.Identity/Data/Policies.cs | 71 ++++------ LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 1 + .../Services/Extensions/LocalServices.cs | 1 + .../Services/Utils/PolicyService.cs | 122 ++++++++++++++++++ 4 files changed, 150 insertions(+), 45 deletions(-) create mode 100644 LIN.Cloud.Identity/Services/Utils/PolicyService.cs diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs index fcd9842..ebc609d 100644 --- a/LIN.Cloud.Identity/Data/Policies.cs +++ b/LIN.Cloud.Identity/Data/Policies.cs @@ -1,6 +1,8 @@ -namespace LIN.Cloud.Identity.Data; +using LIN.Cloud.Identity.Services.Utils; -public class Policies(DataContext context, Data.PoliciesRequirement policiesRequirement, Services.Utils.IIdentityService identityService, IamPolicy iamPolicy) +namespace LIN.Cloud.Identity.Data; + +public class Policies(DataContext context, Data.PoliciesRequirement policiesRequirement, Services.Utils.IIdentityService identityService, PolicyService policyService) { /// @@ -72,7 +74,7 @@ public async Task> ReadAllOwn(int id) return new(); } - + /// /// Obtener una política. /// @@ -86,19 +88,19 @@ public async Task> Read(Guid guid) // Políticas. var policie = await (from policy in context.Policies - where policy.Id == guid - select new PolicyModel - { - Description = policy.Description, - Id = policy.Id, - Name = policy.Name, - OwnerIdentity = new() - { - Id = policy.OwnerIdentityId, - Unique = policy.OwnerIdentity.Unique, - Type = policy.OwnerIdentity.Type - } - }).FirstOrDefaultAsync(); + where policy.Id == guid + select new PolicyModel + { + Description = policy.Description, + Id = policy.Id, + Name = policy.Name, + OwnerIdentity = new() + { + Id = policy.OwnerIdentityId, + Unique = policy.OwnerIdentity.Unique, + Type = policy.OwnerIdentity.Type + } + }).FirstOrDefaultAsync(); if (policie == null) return new(Responses.NotRows); @@ -209,42 +211,21 @@ public async Task HasFor(int id, string policyId) && policy.ApplyFor.Any(t => ids.Contains(t.IdentityId)) select policy).AnyAsync(); + // No tiene acceso. if (!have) return new(Responses.Unauthorized); - // Validar requerimientos. + // Obtener requerimientos. var requirements = await policiesRequirement.ReadAll(result); - DateTime now = DateTime.Now; - - foreach (var requirement in requirements.Models) - { - if (requirement.Type == PolicyRequirementTypes.Time) - { - - var x = System.Text.Json.JsonSerializer.Deserialize(requirement.Requirement ?? ""); - - - - var start = TimeSpan.FromTicks(x.start); - var end = TimeSpan.FromTicks(x.end); - //var days = TimeSpan.FromTicks((requirement.Requirement as dynamic).days); - - bool valid = iamPolicy.PolicyRequirement(start, end); - } - else if (requirement.Type == PolicyRequirementTypes.TFA) - { - - } - else if (requirement.Type == PolicyRequirementTypes.PasswordTime) - { - - } - } - + // Validar. + (bool isValid, string message) = policyService.Validate(requirements.Models); // Respuesta. - return new(have ? Responses.Success : Responses.Unauthorized); + return new(isValid ? Responses.Success : Responses.Unauthorized) + { + Message = message + }; } catch (Exception) diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 7113429..d18af82 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -12,6 +12,7 @@ + diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 7083373..453e22d 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -34,6 +34,7 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic // Allow. services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; diff --git a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs new file mode 100644 index 0000000..09c830a --- /dev/null +++ b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs @@ -0,0 +1,122 @@ +using Newtonsoft.Json.Linq; + +namespace LIN.Cloud.Identity.Services.Utils; + +public class PolicyService(IamPolicy iamPolicy) +{ + + + + public (bool valid, string message) Validate(IEnumerable requirements) + { + + bool isValid = Time(requirements); + + if (!isValid) + return (false, "No tienes acceso a la política por la hora actual."); + + // Validar contaseña. + isValid = PasswordTime(requirements); + + if (!isValid) + return (false, "No tienes acceso a la política devido a que no has cambiado la contraseña en los últimos N dias."); + + isValid = TFA(requirements); + + if (!isValid) + return (false, "No tienes acceso a la política debido a que no tienes el doble factor de autenticación"); + + return (true, string.Empty); + + } + + + bool Time(IEnumerable requirements) + { + + // Solo de tiempo. + requirements = requirements.Where(x => x.Type == PolicyRequirementTypes.Time); + + // Si no hay políticas de tiempo. + if (!requirements.Any()) + return true; + + // Validar los requerimientos de tiempo. + foreach (var requirement in requirements) + { + + // Objeto. + JObject jsonObject = JObject.Parse(requirement.Requirement ?? ""); + + // Obtener los ticks. + var startTicks = Convert.ToInt64(jsonObject["start"]); + var endTicks = Convert.ToInt64(jsonObject["end"]); + + // Parsear a tiempo. + var start = TimeSpan.FromTicks(startTicks); + var end = TimeSpan.FromTicks(endTicks); + + // var days = TimeSpan.FromTicks((requirement.Requirement as dynamic).days); + + bool valid = iamPolicy.PolicyRequirement(start, end); + + if (valid) + return true; + + + } + + return false; + } + + + bool PasswordTime(IEnumerable requirements) + { + + // Solo de tiempo. + requirements = requirements.Where(x => x.Type == PolicyRequirementTypes.PasswordTime); + + // Si no hay políticas de tiempo. + if (!requirements.Any()) + return true; + + // Validar los requerimientos de tiempo. + foreach (var requirement in requirements) + { + + // Objeto. + JObject jsonObject = JObject.Parse(requirement.Requirement ?? ""); + + // Obtener los ticks. + var days = Convert.ToInt64(jsonObject["days"]); + + + // Dias. + if (days <= 30) + { + return true; + } + + } + + return false; + } + + + bool TFA(IEnumerable requirements) + { + + // Solo de tiempo. + requirements = requirements.Where(x => x.Type == PolicyRequirementTypes.TFA); + + // Si no hay políticas de tiempo. + if (!requirements.Any()) + return true; + + // Validar doble facto. + return false; + } + + + +} \ No newline at end of file From e9ede202562fa4def8021d12471cc2b516817fd1 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Thu, 24 Oct 2024 20:54:43 -0500 Subject: [PATCH 127/178] mejoras --- .../Areas/Accounts/AccountController.cs | 24 +++++++++---------- .../Areas/Accounts/AccountLogs.cs | 2 +- .../AuthenticationController.cs | 2 +- .../Areas/Authentication/IntentsController.cs | 4 ++-- .../Areas/AuthenticationBaseController.cs | 2 +- .../Areas/Directories/DirectoryController.cs | 4 ++-- .../Areas/Groups/GroupsController.cs | 8 +++---- .../Areas/Groups/GroupsMembersController.cs | 14 +++++------ .../Areas/Organizations/IdentityController.cs | 6 ++--- .../Organizations/OrganizationController.cs | 4 ++-- .../OrganizationMembersController.cs | 10 ++++---- .../Policies/PoliciesComplacentController.cs | 10 ++++---- .../Areas/Policies/PoliciesController.cs | 10 ++++---- .../Areas/Policies/PolicyRequirements.cs | 4 +--- .../Data/PoliciesRequirement.cs | 3 +-- .../Services/Auth/JwtService.cs | 2 +- .../Services/Extensions/LocalServices.cs | 1 - .../Filters/IdentityTokenAttribute.cs | 12 ++++++++-- .../Services/Formats/Account.cs | 3 --- .../Services/Formats/Identities.cs | 5 ---- 20 files changed, 63 insertions(+), 67 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 85a2992..3de9f06 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -80,9 +80,9 @@ public async Task> Read([FromQuery] int id) // Obtiene el usuario. var response = await accountData.Read(id, new() { - AccountContext = AuthenticationInformation.AccountId, + AccountContext = UserInformation.AccountId, FindOn = FindOn.StableAccounts, - IdentityContext = AuthenticationInformation.IdentityId, + IdentityContext = UserInformation.IdentityId, IsAdmin = false }); @@ -119,9 +119,9 @@ public async Task> Read([FromQuery] string use // Obtiene el usuario. var response = await accountData.Read(user, new() { - AccountContext = AuthenticationInformation.AccountId, + AccountContext = UserInformation.AccountId, FindOn = FindOn.StableAccounts, - IdentityContext = AuthenticationInformation.IdentityId, + IdentityContext = UserInformation.IdentityId, IsAdmin = false }); @@ -159,9 +159,9 @@ public async Task> ReadByIdentity([FromQuery] // Obtiene el usuario. var response = await accountData.ReadByIdentity(id, new() { - AccountContext = AuthenticationInformation.AccountId, + AccountContext = UserInformation.AccountId, FindOn = FindOn.StableAccounts, - IdentityContext = AuthenticationInformation.IdentityId, + IdentityContext = UserInformation.IdentityId, IsAdmin = false }); @@ -190,10 +190,10 @@ public async Task> ReadByIdentity([FromBody] L // Obtiene el usuario var response = await accountData.FindAllByIdentities(ids, new() { - AccountContext = AuthenticationInformation.AccountId, + AccountContext = UserInformation.AccountId, FindOn = FindOn.StableAccounts, IsAdmin = false, - IdentityContext = AuthenticationInformation.IdentityId, + IdentityContext = UserInformation.IdentityId, }); return response; @@ -220,10 +220,10 @@ public async Task> Search([FromQuery] string p // Obtiene el usuario var response = await accountData.Search(pattern, new() { - AccountContext = AuthenticationInformation.AccountId, + AccountContext = UserInformation.AccountId, FindOn = FindOn.StableAccounts, IsAdmin = false, - IdentityContext = AuthenticationInformation.IdentityId, + IdentityContext = UserInformation.IdentityId, }); return response; @@ -242,10 +242,10 @@ public async Task> ReadAll([FromBody] List> ReadAll() { // Obtiene el usuario - var response = await accountData.ReadAll(AuthenticationInformation.AccountId); + var response = await accountData.ReadAll(UserInformation.AccountId); return response; } diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index e171eca..8b93acc 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -90,7 +90,7 @@ public async Task> Login([FromQuery] string us public async Task> LoginWithToken() { // Obtiene el usuario. - var response = await accountData.Read(AuthenticationInformation.AccountId, new QueryAccountFilter() + var response = await accountData.Read(UserInformation.AccountId, new QueryAccountFilter() { IsAdmin = true, FindOn = FindOn.StableAccounts diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 2b05bfb..41fb103 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -17,7 +17,7 @@ public HttpReadAllResponse GetAll() { // Cuenta var account = (from a in PassKeyHub.Attempts - where a.Key.Equals(AuthenticationInformation.Unique, StringComparison.CurrentCultureIgnoreCase) + where a.Key.Equals(UserInformation.Unique, StringComparison.CurrentCultureIgnoreCase) select a).FirstOrDefault().Value ?? []; // Hora actual @@ -49,7 +49,7 @@ where I.Expiración > timeNow public async Task> Count() { // Contar. - var countResponse = await passkeyData.Count(AuthenticationInformation.AccountId); + var countResponse = await passkeyData.Count(UserInformation.AccountId); // Retorna return countResponse; diff --git a/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs b/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs index b1e4fc6..d2df135 100644 --- a/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs +++ b/LIN.Cloud.Identity/Areas/AuthenticationBaseController.cs @@ -6,7 +6,7 @@ public class AuthenticationBaseController : ControllerBase /// /// Información de autenticación. /// - public JwtModel AuthenticationInformation => HttpContext.Items["authentication"] as JwtModel ?? new(); + public JwtModel UserInformation => HttpContext.Items["authentication"] as JwtModel ?? new(); /// diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index d7f9848..923a89c 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -22,7 +22,7 @@ public async Task> ReadAll([FromHeader] int org }; // Obtiene el usuario. - var response = await directoryMembersData.Read(AuthenticationInformation.IdentityId, organization); + var response = await directoryMembersData.Read(UserInformation.IdentityId, organization); // Si es erróneo if (response.Response != Responses.Success) @@ -58,7 +58,7 @@ public async Task> ReadMembers([FromQuery] int // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index 4f831f4..b1decd8 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -14,7 +14,7 @@ public async Task Create([FromBody] GroupModel group) { // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, group.OwnerId ?? 0); + var roles = await rolesIam.Validate(UserInformation.IdentityId, group.OwnerId ?? 0); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -60,7 +60,7 @@ public async Task Create([FromBody] GroupModel group) public async Task> ReadAll([FromHeader] int organization) { // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(UserInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -110,7 +110,7 @@ public async Task> ReadOne([FromHeader] int id) }; // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -160,7 +160,7 @@ public async Task> ReadIdentity([FromHeader] int }; // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index 5be4661..66a5bfe 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -26,7 +26,7 @@ public async Task Create([FromBody] GroupMember model) }; // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -82,7 +82,7 @@ public async Task Create([FromHeader] int group, [FromBody] // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -142,7 +142,7 @@ public async Task> ReadMembers([FromQuery] int }; // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -192,7 +192,7 @@ public async Task> Search([FromHeader] int gr }; // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -245,7 +245,7 @@ public async Task> SearchOnGroups([FromHeader // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -295,7 +295,7 @@ public async Task DeleteMembers([FromQuery] int identity, [Fro }; // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, orgId.Model); + var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -333,7 +333,7 @@ public async Task> OnMembers([FromQuery] int org { // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(UserInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index b4638e6..9c77481 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -14,7 +14,7 @@ public async Task Create([FromBody] IdentityRolesModel rolMode { // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, rolModel.OrganizationId); + var roles = await rolesIam.Validate(UserInformation.IdentityId, rolModel.OrganizationId); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -68,7 +68,7 @@ public async Task> ReadAll([FromHeader] { // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(UserInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -117,7 +117,7 @@ public async Task ReadAll([FromHeader] int identity, [FromHead { // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(UserInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 12fba88..f4aa3ca 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -79,7 +79,7 @@ public async Task> ReadOneByID([FromQuery if (!response.Model.IsPublic) { - var iamIn = await directoryMembersData.IamIn(AuthenticationInformation.IdentityId, response.Model.Id); + var iamIn = await directoryMembersData.IamIn(UserInformation.IdentityId, response.Model.Id); if (iamIn.Response != Responses.Success) return new ReadOneResponse() @@ -113,7 +113,7 @@ public async Task> ReadAll() { // Obtiene la organización - var response = await organizationsData.ReadAll(AuthenticationInformation.IdentityId); + var response = await organizationsData.ReadAll(UserInformation.IdentityId); return response; diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index 8eff39f..d484f69 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -18,7 +18,7 @@ public async Task AddExternalMembers([FromQuery] int organiz { // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(UserInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateInviteMembers(roles); @@ -107,7 +107,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr modelo.Identity.Unique = $"{modelo.Identity.Unique}@{orgIdentity.Model.Unique}"; // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(UserInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); @@ -116,7 +116,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr if (!iam) return new() { - Message = "No tienes acceso para crear cuentas en esta organización.", + Message = "No tienes acceso para crear nuevos usuarios en esta organización.", Response = Responses.Unauthorized }; @@ -146,7 +146,7 @@ public async Task>> ReadAll([FromH { // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(UserInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateRead(roles); @@ -187,7 +187,7 @@ public async Task Expulse([FromQuery] int organization, [FromB { // Confirmar el rol. - var roles = await rolesIam.Validate(AuthenticationInformation.IdentityId, organization); + var roles = await rolesIam.Validate(UserInformation.IdentityId, organization); // Iam. bool iam = ValidateRoles.ValidateDelete(roles); diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs index ec5d848..1d5a131 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs @@ -10,9 +10,9 @@ public class PoliciesComplacentController(Data.Policies policiesData, IamRoles i public async Task Applicants([FromHeader] int identity) { - if (identity != AuthenticationInformation.IdentityId) + if (identity != UserInformation.IdentityId) { - var iamResult = await iam.IamIdentity(AuthenticationInformation.IdentityId, identity); + var iamResult = await iam.IamIdentity(UserInformation.IdentityId, identity); if (iamResult != Types.Enumerations.IamLevels.Privileged) return new ResponseBase(Responses.Unauthorized) { @@ -35,7 +35,7 @@ public async Task AddMember([FromQuery] string policy, [FromHe { // Validar Iam. - var iamResult = await iamPolicy.Validate(AuthenticationInformation.IdentityId, policy); + var iamResult = await iamPolicy.Validate(UserInformation.IdentityId, policy); if (iamResult != Types.Enumerations.IamLevels.Privileged) return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para agregar integrantes a la política." }; @@ -57,7 +57,7 @@ public async Task DeleteMember([FromQuery] string policy, [Fro { // Validar Iam. - var iamResult = await iamPolicy.Validate(AuthenticationInformation.IdentityId, policy); + var iamResult = await iamPolicy.Validate(UserInformation.IdentityId, policy); if (iamResult != Types.Enumerations.IamLevels.Privileged) return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para eliminar integrantes de la política." }; @@ -76,7 +76,7 @@ public async Task DeleteMember([FromQuery] string policy, [Fro [HttpGet] public async Task IsAllow([FromQuery] string policy) { - var response = await policiesData.HasFor(AuthenticationInformation.IdentityId, policy); + var response = await policiesData.HasFor(UserInformation.IdentityId, policy); return response; } diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index a7e44bf..aa92be1 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -23,7 +23,7 @@ public async Task Create([FromBody] PolicyModel modelo, [Fro return new(Responses.NotRows) { Message = $"No se encontró la organización del grupo con identidad {modelo.OwnerIdentityId}" }; // Validar roles. - var roles = await iam.Validate(AuthenticationInformation.IdentityId, owner.Model); + var roles = await iam.Validate(UserInformation.IdentityId, owner.Model); bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); @@ -34,7 +34,7 @@ public async Task Create([FromBody] PolicyModel modelo, [Fro else if (organization is not null && organization > 0) { // Validar roles. - var roles = await iam.Validate(AuthenticationInformation.IdentityId, organization.Value); + var roles = await iam.Validate(UserInformation.IdentityId, organization.Value); bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); @@ -48,7 +48,7 @@ public async Task Create([FromBody] PolicyModel modelo, [Fro else { // Establecer propietario al usuario que realiza la solicitud. - modelo.OwnerIdentityId = AuthenticationInformation.IdentityId; + modelo.OwnerIdentityId = UserInformation.IdentityId; } // Formatear. @@ -76,7 +76,7 @@ public async Task Create([FromBody] PolicyModel modelo, [Fro [HttpGet("all")] public async Task> All() { - var response = await policiesData.ReadAllOwn(AuthenticationInformation.IdentityId); + var response = await policiesData.ReadAllOwn(UserInformation.IdentityId); return response; } @@ -101,7 +101,7 @@ public async Task Delete([FromQuery] string policy) { // Validar Iam. - var iamResult = await iamPolicy.Validate(AuthenticationInformation.IdentityId, policy); + var iamResult = await iamPolicy.Validate(UserInformation.IdentityId, policy); if (iamResult != Types.Enumerations.IamLevels.Privileged) return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para eliminar la política." }; diff --git a/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs b/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs index 2f2c6a0..5729ff9 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs @@ -14,7 +14,7 @@ public async Task Create([FromBody] PolicyRequirementModel m { // Validar roles. - var roles = await iamPolicy.Validate(AuthenticationInformation.IdentityId, modelo.PolicyId.ToString()); + var roles = await iamPolicy.Validate(UserInformation.IdentityId, modelo.PolicyId.ToString()); if (roles != IamLevels.Privileged) return new(Responses.Unauthorized) { Message = $"No tienes permisos c" }; @@ -24,6 +24,4 @@ public async Task Create([FromBody] PolicyRequirementModel m return response; } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/PoliciesRequirement.cs b/LIN.Cloud.Identity/Data/PoliciesRequirement.cs index 0da8f6c..a815a7c 100644 --- a/LIN.Cloud.Identity/Data/PoliciesRequirement.cs +++ b/LIN.Cloud.Identity/Data/PoliciesRequirement.cs @@ -16,8 +16,7 @@ public async Task Create(PolicyRequirementModel modelo) // Attach. context.Attach(modelo.Policy); - if (modelo.Requirement is not string) - modelo.Requirement = System.Text.Json.JsonSerializer.Serialize(modelo.Requirement); + modelo.Requirement ??= System.Text.Json.JsonSerializer.Serialize(modelo.Requirement); // Guardar la cuenta. await context.PolicyRequirements.AddAsync(modelo); diff --git a/LIN.Cloud.Identity/Services/Auth/JwtService.cs b/LIN.Cloud.Identity/Services/Auth/JwtService.cs index fe4ebd2..30fb284 100644 --- a/LIN.Cloud.Identity/Services/Auth/JwtService.cs +++ b/LIN.Cloud.Identity/Services/Auth/JwtService.cs @@ -70,7 +70,7 @@ public static JwtModel Validate(string token) IsAuthenticated = false }; - // Configurar la clave secreta + // Configurar la clave secreta. var key = Encoding.ASCII.GetBytes(JwtKey); // Validar el token diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 453e22d..dc6fe21 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -3,7 +3,6 @@ namespace LIN.Cloud.Identity.Services.Extensions; - public static class LocalServices { diff --git a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs index cb39dc8..4619476 100644 --- a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs +++ b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs @@ -1,4 +1,6 @@ -namespace LIN.Cloud.Identity.Services.Filters; +using LIN.Types.Models; + +namespace LIN.Cloud.Identity.Services.Filters; public class IdentityTokenAttribute : ActionFilterAttribute { @@ -27,13 +29,19 @@ public override async Task OnActionExecutionAsync(ActionExecutingContext context await httpContext.Response.WriteAsJsonAsync(new ResponseBase() { Message = "Token invalido.", + Errors = [ + new ErrorModel() + { + Tittle = "Token invalido", + Description = "El token proporcionado en el header es invalido." + } + ], Response = Responses.Unauthorized }); return; } // Agrega la información del token. - context.HttpContext.Items.Add(value.ToString(), tokenInfo); context.HttpContext.Items.Add("authentication", tokenInfo); await base.OnActionExecutionAsync(context, next); diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 005bfd8..65ba980 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -2,12 +2,9 @@ namespace LIN.Cloud.Identity.Services.Formats; - public class Account { - - /// /// Procesar el modelo. /// diff --git a/LIN.Cloud.Identity/Services/Formats/Identities.cs b/LIN.Cloud.Identity/Services/Formats/Identities.cs index 60fcf00..c9499b0 100644 --- a/LIN.Cloud.Identity/Services/Formats/Identities.cs +++ b/LIN.Cloud.Identity/Services/Formats/Identities.cs @@ -1,25 +1,20 @@ namespace LIN.Cloud.Identity.Services.Formats; - public class Identities { - /// /// Procesar el modelo. /// /// Modelo public static void Process(IdentityModel id) { - id.Id = 0; id.ExpirationTime = DateTime.Now.AddYears(10); id.EffectiveTime = DateTime.Now; id.CreationTime = DateTime.Now; id.Status = IdentityStatus.Enable; id.Unique = id.Unique.Trim(); - } - } \ No newline at end of file From db2e39dbc7bb8ed80d711ce057638ec33fe445de Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Thu, 24 Oct 2024 21:41:39 -0500 Subject: [PATCH 128/178] mejoras de seguiridad --- .../Contexts/DataContext.cs | 10 ++++---- .../Areas/Directories/DirectoryController.cs | 6 ++--- .../Areas/Organizations/IdentityController.cs | 2 +- LIN.Cloud.Identity/Data/DirectoryMembers.cs | 23 ++++++++++++++----- .../Data/PoliciesRequirement.cs | 3 --- LIN.Cloud.Identity/Program.cs | 1 - .../Services/Utils/PolicyService.cs | 2 +- 7 files changed, 26 insertions(+), 21 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index d0ec5fc..b8345c7 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -83,21 +83,21 @@ public class DataContext(DbContextOptions options) : DbContext(opti /// protected override void OnModelCreating(ModelBuilder modelBuilder) { - // Identity Model + // Identity Model. modelBuilder.Entity(entity => { entity.ToTable("identities"); entity.HasIndex(t => t.Unique).IsUnique(); }); - // Account Model + // Account Model. modelBuilder.Entity(entity => { entity.ToTable("accounts"); entity.HasIndex(t => t.IdentityId).IsUnique(); }); - // Organization Model + // Organization Model. modelBuilder.Entity(entity => { entity.ToTable("organizations"); @@ -106,8 +106,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(o => o.DirectoryId) .OnDelete(DeleteBehavior.NoAction); }); - - // Group Model + + // Group Model. modelBuilder.Entity(entity => { entity.ToTable("groups"); diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index 923a89c..1a8217d 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -24,14 +24,14 @@ public async Task> ReadAll([FromHeader] int org // Obtiene el usuario. var response = await directoryMembersData.Read(UserInformation.IdentityId, organization); - // Si es erróneo + // Si es erróneo. if (response.Response != Responses.Success) return new() { Response = response.Response }; - // Retorna el resultado + // Retorna el resultado. return response; } @@ -56,7 +56,6 @@ public async Task> ReadMembers([FromQuery] int Response = Responses.Unauthorized }; - // Confirmar el rol. var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); @@ -71,7 +70,6 @@ public async Task> ReadMembers([FromQuery] int Response = Responses.Unauthorized }; - // Obtiene el usuario. var response = await directoryMembersData.ReadMembers(directory); diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 9c77481..63d6276 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -77,7 +77,7 @@ public async Task> ReadAll([FromHeader] if (!iam) return new() { - Message = "No tienes acceso para leer grupos.", + Message = "No tienes acceso para leer permisos.", Response = Responses.Unauthorized }; diff --git a/LIN.Cloud.Identity/Data/DirectoryMembers.cs b/LIN.Cloud.Identity/Data/DirectoryMembers.cs index f988a22..049db84 100644 --- a/LIN.Cloud.Identity/Data/DirectoryMembers.cs +++ b/LIN.Cloud.Identity/Data/DirectoryMembers.cs @@ -66,7 +66,6 @@ on gm.GroupId equals o.DirectoryId /// /// Identidad /// Id de la organización - /// Contexto public async Task> IamIn(int id, int organization) { @@ -87,16 +86,28 @@ on org.DirectoryId equals gm.GroupId // Si la cuenta no existe. if (query == null) - return new() - { - Response = Responses.NotRows - }; + { + + var x = await (from A in context.Organizations + where A.Directory.IdentityId == id + && A.Id == organization + select A).AnyAsync(); + + + if (!x) + return new() + { + Response = Responses.NotRows + }; + + } + // Success. return new() { Response = Responses.Success, - Model = query.Type + Model = query?.Type ?? GroupMemberTypes.Group }; } diff --git a/LIN.Cloud.Identity/Data/PoliciesRequirement.cs b/LIN.Cloud.Identity/Data/PoliciesRequirement.cs index a815a7c..7d9c4da 100644 --- a/LIN.Cloud.Identity/Data/PoliciesRequirement.cs +++ b/LIN.Cloud.Identity/Data/PoliciesRequirement.cs @@ -48,9 +48,6 @@ public async Task> ReadAll(Guid policy) { // Políticas. - var qw = (from policyRequirement in context.PolicyRequirements - select policyRequirement).ToQueryString(); - var policies = await (from policyRequirement in context.PolicyRequirements where policyRequirement.PolicyId == policy select policyRequirement).Distinct().ToListAsync(); diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index ae6b72a..da8a9db 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -15,7 +15,6 @@ builder.Services.AddLocalServices(); - // Servicio de autenticación. builder.Services.AddScoped(); builder.Services.AddPersistence(builder.Configuration); diff --git a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs index 09c830a..eb689af 100644 --- a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs +++ b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs @@ -15,7 +15,7 @@ public class PolicyService(IamPolicy iamPolicy) if (!isValid) return (false, "No tienes acceso a la política por la hora actual."); - // Validar contaseña. + // Validar contraseña. isValid = PasswordTime(requirements); if (!isValid) From dda97a10ce430b264a934aa39b8dbee21f3cb777 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 26 Oct 2024 09:40:59 -0500 Subject: [PATCH 129/178] =?UTF-8?q?Correcci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contexts/DataContext.cs | 21 +++++ .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../Models/OtpDatabaseModel.cs | 11 +++ .../Authentication/SecurityController.cs | 60 +++++++++++++ LIN.Cloud.Identity/Data/OtpService.cs | 59 +++++++++++++ .../Data/PoliciesRequirement.cs | 86 +++++++++++++++++++ LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 3 +- .../Services/Extensions/LocalServices.cs | 2 + .../Services/Utils/EmailSender.cs | 24 ++++++ 9 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs create mode 100644 LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs create mode 100644 LIN.Cloud.Identity/Data/OtpService.cs create mode 100644 LIN.Cloud.Identity/Data/PoliciesRequirement.cs create mode 100644 LIN.Cloud.Identity/Services/Utils/EmailSender.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index be805b5..c3d1094 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -76,6 +76,18 @@ public class DataContext(DbContextOptions options) : DbContext(opti /// /// Generación del modelo de base de datos. /// + public DbSet PolicyRequirements { get; set; } + + + /// + /// Códigos OTPS. + /// + public DbSet OTPs { get; set; } + + + /// + /// Crear el modelo en BD. + /// protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -276,6 +288,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable("account_logs"); modelBuilder.Entity().ToTable("policies"); + // Códigos OTPS. + modelBuilder.Entity(entity => + { + entity.ToTable("otp_codes"); + entity.HasOne(t => t.Account) + .WithMany() + .HasForeignKey(t => t.AccountId); + }); + // Base. base.OnModelCreating(modelBuilder); } diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index b09ad7d..b87e7fc 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs b/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs new file mode 100644 index 0000000..3d20057 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs @@ -0,0 +1,11 @@ +namespace LIN.Cloud.Identity.Persistence.Models; + +public class OtpDatabaseModel +{ + public int Id { get; set; } + public string Code { get; set; } + public DateTime ExpireTime { get; set; } + public bool IsUsed { get; set; } + public LIN.Types.Cloud.Identity.Models.AccountModel Account { get; set; } + public int AccountId { get; set; } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs new file mode 100644 index 0000000..23737f5 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -0,0 +1,60 @@ +using Http.Attributes; +using LIN.Cloud.Identity.Persistence.Models; +using FindOn = LIN.Cloud.Identity.Services.Models.FindOn; + +namespace LIN.Cloud.Identity.Areas.Authentication; + +[Route("[controller]")] +public class SecurityController(Data.Accounts accountsData, Data.OtpService otpService) : AuthenticationBaseController +{ + + /// + /// Si el usuario olvidó su contraseña, puede solicitar un cambio de contraseña. + /// + /// Usuario. + [HttpPost("forget/password")] + [RateLimit(requestLimit: 2, timeWindowSeconds: 60, blockDurationSeconds: 100)] + public async Task ForgetPassword([FromQuery] string user) + { + + // Validar estado del usuario. + var account = await accountsData.Read(user, new() + { + FindOn = FindOn.StableAccounts, + IncludePhoto = false + }); + + if (account.Response != Responses.Success) + return new(account.Response) + { + Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." + }; + + // Generar OTP. + var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); + + // Guardar OTP. + var modelo = new OtpDatabaseModel + { + Account = account.Model, + AccountId = account.Model.Id, + Code = otpCode, + ExpireTime = DateTime.Now.AddMinutes(10), + IsUsed = false + }; + + var created = await otpService.Create(modelo); + + if (created.Response != Responses.Success) + return new(created.Response) + { + Message = "No se pudo crear el código de verificación." + }; + + // Enviar mail. + var ma = + + + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/OtpService.cs b/LIN.Cloud.Identity/Data/OtpService.cs new file mode 100644 index 0000000..2df6537 --- /dev/null +++ b/LIN.Cloud.Identity/Data/OtpService.cs @@ -0,0 +1,59 @@ +using LIN.Cloud.Identity.Persistence.Models; + +namespace LIN.Cloud.Identity.Data; + +public class OtpService(DataContext context) +{ + + public async Task Create(OtpDatabaseModel model) + { + + try + { + // Guardar OTP. + await context.OTPs.AddAsync(model); + context.SaveChanges(); + + return new(Responses.Success); + + } + catch (Exception) + { + } + return new(Responses.Undefined); + + } + + + public async Task ReadAndUpdate(int accountId, string code) + { + + try + { + + + var update = await (from A in context.OTPs + where A.AccountId == accountId + && A.Code == code + && A.ExpireTime > DateTime.Now + && A.IsUsed == false + select A).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsUsed, true)); + + + if (update <= 0) + return new(Responses.NotRows); + + + return new(Responses.Success); + + } + catch (Exception) + { + } + return new(Responses.Undefined); + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/PoliciesRequirement.cs b/LIN.Cloud.Identity/Data/PoliciesRequirement.cs new file mode 100644 index 0000000..7d9c4da --- /dev/null +++ b/LIN.Cloud.Identity/Data/PoliciesRequirement.cs @@ -0,0 +1,86 @@ +namespace LIN.Cloud.Identity.Data; + +public class PoliciesRequirement(DataContext context) +{ + + public async Task Create(PolicyRequirementModel modelo) + { + try + { + + modelo.Policy = new() + { + Id = modelo.PolicyId + }; + + // Attach. + context.Attach(modelo.Policy); + + modelo.Requirement ??= System.Text.Json.JsonSerializer.Serialize(modelo.Requirement); + + // Guardar la cuenta. + await context.PolicyRequirements.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + LastUnique = modelo.Id.ToString() + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ResourceExist + }; + } + + } + + + public async Task> ReadAll(Guid policy) + { + + // Ejecución + try + { + + // Políticas. + var policies = await (from policyRequirement in context.PolicyRequirements + where policyRequirement.PolicyId == policy + select policyRequirement).Distinct().ToListAsync(); + + return new(Responses.Success, policies); + + } + catch (Exception) + { + } + return new(); + } + + + public async Task Remove(int policyRequirement) + { + + // Ejecución + try + { + + var deleted = await (from policy in context.PolicyRequirements + where policy.Id == policyRequirement + select policy).ExecuteDeleteAsync(); + + // Respuesta. + return new(Responses.Success); + + } + catch (Exception) + { + } + return new(); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 50feb18..28525b1 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,7 +9,8 @@ - + + diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index ba3e759..ee5343e 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -24,6 +24,8 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); // Iam. services.AddScoped(); diff --git a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs new file mode 100644 index 0000000..3562686 --- /dev/null +++ b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs @@ -0,0 +1,24 @@ +namespace LIN.Cloud.Identity.Services.Utils; + +public class EmailSender +{ + + public async Task Send(string body) + { + Global.Http.Services.Client client = new("https://hangfire.linplatform.com/api/mailsender") + { + TimeOut = 10 + }; + + client.AddParameter("subject", ""); + client.AddParameter("mail", ""); + + var aa = await client.Post(body); + + + return true; + + + } + +} \ No newline at end of file From b669ead0d5f2456cde15b48893a8fd7e17a46c76 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 26 Oct 2024 13:03:37 -0500 Subject: [PATCH 130/178] Mejoras de seguridad --- .../Contexts/DataContext.cs | 53 ++++++- .../Models/MailOtpDatabaseModel.cs | 11 ++ .../Authentication/SecurityController.cs | 138 +++++++++++++++++- LIN.Cloud.Identity/Data/Accounts.cs | 30 ++++ LIN.Cloud.Identity/Data/Mails.cs | 136 +++++++++++++++++ LIN.Cloud.Identity/Data/OtpService.cs | 35 ++++- .../Services/Extensions/LocalServices.cs | 4 + .../Services/Utils/EmailSender.cs | 8 +- 8 files changed, 399 insertions(+), 16 deletions(-) create mode 100644 LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs create mode 100644 LIN.Cloud.Identity/Data/Mails.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 35f6a1f..2db89c9 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -1,4 +1,5 @@ -using LIN.Types.Cloud.Identity.Models; +using LIN.Cloud.Identity.Persistence.Models; +using LIN.Types.Cloud.Identity.Models; using Microsoft.EntityFrameworkCore; namespace LIN.Cloud.Identity.Persistence.Contexts; @@ -79,15 +80,21 @@ public class DataContext(DbContextOptions options) : DbContext(opti /// - /// Crear el modelo en BD. + /// Códigos OTPS. /// - public DbSet PolicyRequirements { get; set; } + public DbSet OTPs { get; set; } - /// - /// Códigos OTPS. - /// - public DbSet OTPs { get; set; } + /// + /// Correos asociados a las cuentas. + /// + public DbSet Mails { get; set; } + + + /// + /// Mail Otp. + /// + public DbSet MailOtp { get; set; } /// @@ -118,7 +125,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(o => o.DirectoryId) .OnDelete(DeleteBehavior.NoAction); }); - + // Group Model. modelBuilder.Entity(entity => { @@ -261,6 +268,36 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(t => t.AccountId); }); + // Correos. + modelBuilder.Entity(entity => + { + entity.ToTable("mails"); + entity.HasOne(t => t.Account) + .WithMany() + .HasForeignKey(t => t.AccountId); + + entity.HasIndex(t => t.Mail).IsUnique(); + }); + + // Mail OTP. + modelBuilder.Entity(entity => + { + entity.ToTable("mail_otp"); + + entity.HasOne(t => t.MailModel) + .WithMany() + .HasForeignKey(t => t.MailId) + .OnDelete(DeleteBehavior.NoAction); + + entity.HasOne(t => t.OtpDatabaseModel) + .WithMany() + .HasForeignKey(t => t.OtpId) + .OnDelete(DeleteBehavior.NoAction); + + entity.HasKey(t => new { t.MailId, t.OtpId }); + + }); + // Base. base.OnModelCreating(modelBuilder); } diff --git a/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs b/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs new file mode 100644 index 0000000..3e7431c --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs @@ -0,0 +1,11 @@ +using LIN.Types.Cloud.Identity.Models; + +namespace LIN.Cloud.Identity.Persistence.Models; + +public class MailOtpDatabaseModel +{ + public MailModel MailModel { get; set; } + public OtpDatabaseModel OtpDatabaseModel { get; set; } + public int MailId { get; set; } + public int OtpId { get; set; } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index 23737f5..54f47e3 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -1,13 +1,69 @@ using Http.Attributes; using LIN.Cloud.Identity.Persistence.Models; +using LIN.Cloud.Identity.Services.Utils; using FindOn = LIN.Cloud.Identity.Services.Models.FindOn; namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class SecurityController(Data.Accounts accountsData, Data.OtpService otpService) : AuthenticationBaseController +public class SecurityController(Data.Accounts accountsData, Data.OtpService otpService, EmailSender emailSender, Data.Mails mails) : AuthenticationBaseController { + + /// + /// Si el usuario olvidó su contraseña, puede solicitar un cambio de contraseña. + /// + /// Usuario. + [HttpPost("mail")] + [IdentityToken] + public async Task AddMail([FromQuery] string email) + { + + var model = new MailModel() + { + Mail = email, + AccountId = UserInformation.AccountId, + IsPrincipal = false, + IsVerified = false + }; + + var responseCreate = await mails.Create(model); + + if (responseCreate.Response != Responses.Success) + return new(responseCreate.Response) + { + Message = "No se pudo agregar el correo." + }; + + // Generar Otp. + var x = Global.Utilities.KeyGenerator.GenerateOTP(5); + + //Guardar OTP. + var xxx = await otpService.Create(new MailOtpDatabaseModel + { + MailModel = responseCreate.Model, + OtpDatabaseModel = new() + { + Account = new() { Id = UserInformation.AccountId }, + Code = x, + ExpireTime = DateTime.Now.AddMinutes(10), + IsUsed = false + } + }); + + // Enviar correo de verificación. + if (xxx.Response != Responses.Success) + return new(); + + + // Coreeo + var ma = await emailSender.Send(email, "Verficar correo", $"Verificar tu correo {x}"); + + return new(ma ? Responses.Success : Responses.UnavailableService); + + } + + /// /// Si el usuario olvidó su contraseña, puede solicitar un cambio de contraseña. /// @@ -30,6 +86,16 @@ public async Task ForgetPassword([FromQuery] string user) Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." }; + // Obtener mail principal. + var mail = await mails.ReadPrincipal(user); + + if (mail.Response != Responses.Success) + return new(mail.Response) + { + Message = "Esta cuenta no tiene un correo principal establecido." + }; + + // Generar OTP. var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); @@ -52,9 +118,77 @@ public async Task ForgetPassword([FromQuery] string user) }; // Enviar mail. - var ma = + var ma = await emailSender.Send(mail.Model.Mail, "Recuperación de contraseña", $"Su código de verificación es: {otpCode}"); + + return new(ma ? Responses.Success : Responses.UnavailableService); + + } + + + + [HttpPost("validate")] + public async Task Validate([FromQuery] string mail, [FromQuery] string code) + { + + + var response = await mails.ValidateOtpFormail(mail, code); + + return response; } + + + [HttpPost("reset")] + public async Task Reset([FromQuery] string code, [FromQuery] string unique, [FromQuery] string newPassword) + { + + + if (newPassword is null || newPassword.Length <= 0) + return new(Responses.InvalidParam) + { + Errors = [] + }; + + // Validar OTP. + var account = await accountsData.Read(unique, new() + { + FindOn = FindOn.StableAccounts, + IncludePhoto = false + }); + + if (account.Response != Responses.Success) + return new(account.Response) + { + Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." + }; + + + var response = await otpService.ReadAndUpdate(account.Model.Id, code); + + if (response.Response != Responses.Success) + return new(response.Response) + { + Message = "No se pudo reestablecer la contraseña." + }; + + + + // Encriptar contraseña. + newPassword = Global.Utilities.Cryptography.Encrypt(newPassword); + + + var up = await accountsData.UpdatePassword(account.Model.Id, newPassword); + + return up; + + + } + + + + + + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index c723f91..967fad2 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -287,4 +287,34 @@ public async Task> FindAllByIdentities(List i } + /// + /// Actualizar contraseña de una cuenta. + /// + /// Id de la cuenta. + /// Nueva contraseña. + public async Task UpdatePassword(int accountId, string password) + { + + try + { + var account = await (from a in context.Accounts + where a.Id == accountId + select a).ExecuteUpdateAsync(Accounts => Accounts.SetProperty(t=>t.Password, password)); + + if (account <= 0) + return new(Responses.NotExistAccount); + + return new(Responses.Success); + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + + } + + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Mails.cs b/LIN.Cloud.Identity/Data/Mails.cs new file mode 100644 index 0000000..9c415b4 --- /dev/null +++ b/LIN.Cloud.Identity/Data/Mails.cs @@ -0,0 +1,136 @@ +namespace LIN.Cloud.Identity.Data; + +public class Mails(DataContext context) +{ + + public async Task> Create(MailModel modelo) + { + try + { + + modelo.Id = 0; + modelo.Account = new() + { + Id = modelo.AccountId, + }; + + // Attach. + context.Attach(modelo.Account); + + // Guardar la cuenta. + await context.Mails.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + Model = modelo + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.ResourceExist + }; + } + + } + + + public async Task> ReadPrincipal(string unique) + { + try + { + + var x = await (from mail in context.Mails + join account in context.Accounts + on mail.Account.IdentityId equals account.IdentityId + where account.Identity.Unique == unique + where mail.IsPrincipal + && mail.IsVerified + select mail).FirstOrDefaultAsync(); + + if (x is null) + return new() + { + Response = Responses.NotRows + }; + + + return new() + { + Response = Responses.Success, + Model = x + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + + } + + + public async Task ValidateOtpFormail(string email, string code) + { + try + { + + var x = (from mail in context.Mails + join otp in context.MailOtp + on mail.Id equals otp.MailId + where otp.OtpDatabaseModel.Code == code + && otp.OtpDatabaseModel.IsUsed == false + && otp.OtpDatabaseModel.ExpireTime > DateTime.Now + select otp); + + + var xx = await x.Select(t => t.MailModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsVerified, true)); + var xx2 = await x.Select(t=>t.OtpDatabaseModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsUsed, true)); + + + if (xx2 <= 0) + return new() + { + Response = Responses.NotRows + }; + + + var acf =await (from mail in context.Mails + select mail.AccountId).FirstOrDefaultAsync(); + + + var exist = await (from m in context.Mails + where m.AccountId == acf + && m.IsPrincipal + select m).AnyAsync(); + + if (!exist) + await x.Select(t => t.MailModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsPrincipal, true)); + + + return new() + { + Response = Responses.Success + }; + + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + + } + + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/OtpService.cs b/LIN.Cloud.Identity/Data/OtpService.cs index 2df6537..908c330 100644 --- a/LIN.Cloud.Identity/Data/OtpService.cs +++ b/LIN.Cloud.Identity/Data/OtpService.cs @@ -10,6 +10,8 @@ public async Task Create(OtpDatabaseModel model) try { + model.Account = context.AttachOrUpdate(model.Account); + // Guardar OTP. await context.OTPs.AddAsync(model); context.SaveChanges(); @@ -25,7 +27,7 @@ public async Task Create(OtpDatabaseModel model) } - public async Task ReadAndUpdate(int accountId, string code) + public async Task ReadAndUpdate(int accountId, string code) { try @@ -56,4 +58,35 @@ public async Task ReadAndUpdate(int accountId, string code) + public async Task Create(MailOtpDatabaseModel model) + { + + try + { + + // A + model.OtpDatabaseModel.Account = context.AttachOrUpdate(model.OtpDatabaseModel.Account); + + model.MailModel = new() + { + Id = model.MailModel.Id + }; + model.MailModel = context.AttachOrUpdate(model.MailModel); + + // Guardar OTP. + await context.MailOtp.AddAsync(model); + context.SaveChanges(); + + return new(Responses.Success); + + } + catch (Exception) + { + } + return new(Responses.Undefined); + + } + + + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index c01e2c5..84a42b1 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -26,6 +26,10 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + + // Externos + services.AddSingleton(); // Iam. services.AddScoped(); diff --git a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs index 3562686..ea16aae 100644 --- a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs +++ b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs @@ -3,22 +3,20 @@ public class EmailSender { - public async Task Send(string body) + public async Task Send(string to, string subject, string body) { Global.Http.Services.Client client = new("https://hangfire.linplatform.com/api/mailsender") { TimeOut = 10 }; - client.AddParameter("subject", ""); - client.AddParameter("mail", ""); + client.AddParameter("subject", subject); + client.AddParameter("mail", to); var aa = await client.Post(body); return true; - - } } \ No newline at end of file From 63b319b541953252836423a44108bc955a88d484 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 26 Oct 2024 13:35:14 -0500 Subject: [PATCH 131/178] =?UTF-8?q?Refactorizaci=C3=B3n=20y=20correcci?= =?UTF-8?q?=C3=B3n=20de=20filtros=20y=20validaciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cambiada importación de `LIN.Cloud.Identity.Persistence.Models`. - Corregido nombre del filtro de `QueryAccountFilter` a `QueryObjectFilter`. - Modificada validación de modelo en `AccountController.cs`. - Eliminada llave de cierre innecesaria en `AllowAppsController.cs`. - Mejorado manejo de errores y mensajes en `SecurityController.cs`. - Eliminado sufijo `create` de la ruta en `OrganizationController.cs`. - Agregada variable `ex` en bloque `catch` en `Mails.cs`. - Actualizadas pruebas unitarias en `Accounts.cs`. - Eliminado archivo `QueryAccountFilter.cs`. - Documentado método `Send` en `EmailSender.cs`. --- .../Queries/AccountFindable.cs | 5 +- .../Areas/Accounts/AccountController.cs | 2 +- .../Authentication/AllowAppsController.cs | 1 - .../AuthenticationController.cs | 2 +- .../Authentication/SecurityController.cs | 139 +++++++++++------- .../Organizations/OrganizationController.cs | 2 +- LIN.Cloud.Identity/Data/Accounts.cs | 12 +- LIN.Cloud.Identity/Data/Builders/Account.cs | 20 +-- .../Data/Builders/Identities.cs | 6 +- LIN.Cloud.Identity/Data/Mails.cs | 9 +- LIN.Cloud.Identity/Data/Organizations.cs | 4 +- LIN.Cloud.Identity/Program.cs | 1 - .../Services/Formats/Account.cs | 1 + .../Services/Models/QueryAccountFilter.cs | 18 --- .../Services/Utils/EmailSender.cs | 10 +- LIN.Cloud.Identity/Usings.cs | 5 +- LIN.Identity.Tests/Data/Accounts.cs | 23 +-- 17 files changed, 144 insertions(+), 116 deletions(-) delete mode 100644 LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs diff --git a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs index e23a34a..c624cab 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs @@ -1,4 +1,5 @@ -using LIN.Cloud.Identity.Persistence.Queries.Interfaces; +using LIN.Cloud.Identity.Persistence.Models; +using LIN.Cloud.Identity.Persistence.Queries.Interfaces; using LIN.Types.Cloud.Identity.Enumerations; using LIN.Types.Cloud.Identity.Models; @@ -50,7 +51,7 @@ private IQueryable OnAll() - public IQueryable GetAccounts(int id, Models.QueryObjectFilter filters) + public IQueryable GetAccounts(int id, QueryObjectFilter filters) { // Query general diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 3de9f06..9b51a51 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -14,7 +14,7 @@ public async Task Create([FromBody] AccountModel? modelo) { // Validaciones del modelo. - if (modelo == null || modelo.Identity == null || modelo.Password.Length < 4 || modelo.Name.Length <= 0 || modelo.Identity.Unique.Length <= 0) + if (modelo is null || modelo.Identity is null || modelo.Password.Length < 4 || modelo.Name.Length <= 0 || modelo.Identity.Unique.Length <= 0) return new(Responses.InvalidParam) { Message = "Uno o varios parámetros son inválidos." diff --git a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs index 40d383f..9af3481 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs @@ -3,5 +3,4 @@ [Route("applications/allow")] public class AllowAppsController : ControllerBase { - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 8b93acc..a493837 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -90,7 +90,7 @@ public async Task> Login([FromQuery] string us public async Task> LoginWithToken() { // Obtiene el usuario. - var response = await accountData.Read(UserInformation.AccountId, new QueryAccountFilter() + var response = await accountData.Read(UserInformation.AccountId, new QueryObjectFilter() { IsAdmin = true, FindOn = FindOn.StableAccounts diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index 54f47e3..a033fcb 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -1,24 +1,18 @@ -using Http.Attributes; -using LIN.Cloud.Identity.Persistence.Models; -using LIN.Cloud.Identity.Services.Utils; -using FindOn = LIN.Cloud.Identity.Services.Models.FindOn; - -namespace LIN.Cloud.Identity.Areas.Authentication; +namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] public class SecurityController(Data.Accounts accountsData, Data.OtpService otpService, EmailSender emailSender, Data.Mails mails) : AuthenticationBaseController { - /// - /// Si el usuario olvidó su contraseña, puede solicitar un cambio de contraseña. + /// Agregar un correo a una cuenta. /// - /// Usuario. + /// Correo. [HttpPost("mail")] [IdentityToken] public async Task AddMail([FromQuery] string email) { - + // Generar modelo del correo. var model = new MailModel() { Mail = email, @@ -27,47 +21,84 @@ public async Task AddMail([FromQuery] string email) IsVerified = false }; + // Respuesta. var responseCreate = await mails.Create(model); - if (responseCreate.Response != Responses.Success) - return new(responseCreate.Response) - { - Message = "No se pudo agregar el correo." - }; + // Si hubo un error. + switch (responseCreate.Response) + { + // Correcto. + case Responses.Success: + break; + + // Ya estaba registrado. + case Responses.ResourceExist: + return new(responseCreate.Response) + { + Message = $"Hubo un error al agregar el correo <{email}> a la cuenta {model.Account.Identity.Unique}", + Errors = [ + new() { + Tittle = "Mail duplicado", + Description = "El correo ya se encuentra registrado en el sistema." + } + ] + }; + default: + return new(responseCreate.Response) + { + Message = $"Hubo un error al agregar el correo <{email}> a la cuenta {model.Account.Identity.Unique}" + }; + } // Generar Otp. - var x = Global.Utilities.KeyGenerator.GenerateOTP(5); + var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); //Guardar OTP. - var xxx = await otpService.Create(new MailOtpDatabaseModel + var otpCreateResponse = await otpService.Create(new MailOtpDatabaseModel { MailModel = responseCreate.Model, OtpDatabaseModel = new() { Account = new() { Id = UserInformation.AccountId }, - Code = x, + Code = otpCode, ExpireTime = DateTime.Now.AddMinutes(10), IsUsed = false } }); // Enviar correo de verificación. - if (xxx.Response != Responses.Success) - return new(); + if (otpCreateResponse.Response != Responses.Success) + return new() + { + Message = "Hubo un error al guardar el código OTP." + }; + // Enviar correo. + var success = await emailSender.Send(email, "Verificar", $"Verificar tu correo {otpCode}"); - // Coreeo - var ma = await emailSender.Send(email, "Verficar correo", $"Verificar tu correo {x}"); + return new(success ? Responses.Success : Responses.UnavailableService); + + } - return new(ma ? Responses.Success : Responses.UnavailableService); + /// + /// Validar un correo. + /// + /// Correo a validar. + /// Código OTP. + [HttpPost("validate")] + public async Task Validate([FromQuery] string mail, [FromQuery] string code) + { + // Validar OTP. + var response = await mails.ValidateOtpFormail(mail, code); + return response; } /// - /// Si el usuario olvidó su contraseña, puede solicitar un cambio de contraseña. + /// Si un usuario olvido la contraseña. /// - /// Usuario. + /// Usuario que olvido. [HttpPost("forget/password")] [RateLimit(requestLimit: 2, timeWindowSeconds: 60, blockDurationSeconds: 100)] public async Task ForgetPassword([FromQuery] string user) @@ -95,7 +126,6 @@ public async Task ForgetPassword([FromQuery] string user) Message = "Esta cuenta no tiene un correo principal establecido." }; - // Generar OTP. var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); @@ -109,8 +139,10 @@ public async Task ForgetPassword([FromQuery] string user) IsUsed = false }; + // Crear OTP. var created = await otpService.Create(modelo); + // Si hubo un error. if (created.Response != Responses.Success) return new(created.Response) { @@ -118,33 +150,24 @@ public async Task ForgetPassword([FromQuery] string user) }; // Enviar mail. - var ma = await emailSender.Send(mail.Model.Mail, "Recuperación de contraseña", $"Su código de verificación es: {otpCode}"); + var success = await emailSender.Send(mail.Model.Mail, "Recuperación de contraseña", $"Su código de verificación es: {otpCode}"); - return new(ma ? Responses.Success : Responses.UnavailableService); + return new(success ? Responses.Success : Responses.UnavailableService); } - - [HttpPost("validate")] - public async Task Validate([FromQuery] string mail, [FromQuery] string code) - { - - - var response = await mails.ValidateOtpFormail(mail, code); - - return response; - - - } - - - + /// + /// Reestablecer una contraseña. + /// + /// Código OTP. + /// Usuario único. + /// Nueva contraseña. [HttpPost("reset")] public async Task Reset([FromQuery] string code, [FromQuery] string unique, [FromQuery] string newPassword) { - + // Validar nueva contraseña. if (newPassword is null || newPassword.Length <= 0) return new(Responses.InvalidParam) { @@ -158,37 +181,39 @@ public async Task Reset([FromQuery] string code, [FromQuery] s IncludePhoto = false }); + // Si hubo un error al obtener la cuenta. if (account.Response != Responses.Success) return new(account.Response) { Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." }; - + // Leer y actualizar OTP. var response = await otpService.ReadAndUpdate(account.Model.Id, code); + // Si hubo un error al leer y actualizar el código. if (response.Response != Responses.Success) - return new(response.Response) + return new(Responses.Unauthorized) { - Message = "No se pudo reestablecer la contraseña." + Message = "No se pudo reestablecer la contraseña.", + Errors = [ + new() + { + Tittle = "Código OTP invalido", + Description = "El código es invalido o ya venció." + } + ] }; - - // Encriptar contraseña. newPassword = Global.Utilities.Cryptography.Encrypt(newPassword); + // Actualizar contraseña. + var update = await accountsData.UpdatePassword(account.Model.Id, newPassword); - var up = await accountsData.UpdatePassword(account.Model.Id, newPassword); - - return up; + return update; } - - - - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index f4aa3ca..3866bad 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -8,7 +8,7 @@ public class OrganizationsController(Data.Organizations organizationsData, Data. /// Crea una nueva organización. /// /// Modelo de la organización y el usuario administrador - [HttpPost("create")] + [HttpPost] public async Task Create([FromBody] OrganizationModel modelo) { diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index 967fad2..634ac4a 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -84,7 +84,7 @@ public async Task> Create(AccountModel modelo, int /// /// Id de la cuenta. /// Filtros de búsqueda. - public async Task> Read(int id, QueryAccountFilter filters) + public async Task> Read(int id, QueryObjectFilter filters) { try @@ -124,7 +124,7 @@ public async Task> Read(int id, QueryAccountFilter /// /// Único. /// Filtros de búsqueda. - public async Task> Read(string unique, QueryAccountFilter filters) + public async Task> Read(string unique, QueryObjectFilter filters) { try @@ -164,7 +164,7 @@ public async Task> Read(string unique, QueryAccoun /// /// Id de la identidad. /// Filtros de búsqueda. - public async Task> ReadByIdentity(int id, QueryAccountFilter filters) + public async Task> ReadByIdentity(int id, QueryObjectFilter filters) { try @@ -204,7 +204,7 @@ public async Task> ReadByIdentity(int id, QueryAcc /// /// patron de búsqueda /// Filtros - public async Task> Search(string pattern, QueryAccountFilter filters) + public async Task> Search(string pattern, QueryObjectFilter filters) { // Ejecución @@ -231,7 +231,7 @@ public async Task> Search(string pattern, QueryAcc /// Obtiene los usuarios con IDs coincidentes /// /// Lista de IDs - public async Task> FindAll(List ids, QueryAccountFilter filters) + public async Task> FindAll(List ids, QueryObjectFilter filters) { // Ejecución @@ -261,7 +261,7 @@ public async Task> FindAll(List ids, QueryAcc /// Obtiene los usuarios con IDs coincidentes /// /// Lista de IDs - public async Task> FindAllByIdentities(List ids, QueryAccountFilter filters) + public async Task> FindAllByIdentities(List ids, QueryObjectFilter filters) { // Ejecución diff --git a/LIN.Cloud.Identity/Data/Builders/Account.cs b/LIN.Cloud.Identity/Data/Builders/Account.cs index b81f6cf..3c95a05 100644 --- a/LIN.Cloud.Identity/Data/Builders/Account.cs +++ b/LIN.Cloud.Identity/Data/Builders/Account.cs @@ -52,13 +52,13 @@ public static IQueryable OnAll(DataContext context) /// Id de la cuenta /// Filtros /// Contexto - public static IQueryable GetAccounts(int id, Services.Models.QueryAccountFilter filters, DataContext context) + public static IQueryable GetAccounts(int id, QueryObjectFilter filters, DataContext context) { // Query general IQueryable accounts; - if (filters.FindOn == Services.Models.FindOn.StableAccounts) + if (filters.FindOn == FindOn.StableAccounts) accounts = from account in OnStable(context) where account.Id == id select account; @@ -83,13 +83,13 @@ public static IQueryable GetAccounts(int id, Services.Models.Query /// Identidad unica. /// Filtros /// Contexto - public static IQueryable GetAccounts(string user, Services.Models.QueryAccountFilter filters, DataContext context) + public static IQueryable GetAccounts(string user, QueryObjectFilter filters, DataContext context) { // Query general IQueryable accounts; - if (filters.FindOn == Services.Models.FindOn.StableAccounts) + if (filters.FindOn == FindOn.StableAccounts) accounts = from account in OnStable(context) where account.Identity.Unique == user select account; @@ -114,13 +114,13 @@ public static IQueryable GetAccounts(string user, Services.Models. /// Id de la Identidad. /// Filtros /// Contexto - public static IQueryable GetAccountsByIdentity(int id, Services.Models.QueryAccountFilter filters, DataContext context) + public static IQueryable GetAccountsByIdentity(int id, QueryObjectFilter filters, DataContext context) { // Query general IQueryable accounts; - if (filters.FindOn == Services.Models.FindOn.StableAccounts) + if (filters.FindOn == FindOn.StableAccounts) accounts = from account in OnStable(context) where account.Identity.Id == id select account; @@ -141,7 +141,7 @@ public static IQueryable GetAccountsByIdentity(int id, Services.Mo - public static IQueryable Search(string pattern, QueryAccountFilter filters, DataContext context) + public static IQueryable Search(string pattern, QueryObjectFilter filters, DataContext context) { // Query general. @@ -160,7 +160,7 @@ where account.Identity.Unique.Contains(pattern) - public static IQueryable FindAll(IEnumerable ids, QueryAccountFilter filters, DataContext context) + public static IQueryable FindAll(IEnumerable ids, QueryObjectFilter filters, DataContext context) { IQueryable accounts; @@ -191,7 +191,7 @@ where ids.Contains(account.Id) - public static IQueryable FindAllByIdentities(IEnumerable ids, QueryAccountFilter filters, DataContext context) + public static IQueryable FindAllByIdentities(IEnumerable ids, QueryObjectFilter filters, DataContext context) { IQueryable accounts; @@ -230,7 +230,7 @@ where ids.Contains(account.IdentityId) /// /// Consulta base. /// Filtros. - private static IQueryable BuildModel(IQueryable query, Services.Models.QueryAccountFilter filters, DataContext context) + private static IQueryable BuildModel(IQueryable query, QueryObjectFilter filters, DataContext context) { byte[] profile = []; diff --git a/LIN.Cloud.Identity/Data/Builders/Identities.cs b/LIN.Cloud.Identity/Data/Builders/Identities.cs index f3450fe..279eb04 100644 --- a/LIN.Cloud.Identity/Data/Builders/Identities.cs +++ b/LIN.Cloud.Identity/Data/Builders/Identities.cs @@ -54,13 +54,13 @@ public static IQueryable OnAll(DataContext context) /// Id /// Filtros /// Contexto - public static IQueryable GetIds(int id, Services.Models.QueryIdentityFilter filters, DataContext context) + public static IQueryable GetIds(int id, QueryIdentityFilter filters, DataContext context) { // Query general IQueryable ids; - if (filters.FindOn == Services.Models.FindOn.StableAccounts) + if (filters.FindOn == FindOn.StableAccounts) ids = from identity in OnStable(context) where identity.Id == id select identity; @@ -91,7 +91,7 @@ public static IQueryable GetIds(string unique, Services.Models.Qu // Query general IQueryable ids; - if (filters.FindOn == Services.Models.FindOn.StableAccounts) + if (filters.FindOn == FindOn.StableAccounts) ids = from identity in OnStable(context) where identity.Unique == unique select identity; diff --git a/LIN.Cloud.Identity/Data/Mails.cs b/LIN.Cloud.Identity/Data/Mails.cs index 9c415b4..1dab4e9 100644 --- a/LIN.Cloud.Identity/Data/Mails.cs +++ b/LIN.Cloud.Identity/Data/Mails.cs @@ -28,14 +28,21 @@ public async Task> Create(MailModel modelo) }; } - catch (Exception) + catch (Exception ex) { + + return new() { Response = Responses.ResourceExist }; } + return new() + { + Response = Responses.Undefined + }; + } diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index 97f0948..a5436cb 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -1,4 +1,6 @@ -namespace LIN.Cloud.Identity.Data; +using IdentityService = LIN.Types.Cloud.Identity.Enumerations.IdentityService; + +namespace LIN.Cloud.Identity.Data; public class Organizations(DataContext context, IamRoles iam) diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index da8a9db..e1023fd 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -2,7 +2,6 @@ using LIN.Cloud.Identity.Services.Auth.Interfaces; using LIN.Cloud.Identity.Services.Extensions; using LIN.Cloud.Identity.Services.Realtime; -using Microsoft.AspNetCore.Http.HttpResults; var builder = WebApplication.CreateBuilder(args); diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 65ba980..be343af 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using IdentityService = LIN.Types.Cloud.Identity.Enumerations.IdentityService; namespace LIN.Cloud.Identity.Services.Formats; diff --git a/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs b/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs deleted file mode 100644 index 295e733..0000000 --- a/LIN.Cloud.Identity/Services/Models/QueryAccountFilter.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Models; - -public class QueryAccountFilter -{ - public int AccountContext { get; set; } - public int IdentityContext { get; set; } - public List OrganizationsDirectories { get; set; } = []; - public bool IsAdmin { get; set; } - public bool IncludePhoto { get; set; } = true; - public FindOn FindOn { get; set; } - -} - -public enum FindOn -{ - StableAccounts, - AllAccounts -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs index ea16aae..36c4058 100644 --- a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs +++ b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs @@ -3,9 +3,17 @@ public class EmailSender { + /// + /// Enviar correo. + /// + /// A. + /// Asunto. + /// Cuerpo HTML. public async Task Send(string to, string subject, string body) { - Global.Http.Services.Client client = new("https://hangfire.linplatform.com/api/mailsender") + + // Servicio. + Global.Http.Services.Client client = new(Http.Services.Configuration.GetConfiguration("hangfire:mail")) { TimeOut = 10 }; diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 5672740..22502d7 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -23,4 +23,7 @@ global using System.Security.Claims; // Framework. global using System.Text; -global using LIN.Types.Enumerations; \ No newline at end of file +global using LIN.Types.Enumerations; +global using Http.Attributes; +global using LIN.Cloud.Identity.Persistence.Models; +global using LIN.Cloud.Identity.Services.Utils; \ No newline at end of file diff --git a/LIN.Identity.Tests/Data/Accounts.cs b/LIN.Identity.Tests/Data/Accounts.cs index cf2fb20..b7bff05 100644 --- a/LIN.Identity.Tests/Data/Accounts.cs +++ b/LIN.Identity.Tests/Data/Accounts.cs @@ -1,4 +1,5 @@ using LIN.Cloud.Identity.Persistence.Contexts; +using LIN.Cloud.Identity.Persistence.Models; using LIN.Cloud.Identity.Services.Models; using LIN.Types.Cloud.Identity.Models; using LIN.Types.Responses; @@ -62,7 +63,7 @@ public async Task Read_ShouldReturnNotRowsResponse_WhenAccountDoesNotExist() var accounts = new Cloud.Identity.Data.Accounts(context); // Act - var response = await accounts.Read(999, new QueryAccountFilter()); + var response = await accounts.Read(999, new QueryObjectFilter()); // Assert Assert.Equal(Responses.NotRows, response.Response); @@ -88,7 +89,7 @@ public async Task ReadByUnique_ShouldReturnSuccessResponse_WhenAccountExists() await context.SaveChangesAsync(); // Act - var response = await accounts.Read("unique-id", new QueryAccountFilter()); + var response = await accounts.Read("unique-id", new QueryObjectFilter()); // Assert Assert.Equal(Responses.Success, response.Response); @@ -104,7 +105,7 @@ public async Task ReadByUnique_ShouldReturnNotRowsResponse_WhenAccountDoesNotExi var accounts = new Cloud.Identity.Data.Accounts(context); // Act - var response = await accounts.Read("non-existent-id", new QueryAccountFilter()); + var response = await accounts.Read("non-existent-id", new QueryObjectFilter()); // Assert Assert.Equal(Responses.NotRows, response.Response); @@ -130,7 +131,7 @@ public async Task ReadByIdentity_ShouldReturnSuccessResponse_WhenAccountExists() await context.SaveChangesAsync(); // Act - var response = await accounts.ReadByIdentity(1, new QueryAccountFilter()); + var response = await accounts.ReadByIdentity(1, new QueryObjectFilter()); // Assert Assert.Equal(Responses.Success, response.Response); @@ -146,7 +147,7 @@ public async Task ReadByIdentity_ShouldReturnNotRowsResponse_WhenAccountDoesNotE var accounts = new Cloud.Identity.Data.Accounts(context); // Act - var response = await accounts.ReadByIdentity(999, new QueryAccountFilter()); + var response = await accounts.ReadByIdentity(999, new QueryObjectFilter()); // Assert Assert.Equal(Responses.NotRows, response.Response); @@ -172,7 +173,7 @@ public async Task Search_ShouldReturnSuccessResponse_WhenAccountsMatchPattern() await context.SaveChangesAsync(); // Act - var response = await accounts.Search("Test", new QueryAccountFilter()); + var response = await accounts.Search("Test", new QueryObjectFilter()); // Assert Assert.Equal(Responses.Success, response.Response); @@ -187,7 +188,7 @@ public async Task Search_ShouldReturnNotRowsResponse_WhenNoAccountsMatchPattern( var accounts = new Cloud.Identity.Data.Accounts(context); // Act - var response = await accounts.Search("NonExistentPattern", new QueryAccountFilter()); + var response = await accounts.Search("NonExistentPattern", new QueryObjectFilter()); // Assert Assert.Equal(Responses.NotRows, response.Response); @@ -213,7 +214,7 @@ public async Task FindAll_ShouldReturnSuccessResponse_WhenAccountsExist() await context.SaveChangesAsync(); // Act - var response = await accounts.FindAll([accountModel.Id], new QueryAccountFilter()); + var response = await accounts.FindAll([accountModel.Id], new QueryObjectFilter()); // Assert Assert.Equal(Responses.Success, response.Response); @@ -228,7 +229,7 @@ public async Task FindAll_ShouldReturnNotRowsResponse_WhenAccountsDoNotExist() var accounts = new Cloud.Identity.Data.Accounts(context); // Act - var response = await accounts.FindAll([999], new QueryAccountFilter()); + var response = await accounts.FindAll([999], new QueryObjectFilter()); // Assert Assert.Equal(Responses.NotRows, response.Response); @@ -254,7 +255,7 @@ public async Task FindAllByIdentities_ShouldReturnSuccessResponse_WhenAccountsEx await context.SaveChangesAsync(); // Act - var response = await accounts.FindAllByIdentities([1], new QueryAccountFilter()); + var response = await accounts.FindAllByIdentities([1], new QueryObjectFilter()); // Assert Assert.Equal(Responses.Success, response.Response); @@ -269,7 +270,7 @@ public async Task FindAllByIdentities_ShouldReturnNotRowsResponse_WhenAccountsDo var accounts = new Cloud.Identity.Data.Accounts(context); // Act - var response = await accounts.FindAllByIdentities([999], new QueryAccountFilter()); + var response = await accounts.FindAllByIdentities([999], new QueryObjectFilter()); // Assert Assert.Equal(Responses.NotRows, response.Response); From 57de9bcf8f09fbdee9395b3f2b9ceaad0b4d1466 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 11 Nov 2024 19:26:02 -0500 Subject: [PATCH 132/178] Mejoras --- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../Areas/Authentication/SecurityController.cs | 7 +++---- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 4 ++-- LIN.Cloud.Identity/Program.cs | 5 +++-- LIN.Cloud.Identity/Services/Iam/IamRoles.cs | 4 +--- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 527fc4f..fb3d171 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index a033fcb..8ed01f5 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -100,7 +100,6 @@ public async Task Validate([FromQuery] string mail, [FromQuery /// /// Usuario que olvido. [HttpPost("forget/password")] - [RateLimit(requestLimit: 2, timeWindowSeconds: 60, blockDurationSeconds: 100)] public async Task ForgetPassword([FromQuery] string user) { @@ -112,7 +111,7 @@ public async Task ForgetPassword([FromQuery] string user) }); if (account.Response != Responses.Success) - return new(account.Response) + return new(Responses.NotExistAccount) { Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." }; @@ -121,7 +120,7 @@ public async Task ForgetPassword([FromQuery] string user) var mail = await mails.ReadPrincipal(user); if (mail.Response != Responses.Success) - return new(mail.Response) + return new(Responses.NotRows) { Message = "Esta cuenta no tiene un correo principal establecido." }; @@ -183,7 +182,7 @@ public async Task Reset([FromQuery] string code, [FromQuery] s // Si hubo un error al obtener la cuenta. if (account.Response != Responses.Success) - return new(account.Response) + return new(Responses.NotExistAccount) { Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." }; diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 2cbe52b..d5c6247 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -10,9 +10,9 @@ - + - + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index e1023fd..a72af74 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -1,4 +1,5 @@ using Http.Extensions; +using Http.Extensions.OpenApi; using LIN.Cloud.Identity.Services.Auth.Interfaces; using LIN.Cloud.Identity.Services.Extensions; using LIN.Cloud.Identity.Services.Realtime; @@ -6,12 +7,12 @@ var builder = WebApplication.CreateBuilder(args); // Servicios de contenedor. -builder.Services.AddSignalR(); builder.Services.AddLINHttp(true, (options) => { - options.OperationFilter>("token"); + options.OperationFilter>("token", "Token de acceso a LIN Cloud Identity"); }); +builder.Services.AddSignalR(); builder.Services.AddLocalServices(); // Servicio de autenticación. diff --git a/LIN.Cloud.Identity/Services/Iam/IamRoles.cs b/LIN.Cloud.Identity/Services/Iam/IamRoles.cs index 096f95c..0d6a711 100644 --- a/LIN.Cloud.Identity/Services/Iam/IamRoles.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamRoles.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Utils; - -namespace LIN.Cloud.Identity.Services.Iam; +namespace LIN.Cloud.Identity.Services.Iam; public class IamRoles(DataContext context, Data.Groups groups, IIdentityService identityService) { From a56988c703ce38319a13eee0daf408836b821adf Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 11 Nov 2024 19:52:53 -0500 Subject: [PATCH 133/178] Net 9 PRE --- .../LIN.Cloud.Identity.Persistence.csproj | 4 ++-- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 4 ++-- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index fb3d171..2bd384c 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable enable Debug;Release;Local;Release-dev;Debug-dev @@ -11,7 +11,7 @@ - + \ No newline at end of file diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index d5c6247..29784d5 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable enable false @@ -12,7 +12,7 @@ - + diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 4239f08..a13144d 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable false @@ -14,8 +14,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + From b021b4cbd73ba53c9de88a7031b68cdee149ecd8 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Mon, 11 Nov 2024 19:54:17 -0500 Subject: [PATCH 134/178] Mejoras --- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index d5c6247..601d26e 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -12,7 +12,7 @@ - + From b6cb22e1cb78ba262b0261cdce260378c2aeb0f9 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Wed, 13 Nov 2024 19:22:51 -0500 Subject: [PATCH 135/178] Update --- .../Areas/Accounts/AccountController.cs | 4 +-- LIN.Cloud.Identity/Data/AccountLogs.cs | 5 ---- .../Services/Formats/Account.cs | 4 +-- .../Services/Realtime/PassKeyHub.cs | 2 +- .../Services/Realtime/PassKeyHubActions.cs | 1 - .../Services/Utils/EmailSender.cs | 1 - .../Services/Utils/IdentityService.cs | 26 +++++++++---------- .../Services/Utils/PolicyService.cs | 2 +- 8 files changed, 18 insertions(+), 27 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 9b51a51..89155a4 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -31,12 +31,10 @@ public async Task Create([FromBody] AccountModel? modelo) }; // Organización del modelo. - modelo.Identity.EffectiveTime = default; - modelo.Identity.ExpirationTime = default; modelo = Services.Formats.Account.Process(modelo); // Creación del usuario. - var response = await accountData.Create(modelo, 0); + var response = await accountData.Create(modelo); // Evaluación. if (response.Response != Responses.Success) diff --git a/LIN.Cloud.Identity/Data/AccountLogs.cs b/LIN.Cloud.Identity/Data/AccountLogs.cs index 7a628cb..1ff807c 100644 --- a/LIN.Cloud.Identity/Data/AccountLogs.cs +++ b/LIN.Cloud.Identity/Data/AccountLogs.cs @@ -3,10 +3,6 @@ public class AccountLogs(DataContext context) { - /// - /// Crear nuevo log. - /// - /// Modelo. public async Task Create(AccountLog log) { @@ -45,7 +41,6 @@ public async Task Create(AccountLog log) } - public async Task> ReadAll(int accountId) { diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index be343af..e708a64 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -61,8 +61,8 @@ public static AccountModel Process(AccountModel baseAccount) Status = IdentityStatus.Enable, Type = IdentityType.Account, CreationTime = DateTime.Now, - EffectiveTime = baseAccount.Identity.EffectiveTime == default ? DateTime.Now : baseAccount.Identity.EffectiveTime, - ExpirationTime = baseAccount.Identity.ExpirationTime == default ? DateTime.Now.AddYears(5) : baseAccount.Identity.ExpirationTime, + EffectiveTime = DateTime.Now, + ExpirationTime = DateTime.Now.AddYears(5), Roles = [], Unique = baseAccount.Identity.Unique.Trim() } diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 81e6dbf..718b2d6 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -26,7 +26,6 @@ public partial class PassKeyHub(Data.AccountLogs accountLogs) : Hub /// /// Evento cuando se desconecta. /// - public override Task OnDisconnectedAsync(Exception? exception) { @@ -156,4 +155,5 @@ await accountLogs.Create(new() { } } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index e56ac46..9c783e8 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -6,7 +6,6 @@ public partial class PassKeyHub /// /// Agregar un dispositivo administrador. /// - public async Task JoinAdmin(string token) { diff --git a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs index 36c4058..c193b64 100644 --- a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs +++ b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs @@ -23,7 +23,6 @@ public async Task Send(string to, string subject, string body) var aa = await client.Post(body); - return true; } diff --git a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs index 8dff2d5..487ed35 100644 --- a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs +++ b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs @@ -34,23 +34,23 @@ private async Task GetIdentities(int identity, List ids) select member.Group.IdentityId).ToList(), }; - // Si hay elementos. - if (query.Any()) - { - // Ejecuta la consulta. - var local = query.ToList(); + // Si no hay elementos. + if (!query.Any()) + return; - // Obtiene las bases. - var bases = local.SelectMany(t => t.In); + // Ejecuta la consulta. + var local = query.ToList(); - // Agregar a los objetos. - ids.AddRange(bases); + // Obtiene las bases. + var bases = local.SelectMany(t => t.In); - // Recorrer. - foreach (var @base in bases) - await GetIdentities(@base, ids); + // Agregar a los objetos. + ids.AddRange(bases); + + // Recorrer. + foreach (var @base in bases) + await GetIdentities(@base, ids); - } } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs index eb689af..07dc1bf 100644 --- a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs +++ b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs @@ -19,7 +19,7 @@ public class PolicyService(IamPolicy iamPolicy) isValid = PasswordTime(requirements); if (!isValid) - return (false, "No tienes acceso a la política devido a que no has cambiado la contraseña en los últimos N dias."); + return (false, "No tienes acceso a la política debido a que no has cambiado la contraseña en los últimos N dias."); isValid = TFA(requirements); From b8df6a075433598c7f42000e2fdc4da668788c7c Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Wed, 13 Nov 2024 19:29:31 -0500 Subject: [PATCH 136/178] Update --- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 2bd384c..fa93c38 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -11,7 +11,7 @@ - + \ No newline at end of file diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index a13144d..ceda807 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -15,8 +15,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + From 42f90075afc4cdfab8519b777871b104aea06f96 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Wed, 13 Nov 2024 19:36:30 -0500 Subject: [PATCH 137/178] Update --- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index fb3d171..f9dbfd6 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -11,7 +11,7 @@ - + \ No newline at end of file diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 4239f08..acb2df9 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -14,8 +14,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + From c110787a448edc67a0a6940f529aa32ebfa655e5 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 24 Nov 2024 19:45:30 -0500 Subject: [PATCH 138/178] mejoras --- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 4 ++-- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index f9dbfd6..fc86603 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 601d26e..f63af37 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -10,9 +10,9 @@ - + - + diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index acb2df9..90b6272 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -16,7 +16,7 @@ - + From 83790bdd8eec9d033943a823b8782e90754ac6a4 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Wed, 27 Nov 2024 21:23:02 -0500 Subject: [PATCH 139/178] General --- .../Areas/Accounts/AccountController.cs | 7 +- .../OrganizationMembersController.cs | 7 +- LIN.Cloud.Identity/Data/Policies.cs | 7 +- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- .../Services/Formats/Account.cs | 27 ++++-- .../Services/Utils/PolicyService.cs | 25 +++-- LIN.Identity.Tests/Validations.cs | 91 ------------------- 7 files changed, 52 insertions(+), 114 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 89155a4..fbcb67a 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -21,13 +21,14 @@ public async Task Create([FromBody] AccountModel? modelo) }; // Validar usuario y nombre. - var (pass, message) = Services.Formats.Account.Validate(modelo); + var errors = Services.Formats.Account.Validate(modelo); // Si no fue valido. - if (!pass) + if (errors.Count > 0) return new(Responses.InvalidParam) { - Message = message + Message = "Ocurrió un error al crear la cuenta", + Errors = errors }; // Organización del modelo. diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index d484f69..5af931b 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -94,13 +94,14 @@ public async Task Create([FromBody] AccountModel modelo, [Fr }; // Validar usuario y nombre. - var (pass, message) = Services.Formats.Account.Validate(modelo); + var errors = Services.Formats.Account.Validate(modelo); // Si no fue valido. - if (!pass) + if (errors.Count > 0) return new(Responses.InvalidParam) { - Message = message + Message = "Error al crear la cuenta", + Errors = errors }; // Agregar la identidad. diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs index ebc609d..7569931 100644 --- a/LIN.Cloud.Identity/Data/Policies.cs +++ b/LIN.Cloud.Identity/Data/Policies.cs @@ -219,12 +219,13 @@ public async Task HasFor(int id, string policyId) var requirements = await policiesRequirement.ReadAll(result); // Validar. - (bool isValid, string message) = policyService.Validate(requirements.Models); + var validate = policyService.Validate(requirements.Models); // Respuesta. - return new(isValid ? Responses.Success : Responses.Unauthorized) + return new(validate is not null ? Responses.Success : Responses.Unauthorized) { - Message = message + Message = "Error", + Errors = [validate] }; } diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index f63af37..070a0f3 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -10,7 +10,7 @@ - + diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index e708a64..86a3be9 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -1,4 +1,5 @@ -using System.Text.RegularExpressions; +using LIN.Types.Models; +using System.Text.RegularExpressions; using IdentityService = LIN.Types.Cloud.Identity.Enumerations.IdentityService; namespace LIN.Cloud.Identity.Services.Formats; @@ -10,21 +11,33 @@ public class Account /// Procesar el modelo. /// /// Modelo - public static (bool pass, string message) Validate(AccountModel baseAccount) + public static List Validate(AccountModel baseAccount) { + List errors = []; if (string.IsNullOrWhiteSpace(baseAccount.Name)) - return (false, "La cuenta debe de tener un nombre valido."); + errors.Add(new ErrorModel() + { + Tittle = "Nombre invalido", + Description = "El nombre del usuario no puede estar vacío.", + }); if (baseAccount.Identity == null || string.IsNullOrWhiteSpace(baseAccount.Identity.Unique)) - return (false, "La cuenta debe de tener una identidad unica valida."); + errors.Add(new ErrorModel() + { + Tittle = "Identidad no valida", + Description = "La cuenta debe tener un identificador único valido.", + }); if (!ValidarCadena(baseAccount.Identity.Unique)) - return (false, "La identidad de la cuenta no puede contener símbolos NO alfanuméricos."); - + errors.Add(new ErrorModel() + { + Tittle = "Identidad no valida", + Description = "La identidad de la cuenta no puede contener símbolos NO alfanuméricos." + }); - return (true, ""); + return errors; } diff --git a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs index 07dc1bf..912f173 100644 --- a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs +++ b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using LIN.Types.Models; +using Newtonsoft.Json.Linq; namespace LIN.Cloud.Identity.Services.Utils; @@ -7,26 +8,38 @@ public class PolicyService(IamPolicy iamPolicy) - public (bool valid, string message) Validate(IEnumerable requirements) + public ErrorModel? Validate(IEnumerable requirements) { bool isValid = Time(requirements); if (!isValid) - return (false, "No tienes acceso a la política por la hora actual."); + return new() + { + Tittle = "Política no complaciente", + Description = "No tienes acceso a la política por la hora actual." + }; // Validar contraseña. isValid = PasswordTime(requirements); if (!isValid) - return (false, "No tienes acceso a la política debido a que no has cambiado la contraseña en los últimos N dias."); + return new() + { + Tittle = "Política no complaciente", + Description = "No tienes acceso a la política debido a que no has cambiado la contraseña en los últimos N dias." + }; isValid = TFA(requirements); if (!isValid) - return (false, "No tienes acceso a la política debido a que no tienes el doble factor de autenticación"); + return new() + { + Tittle = "Política no complaciente", + Description = "No tienes acceso a la política debido a que no tienes el doble factor de autenticación" + }; - return (true, string.Empty); + return null; } diff --git a/LIN.Identity.Tests/Validations.cs b/LIN.Identity.Tests/Validations.cs index 4aa7166..6b2f064 100644 --- a/LIN.Identity.Tests/Validations.cs +++ b/LIN.Identity.Tests/Validations.cs @@ -6,97 +6,6 @@ namespace LIN.Identity.Tests; public class AccountTests { - - [Fact] - public void Validate_ShouldReturnFalse_WhenNameIsEmpty() - { - // Arrange - var account = new AccountModel - { - Name = "", - Identity = new IdentityModel { Unique = "unique_user" } - }; - - // Act - var (pass, message) = Account.Validate(account); - - // Assert - Assert.False(pass); - Assert.Equal("La cuenta debe de tener un nombre valido.", message); - } - - [Fact] - public void Validate_ShouldReturnFalse_WhenIdentityIsNull() - { - // Arrange - var account = new AccountModel - { - Name = "Test Account", - Identity = null - }; - - // Act - var result = Account.Validate(account); - - // Assert - Assert.False(result.pass); - Assert.Equal("La cuenta debe de tener una identidad unica valida.", result.message); - } - - [Fact] - public void Validate_ShouldReturnFalse_WhenIdentityUniqueIsEmpty() - { - // Arrange - var account = new AccountModel - { - Name = "Test Account", - Identity = new IdentityModel { Unique = "" } - }; - - // Act - var result = Account.Validate(account); - - // Assert - Assert.False(result.pass); - Assert.Equal("La cuenta debe de tener una identidad unica valida.", result.message); - } - - [Fact] - public void Validate_ShouldReturnFalse_WhenIdentityUniqueContainsNonAlphanumeric() - { - // Arrange - var account = new AccountModel - { - Name = "Test Account", - Identity = new IdentityModel { Unique = "unique_user!" } - }; - - // Act - var result = Account.Validate(account); - - // Assert - Assert.False(result.pass); - Assert.Equal("La identidad de la cuenta no puede contener símbolos NO alfanuméricos.", result.message); - } - - [Fact] - public void Validate_ShouldReturnTrue_WhenAccountIsValid() - { - // Arrange - var account = new AccountModel - { - Name = "Test Account", - Identity = new IdentityModel { Unique = "uniqueuser" } - }; - - // Act - var (pass, message) = Account.Validate(account); - - // Assert - Assert.True(pass); - Assert.Equal("", message); - } - [Fact] public void Process_ShouldReturnProcessedAccountModel() { From fa0d1327657ceb4fece84132238a058aac8c9ea2 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 7 Dec 2024 19:21:04 -0500 Subject: [PATCH 140/178] Update --- LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs | 2 +- .../Areas/Organizations/OrganizationMembersController.cs | 4 ++-- LIN.Cloud.Identity/Data/DirectoryMembers.cs | 4 ++-- LIN.Cloud.Identity/Data/Policies.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 2db89c9..f58a119 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -22,7 +22,7 @@ public class DataContext(DbContextOptions options) : DbContext(opti /// /// Organizaciones. /// - public DbSet Organizations { get; set; } + public DbSet Organizations { get; set; } /// diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index 5af931b..43a776e 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -184,7 +184,7 @@ public async Task>> ReadAll([FromH /// Lista de ids a agregar. /// Retorna el resultado del proceso. [HttpPost("expulse")] - public async Task Expulse([FromQuery] int organization, [FromBody] List ids) + public async Task Expulse([FromQuery] int organization, [FromBody] IEnumerable ids) { // Confirmar el rol. @@ -202,7 +202,7 @@ public async Task Expulse([FromQuery] int organization, [FromB }; // Solo elementos distintos. - ids = ids.Distinct().ToList(); + ids = ids.Distinct(); // Valida si el usuario pertenece a la organización. var (existentes, _) = await directoryMembersData.IamIn(ids, organization); diff --git a/LIN.Cloud.Identity/Data/DirectoryMembers.cs b/LIN.Cloud.Identity/Data/DirectoryMembers.cs index 049db84..3aa93b5 100644 --- a/LIN.Cloud.Identity/Data/DirectoryMembers.cs +++ b/LIN.Cloud.Identity/Data/DirectoryMembers.cs @@ -129,7 +129,7 @@ on org.DirectoryId equals gm.GroupId /// Identidades /// Id de la organización /// Contexto - public async Task<(List success, List failure)> IamIn(List ids, int organization) + public async Task<(IEnumerable success, List failure)> IamIn(IEnumerable ids, int organization) { try @@ -325,7 +325,7 @@ on gm.IdentityId equals a.IdentityId /// Lista de identidades. /// Id de la organización. /// Respuesta del proceso. - public async Task Expulse(List ids, int organization) + public async Task Expulse(IEnumerable ids, int organization) { try diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs index 7569931..c6c4278 100644 --- a/LIN.Cloud.Identity/Data/Policies.cs +++ b/LIN.Cloud.Identity/Data/Policies.cs @@ -222,7 +222,7 @@ public async Task HasFor(int id, string policyId) var validate = policyService.Validate(requirements.Models); // Respuesta. - return new(validate is not null ? Responses.Success : Responses.Unauthorized) + return new(validate is null ? Responses.Success : Responses.Unauthorized) { Message = "Error", Errors = [validate] From 9777866f7fa866a545686bc3321a74b0ddffca7e Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Wed, 11 Dec 2024 21:20:32 -0500 Subject: [PATCH 141/178] Update --- LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs | 2 +- .../Areas/Organizations/OrganizationMembersController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index 66a5bfe..d9a5874 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -114,7 +114,7 @@ public async Task Create([FromHeader] int group, [FromBody] } })); - response.Message = $"Se agregaron {successIds.Count} integrantes y se omitieron {failureIds.Count} debido a que no pertenecen a esta organización."; + response.Message = $"Se agregaron {successIds.Count()} integrantes y se omitieron {failureIds.Count} debido a que no pertenecen a esta organización."; // Retorna el resultado return response; diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index 43a776e..69dcecb 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -53,7 +53,7 @@ public async Task AddExternalMembers([FromQuery] int organiz Type = GroupMemberTypes.Guest })); - response.Message = $"Se agregaron {noUpdated.Count} integrantes como invitados y se omitieron {existentes.Count} debido a que ya pertenecen a esta organización."; + response.Message = $"Se agregaron {noUpdated.Count} integrantes como invitados y se omitieron {existentes.Count()} debido a que ya pertenecen a esta organización."; // Retorna el resultado return response; From e93c82539a21861a5aeb0cb9647aa2b786ab0127 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Tue, 21 Jan 2025 19:34:52 -0500 Subject: [PATCH 142/178] =?UTF-8?q?Actualizaci=C3=B3n=20de=20paquetes=20y?= =?UTF-8?q?=20limpieza=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se han actualizado las versiones de varios paquetes en los archivos `LIN.Cloud.Identity.Persistence.csproj`, `LIN.Cloud.Identity.csproj` y `LIN.Identity.Tests.csproj`. Se han realizado varias limpiezas de código: - Eliminación de clases `AllowApps`, `Groups` y `IdentityRoles`. - Ajustes en comentarios en `AccountController.cs`, `IntentsController.cs`, `SecurityController.cs`, `Organizations.cs`, `IamPolicy.cs`. - Eliminación de un corchete innecesario en `AllowService.cs`. - Eliminación de una línea de `using` y ajuste de espacio en blanco en `Policies.cs`. --- .../LIN.Cloud.Identity.Persistence.csproj | 6 +++--- LIN.Cloud.Identity/Areas/Accounts/AccountController.cs | 2 -- .../Areas/Authentication/IntentsController.cs | 6 +++--- .../Areas/Authentication/SecurityController.cs | 2 +- LIN.Cloud.Identity/Data/AllowApps.cs | 4 ---- LIN.Cloud.Identity/Data/Groups.cs | 1 - LIN.Cloud.Identity/Data/IdentityRoles.cs | 4 ---- LIN.Cloud.Identity/Data/Organizations.cs | 1 - LIN.Cloud.Identity/Data/Policies.cs | 4 +--- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- LIN.Cloud.Identity/Services/Auth/AllowService.cs | 1 - LIN.Cloud.Identity/Services/Iam/IamPolicy.cs | 2 +- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 10 +++++----- 13 files changed, 15 insertions(+), 30 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 6ef6aec..c825bb5 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,9 +9,9 @@ - - - + + + \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index fbcb67a..81133e8 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -94,7 +94,6 @@ public async Task> Read([FromQuery] int id) // Retorna el resultado return response; - } @@ -173,7 +172,6 @@ public async Task> ReadByIdentity([FromQuery] // Retorna el resultado return response; - } diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 41fb103..1d5bf99 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -15,15 +15,15 @@ public HttpReadAllResponse GetAll() { try { - // Cuenta + // Cuenta. var account = (from a in PassKeyHub.Attempts where a.Key.Equals(UserInformation.Unique, StringComparison.CurrentCultureIgnoreCase) select a).FirstOrDefault().Value ?? []; - // Hora actual + // Hora actual. var timeNow = DateTime.Now; - // Intentos + // Intentos. var intentos = (from I in account where I.Status == PassKeyStatus.Undefined where I.Expiración > timeNow diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index 8ed01f5..47a092d 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -53,7 +53,7 @@ public async Task AddMail([FromQuery] string email) // Generar Otp. var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); - //Guardar OTP. + // Guardar OTP. var otpCreateResponse = await otpService.Create(new MailOtpDatabaseModel { MailModel = responseCreate.Model, diff --git a/LIN.Cloud.Identity/Data/AllowApps.cs b/LIN.Cloud.Identity/Data/AllowApps.cs index 1e729b2..2f9c272 100644 --- a/LIN.Cloud.Identity/Data/AllowApps.cs +++ b/LIN.Cloud.Identity/Data/AllowApps.cs @@ -1,10 +1,8 @@ namespace LIN.Cloud.Identity.Data; - public class AllowApps(DataContext context, LIN.Cloud.Identity.Services.Utils.IIdentityService identityService) { - /// /// Crear acceso a app. /// @@ -48,7 +46,6 @@ public async Task> Create(AllowApp modelo) } - /// /// Obtener las apps a las que una identidad tiene acceso o no. /// @@ -86,5 +83,4 @@ where identities.Contains(allow.IdentityId) return new(); } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Groups.cs b/LIN.Cloud.Identity/Data/Groups.cs index d0e2c7c..0c38444 100644 --- a/LIN.Cloud.Identity/Data/Groups.cs +++ b/LIN.Cloud.Identity/Data/Groups.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Data; - public class Groups(DataContext context) { diff --git a/LIN.Cloud.Identity/Data/IdentityRoles.cs b/LIN.Cloud.Identity/Data/IdentityRoles.cs index 07d2387..a81c9b1 100644 --- a/LIN.Cloud.Identity/Data/IdentityRoles.cs +++ b/LIN.Cloud.Identity/Data/IdentityRoles.cs @@ -1,10 +1,8 @@ namespace LIN.Cloud.Identity.Data; - public class IdentityRoles(DataContext context) { - /// /// Crear nuevo rol en identidad. /// @@ -20,7 +18,6 @@ public async Task Create(IdentityRolesModel modelo) context.Attach(modelo.Identity); context.Attach(modelo.Organization); - // Guardar la identidad. await context.IdentityRoles.AddAsync(modelo); context.SaveChanges(); @@ -42,7 +39,6 @@ public async Task Create(IdentityRolesModel modelo) } - /// /// Obtener los roles asociados a una identidad en una organización determinada. /// diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index a5436cb..8d9a5ba 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -31,7 +31,6 @@ public async Task Create(OrganizationModel modelo) context.SaveChanges(); - // Cuenta de usuario var account = new AccountModel() { diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs index c6c4278..7585182 100644 --- a/LIN.Cloud.Identity/Data/Policies.cs +++ b/LIN.Cloud.Identity/Data/Policies.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Utils; - -namespace LIN.Cloud.Identity.Data; +namespace LIN.Cloud.Identity.Data; public class Policies(DataContext context, Data.PoliciesRequirement policiesRequirement, Services.Utils.IIdentityService identityService, PolicyService policyService) { diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index d16cfbd..e95a975 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity/Services/Auth/AllowService.cs b/LIN.Cloud.Identity/Services/Auth/AllowService.cs index 8a43a91..51a9c9a 100644 --- a/LIN.Cloud.Identity/Services/Auth/AllowService.cs +++ b/LIN.Cloud.Identity/Services/Auth/AllowService.cs @@ -20,7 +20,6 @@ public async Task IsAllow(IEnumerable identities, int appId) select allow.IsAllow).ToListAsync(); return isAllow.Contains(true); - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs index 37fbe4a..1f0b785 100644 --- a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs @@ -17,7 +17,7 @@ public async Task Validate(int identity, string policy) && pol.Id == Guid.Parse(policy) select pol).AnyAsync(); - // Es privilegiado. + // Es el creador. if (isOwner) return IamLevels.Privileged; diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 4629940..b397340 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -10,16 +10,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 9ba07e2f976d4b0ed62001857d20e88c20da6039 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 9 Feb 2025 17:01:12 -0500 Subject: [PATCH 143/178] Update --- LIN.Cloud.Identity/Data/Builders/Account.cs | 4 ++-- LIN.Cloud.Identity/Data/Organizations.cs | 1 - LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- LIN.Identity.Tests/Data/Groups.cs | 2 +- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 4 ++-- LIN.Identity.Tests/Validations.cs | 7 +------ 6 files changed, 7 insertions(+), 13 deletions(-) diff --git a/LIN.Cloud.Identity/Data/Builders/Account.cs b/LIN.Cloud.Identity/Data/Builders/Account.cs index 3c95a05..4b0bfb3 100644 --- a/LIN.Cloud.Identity/Data/Builders/Account.cs +++ b/LIN.Cloud.Identity/Data/Builders/Account.cs @@ -217,7 +217,7 @@ where ids.Contains(account.IdentityId) } - private static readonly byte[] selector = []; + private static readonly string selector = string.Empty; @@ -263,7 +263,7 @@ private static IQueryable BuildModel(IQueryable quer }, Password = account.Password, Visibility = account.Visibility, - Profile = filters.IncludePhoto ? profile : selector, + Profile = filters.IncludePhoto ? string.Empty : selector, IdentityId = account.Identity.Id, IdentityService = account.IdentityService }; diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index 8d9a5ba..0df0229 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -161,7 +161,6 @@ public async Task> Read(int id) /// Obtener las organizaciones donde una identidad pertenece. /// /// Identidad - /// Contexto public async Task> ReadAll(int id) { diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index e95a975..6242ce3 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -10,7 +10,7 @@ - + diff --git a/LIN.Identity.Tests/Data/Groups.cs b/LIN.Identity.Tests/Data/Groups.cs index 30eb583..c367902 100644 --- a/LIN.Identity.Tests/Data/Groups.cs +++ b/LIN.Identity.Tests/Data/Groups.cs @@ -11,7 +11,7 @@ public class GroupsTests private static DataContext GetInMemoryDbContext() { var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: "TestDatabase") + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; return new DataContext(options); diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index b397340..0494b82 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/LIN.Identity.Tests/Validations.cs b/LIN.Identity.Tests/Validations.cs index 6b2f064..8e864f8 100644 --- a/LIN.Identity.Tests/Validations.cs +++ b/LIN.Identity.Tests/Validations.cs @@ -13,13 +13,11 @@ public void Process_ShouldReturnProcessedAccountModel() var account = new AccountModel { Name = " Test Account ", - Profile = new byte[] { 0x01, 0x02 }, + Profile = string.Empty, Password = "password", Visibility = Visibility.Visible, Identity = new IdentityModel { - EffectiveTime = DateTime.Now, - ExpirationTime = DateTime.Now.AddYears(1), Unique = "uniqueuser" } }; @@ -29,14 +27,11 @@ public void Process_ShouldReturnProcessedAccountModel() // Assert Assert.Equal("Test Account", processedAccount.Name); - Assert.Equal(account.Profile, processedAccount.Profile); Assert.NotEqual("password", processedAccount.Password); // Assuming encryption changes the password Assert.Equal(Visibility.Visible, processedAccount.Visibility); Assert.Equal("uniqueuser", processedAccount.Identity.Unique); Assert.Equal(IdentityStatus.Enable, processedAccount.Identity.Status); Assert.Equal(IdentityType.Account, processedAccount.Identity.Type); - Assert.Equal(account.Identity.EffectiveTime, processedAccount.Identity.EffectiveTime); - Assert.Equal(account.Identity.ExpirationTime, processedAccount.Identity.ExpirationTime); } } From 3ae0901a35445971fa395dfa73c7b979c3c4f0e4 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 11:00:48 -0500 Subject: [PATCH 144/178] Data seed, mejoras en autenticacion --- .../Contexts/DataContext.cs | 62 +++++++- .../Extensions/PersistenceExtensions.cs | 3 + .../LIN.Cloud.Identity.Persistence.csproj | 17 +- .../Areas/Accounts/AccountController.cs | 1 + .../ApplicationRestrictionsController.cs | 29 ++++ .../Applications/ApplicationsController.cs | 61 ++++++++ .../AuthenticationController.cs | 8 + .../OrganizationMembersController.cs | 1 + .../Data/ApplicationRestrictions.cs | 147 ++++++++++++++++++ LIN.Cloud.Identity/Data/Builders/Account.cs | 4 +- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 35 ++--- .../Services/Auth/Authentication.cs | 61 +++++++- .../Services/Extensions/LocalServices.cs | 1 + .../Services/Formats/Account.cs | 1 + .../wwwroot/seeds/applications.json | 121 ++++++++++++++ LIN.Cloud.Identity/wwwroot/seeds/users.json | 21 +++ LIN.Identity.Tests/LIN.Identity.Tests.csproj | 66 ++++---- 17 files changed, 576 insertions(+), 63 deletions(-) create mode 100644 LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs create mode 100644 LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs create mode 100644 LIN.Cloud.Identity/Data/ApplicationRestrictions.cs create mode 100644 LIN.Cloud.Identity/wwwroot/seeds/applications.json create mode 100644 LIN.Cloud.Identity/wwwroot/seeds/users.json diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index f58a119..997efdb 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -1,6 +1,8 @@ -using LIN.Cloud.Identity.Persistence.Models; +using LIN.Cloud.Identity.Persistence.Extensions; +using LIN.Cloud.Identity.Persistence.Models; using LIN.Types.Cloud.Identity.Models; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; namespace LIN.Cloud.Identity.Persistence.Contexts; @@ -22,7 +24,7 @@ public class DataContext(DbContextOptions options) : DbContext(opti /// /// Organizaciones. /// - public DbSet Organizations { get; set; } + public DbSet Organizations { get; set; } /// @@ -55,6 +57,12 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet AllowApps { get; set; } + /// + /// Restricciones de apps. + /// + public DbSet ApplicationRestrictions { get; set; } + + /// /// Logs de accounts. /// @@ -172,11 +180,22 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(t => t.OrganizationId); }); + // Application restrictions Model + modelBuilder.Entity(entity => + { + entity.ToTable("applications_restrictions"); + entity.HasOne(t => t.Application) + .WithMany(t => t.Restrictions) + .HasForeignKey(t => t.ApplicationId) + .OnDelete(DeleteBehavior.NoAction); + }); + // Application Model modelBuilder.Entity(entity => { entity.ToTable("applications"); entity.HasIndex(t => t.IdentityId).IsUnique(); + entity.HasIndex(t => t.Key).IsUnique(); entity.HasOne(t => t.Identity) .WithMany() @@ -302,4 +321,43 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } + + public void Seed() + { + if (!Accounts.Any()) + { + var jsonData = File.ReadAllText("wwwroot/seeds/users.json"); + var users = JsonConvert.DeserializeObject>(jsonData) ?? []; + + foreach (var user in users) + { + user.Password = Global.Utilities.Cryptography.Encrypt(user.Password); + } + + if (users != null && users.Count > 0) + { + Accounts.AddRange(users); + SaveChanges(); + } + } + + if (!Applications.Any()) + { + var jsonData = File.ReadAllText("wwwroot/seeds/applications.json"); + var apps = JsonConvert.DeserializeObject>(jsonData) ?? []; + + foreach (var app in apps) + { + app.Identity.Type = Types.Cloud.Identity.Enumerations.IdentityType.Application; + app.Owner = new() { Id = app.OwnerId }; + app.Owner = EntityFramework.AttachOrUpdate(this, app.Owner); + } + + if (apps != null && apps.Count > 0) + { + Applications.AddRange(apps); + SaveChanges(); + } + } + } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 6adcb5f..575edb6 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -47,6 +47,9 @@ public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) var scope = app.ApplicationServices.CreateScope(); var context = scope.ServiceProvider.GetService(); context?.Database.EnsureCreated(); + + // Data seed. + context?.Seed(); } catch (Exception ex) { diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index c825bb5..1f5eca7 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -1,17 +1,18 @@  - - net9.0 - enable - enable - Debug;Release;Local;Release-dev;Debug-dev - + + net9.0 + enable + enable + Debug;Release;Local;Release-dev;Debug-dev + - + - + + \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 81133e8..375f130 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -32,6 +32,7 @@ public async Task Create([FromBody] AccountModel? modelo) }; // Organización del modelo. + modelo.AccountType = AccountTypes.Personal; modelo = Services.Formats.Account.Process(modelo); // Creación del usuario. diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs new file mode 100644 index 0000000..6705477 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs @@ -0,0 +1,29 @@ +namespace LIN.Cloud.Identity.Areas.Applications; + +[IdentityToken] +[Route("applications/restrictions")] +public class ApplicationRestrictionsController(Data.ApplicationRestrictions applicationRestrictions) : AuthenticationBaseController +{ + + [HttpPost] + public async Task Create([FromBody] ApplicationRestrictionModel app) + { + + // Si el modelo es nulo. + if (app is null) + return new(Responses.InvalidParam) + { + Errors = [new() { Tittle = "Modelo invalido", Description = "El modelo json es invalido." }] + }; + + app.Application = new() + { + Id = app.ApplicationId + }; + + var create = await applicationRestrictions.Create(app); + + return create; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs new file mode 100644 index 0000000..106f123 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs @@ -0,0 +1,61 @@ +namespace LIN.Cloud.Identity.Areas.Applications; + +[IdentityToken] +[Route("applications")] +public class ApplicationsController(Data.ApplicationRestrictions applicationRestrictions) : AuthenticationBaseController +{ + + /// + /// Crear nueva aplicación. + /// + /// App. + [HttpPost] + public async Task Create([FromBody] ApplicationModel app) + { + + // Si el modelo es nulo. + if (app is null) + return new(Responses.InvalidParam) + { + Errors = [new() { Tittle = "Modelo invalido", Description = "El modelo json es invalido." }] + }; + + // Validar identidad. + if (app.Identity is null) + return new(Responses.InvalidParam) + { + Errors = [new() { Tittle = "Identidad invalida", Description = "La identidad es invalida." }] + }; + + if (string.IsNullOrWhiteSpace(app.Identity.Unique)) + return new(Responses.InvalidParam) + { + Errors = [new() { Tittle = "Unique invalido", Description = "La identidad unica es invalida (No puede ser vacio)." }] + }; + + // Validar otros parametros. + if (string.IsNullOrWhiteSpace(app.Name)) + return new(Responses.InvalidParam) + { + Errors = [new() { Tittle = "Nombre invalido", Description = "El nombre de la aplicación no puede estar vacio." }] + }; + + // Formatear app. + app.Key = Guid.NewGuid(); + app.Restrictions = []; + app.Identity.Type = IdentityType.Application; + app.Identity.Roles = []; + + app.Owner = new() + { + Id = UserInformation.IdentityId + }; + + Services.Formats.Identities.Process(app.Identity); + + var create = await applicationRestrictions.Create(app); + + return create; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index a493837..7de3721 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -57,6 +57,14 @@ public async Task> Login([FromQuery] string us Message = "Contraseña incorrecta." }; + // Contraseña invalida. + case Responses.UnauthorizedByApp: + return new() + { + Response = Responses.UnauthorizedByApp, + Message = "La aplicación no existe o no permite que inicies sesion en este momento." + }; + // Incorrecto default: return new() diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index 69dcecb..cc2ce5b 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -82,6 +82,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr modelo.Visibility = Visibility.Hidden; modelo.Password = $"pwd@{DateTime.Now.Year}"; modelo = Services.Formats.Account.Process(modelo); + modelo.AccountType = AccountTypes.Work; // Organización. var orgIdentity = await organizationsData.GetDomain(organization); diff --git a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs new file mode 100644 index 0000000..40c0d27 --- /dev/null +++ b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs @@ -0,0 +1,147 @@ +namespace LIN.Cloud.Identity.Data; + +public class ApplicationRestrictions(DataContext context) +{ + + + public async Task Create(ApplicationModel modelo) + { + // Pre. + modelo.Id = 0; + + try + { + // Modelo ya existe. + modelo.Owner = context.AttachOrUpdate(modelo.Owner); + + // Guardar la identidad. + await context.Applications.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + LastID = modelo.Id + }; + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + } + + + + /// + /// Crear nueva restriccion de aplicación. + /// + /// Modelo. + public async Task Create(ApplicationRestrictionModel modelo) + { + // Pre. + modelo.Id = 0; + + try + { + // Modelo ya existe. + modelo.Application = context.AttachOrUpdate(modelo.Application); + + // Guardar la identidad. + await context.ApplicationRestrictions.AddAsync(modelo); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + LastID = modelo.Id + }; + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + } + + + /// + /// Obtener las restricciones de aplicacion. + /// + /// Id de la aplicación. + public async Task> ReadAll(int id) + { + + try + { + + var restrictions = await (from ar in context.ApplicationRestrictions + where ar.ApplicationId == id + select ar).ToListAsync(); + + // Success. + return new(Responses.Success, restrictions); + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + } + + + public async Task> ReadAll(string id) + { + + try + { + + var restrictions = await (from ar in context.ApplicationRestrictions + where ar.Application.Key == Guid.Parse(id) + select ar).ToListAsync(); + + // Success. + return new(Responses.Success, restrictions); + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + } + + + public async Task> ExistApp(string id) + { + + try + { + + var restrictions = await (from ar in context.Applications + where ar.Key == Guid.Parse(id) + select ar).AnyAsync(); + + // Success. + return new(Responses.Success, restrictions); + + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Builders/Account.cs b/LIN.Cloud.Identity/Data/Builders/Account.cs index 4b0bfb3..d7590e8 100644 --- a/LIN.Cloud.Identity/Data/Builders/Account.cs +++ b/LIN.Cloud.Identity/Data/Builders/Account.cs @@ -265,7 +265,9 @@ private static IQueryable BuildModel(IQueryable quer Visibility = account.Visibility, Profile = filters.IncludePhoto ? string.Empty : selector, IdentityId = account.Identity.Id, - IdentityService = account.IdentityService + IdentityService = account.IdentityService, + AccountType = account.AccountType, + IsLINAdmin = account.IsLINAdmin }; var s = queryFinal.ToQueryString(); diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 6242ce3..0e93dfb 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -1,24 +1,23 @@  - - net9.0 - enable - enable - false - Debug;Release;Local;Release-dev;Debug-dev - + + net9.0 + enable + enable + false + Debug;Release;Local;Release-dev;Debug-dev + - - - - - - - + + + + + + - - - + + + - + \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index fc6f704..efcb850 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -2,7 +2,7 @@ namespace LIN.Cloud.Identity.Services.Auth; -public class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs) : Interfaces.IAuthentication +public class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions) : Interfaces.IAuthentication { /// @@ -71,6 +71,13 @@ public async Task Start(AuthenticationSettings? settings = null) if (!password) return Responses.InvalidPassword; + // Validar aplicación. + var valApp = await ValidateApp(); + + // Bloqueado por la aplicación. + if (!valApp) + return Responses.UnauthorizedByApp; + if (Settings.Log) await SaveLog(); @@ -100,6 +107,58 @@ private async Task GetAccount() } + + private async Task ValidateApp() + { + + if (Account.IsLINAdmin) + return true; + + // Validar si existe la app. + var appInfo = await applicationRestrictions.ExistApp(AppCode); + + if (appInfo.Response != Responses.Success) + return false; + + var app = await applicationRestrictions.ReadAll(AppCode); + + if (!app.Models.Any()) + return true; + + bool isAuthorized = false; + + foreach (var e in app.Models) + { + + switch (Account.AccountType) + { + case AccountTypes.Personal: + if (e.AllowPersonalAccounts) isAuthorized = true; + break; + case AccountTypes.Work: + if (e.AllowWorkAccounts) isAuthorized = true; + break; + case AccountTypes.Education: + if (e.AllowEducationsAccounts) isAuthorized = true; + break; + } + + // Si la app no permite que sea del tipo de la cuenta. + if (!isAuthorized) + break; + + // Validar horas. + + // Validar identidades. + + } + + // Respuesta. + return isAuthorized; + + } + + /// /// Validar la contraseña. /// diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 84a42b1..9da8a1b 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -27,6 +27,7 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Externos services.AddSingleton(); diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 86a3be9..659c53c 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -68,6 +68,7 @@ public static AccountModel Process(AccountModel baseAccount) Password = Global.Utilities.Cryptography.Encrypt(baseAccount.Password), Visibility = baseAccount.Visibility, IdentityId = 0, + IsLINAdmin = false, Identity = new() { Id = 0, diff --git a/LIN.Cloud.Identity/wwwroot/seeds/applications.json b/LIN.Cloud.Identity/wwwroot/seeds/applications.json new file mode 100644 index 0000000..76a3c09 --- /dev/null +++ b/LIN.Cloud.Identity/wwwroot/seeds/applications.json @@ -0,0 +1,121 @@ +[ + { + "id": 0, + "name": "LIN Inventory", + "key": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "identity": { + "id": 0, + "unique": "inventory#lin", + "creationTime": "2025-02-16T10:00:28.998Z", + "effectiveTime": "2025-02-16T10:00:28.998Z", + "expirationTime": "2045-02-16T15:41:28.998Z", + "status": 0, + "type": 0, + "roles": [] + }, + "ownerId": 1, + "restrictions": [] + }, + { + "id": 0, + "name": "LIN Allo", + "key": "829fa4ce-7e74-4c62-8a1e-9ba04ee7433a", + "identity": { + "id": 0, + "unique": "allo#lin", + "creationTime": "2025-02-16T10:00:28.998Z", + "effectiveTime": "2025-02-16T10:00:28.998Z", + "expirationTime": "2045-02-16T15:41:28.998Z", + "status": 0, + "type": 0, + "roles": [] + }, + "ownerId": 1, + "restrictions": [] + }, + { + "id": 0, + "name": "LIN Calendar", + "key": "03fcf406-c931-4454-b935-1cd9563b277f", + "identity": { + "id": 0, + "unique": "calendar#lin", + "creationTime": "2025-02-16T10:00:28.998Z", + "effectiveTime": "2025-02-16T10:00:28.998Z", + "expirationTime": "2045-02-16T15:41:28.998Z", + "status": 0, + "type": 0, + "roles": [] + }, + "ownerId": 1, + "restrictions": [] + }, + { + "id": 0, + "name": "LIN Notes", + "key": "3d103901-f3c9-49fd-8b86-e6cf5f22b5d6", + "identity": { + "id": 0, + "unique": "notes#lin", + "creationTime": "2025-02-16T10:00:28.998Z", + "effectiveTime": "2025-02-16T10:00:28.998Z", + "expirationTime": "2045-02-16T15:41:28.998Z", + "status": 0, + "type": 0, + "roles": [] + }, + "ownerId": 1, + "restrictions": [] + }, + { + "id": 0, + "name": "LIN Mail", + "key": "30ba6fd1-dff3-4e72-91d3-fb4a46fcbf60", + "identity": { + "id": 0, + "unique": "mail#lin", + "creationTime": "2025-02-16T10:00:28.998Z", + "effectiveTime": "2025-02-16T10:00:28.998Z", + "expirationTime": "2045-02-16T15:41:28.998Z", + "status": 0, + "type": 0, + "roles": [] + }, + "ownerId": 1, + "restrictions": [] + }, + { + "id": 0, + "name": "LIN Vault", + "key": "de7c4f00-3ad0-4911-9c9b-b8a2fbe079bc", + "identity": { + "id": 0, + "unique": "vault#lin", + "creationTime": "2025-02-16T10:00:28.998Z", + "effectiveTime": "2025-02-16T10:00:28.998Z", + "expirationTime": "2045-02-16T15:41:28.998Z", + "status": 0, + "type": 0, + "roles": [] + }, + "ownerId": 1, + "restrictions": [] + }, + { + "id": 0, + "name": "LIN Console", + "key": "ce85a537-41a8-46ad-9ff0-86039850a343", + "identity": { + "id": 0, + "unique": "console#lin", + "creationTime": "2025-02-16T10:00:28.998Z", + "effectiveTime": "2025-02-16T10:00:28.998Z", + "expirationTime": "2045-02-16T15:41:28.998Z", + "status": 0, + "type": 0, + "roles": [] + }, + "ownerId": 1, + "restrictions": [] + } +] \ No newline at end of file diff --git a/LIN.Cloud.Identity/wwwroot/seeds/users.json b/LIN.Cloud.Identity/wwwroot/seeds/users.json new file mode 100644 index 0000000..818628c --- /dev/null +++ b/LIN.Cloud.Identity/wwwroot/seeds/users.json @@ -0,0 +1,21 @@ +[ + { + "id": 0, + "name": "LIN General Admin", + "profile": "", + "password": "default", + "visibility": 0, + "identityService": 0, + "islinadmin": true, + "identity": { + "id": 0, + "unique": "linadmin", + "creationTime": "2025-02-16T10:00:37.735Z", + "effectiveTime": "2025-02-16T10:00:37.735Z", + "expirationTime": "2035-02-16T15:20:37.735Z", + "status": 0, + "type": 0, + "roles": [] + } + } +] \ No newline at end of file diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj index 0494b82..94a5a57 100644 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ b/LIN.Identity.Tests/LIN.Identity.Tests.csproj @@ -1,40 +1,40 @@  - - net9.0 - enable - enable - false - true - Debug;Release;Local;Release-dev;Debug-dev - + + net9.0 + enable + enable + false + true + Debug;Release;Local;Release-dev;Debug-dev + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - + + + - - - + + + - - - + + + - + \ No newline at end of file From b3188554cfb27110128968551705c9e2d7379730 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 11:16:33 -0500 Subject: [PATCH 145/178] =?UTF-8?q?Mejoras=20de=20optimizaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LIN.Cloud.Identity/Data/AccountLogs.cs | 12 +++++-- .../Data/ApplicationRestrictions.cs | 25 +++++++++++++++ .../Services/Auth/Authentication.cs | 31 +++++++++++++------ .../Services/Realtime/PassKeyHub.cs | 4 +-- 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/LIN.Cloud.Identity/Data/AccountLogs.cs b/LIN.Cloud.Identity/Data/AccountLogs.cs index 1ff807c..f9c16dd 100644 --- a/LIN.Cloud.Identity/Data/AccountLogs.cs +++ b/LIN.Cloud.Identity/Data/AccountLogs.cs @@ -16,8 +16,16 @@ public async Task Create(AccountLog log) Id = log.AccountId }; context.Attach(log.Account); - log.Application = null; - log.ApplicationId = null; + + if (log.Application is null) + { + log.Application = null; + log.ApplicationId = null; + } + else + { + log.Application = context.AttachOrUpdate(log.Application); + } // Guardar la cuenta. await context.AccountLogs.AddAsync(log); diff --git a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs index 40c0d27..b0644d1 100644 --- a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs +++ b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs @@ -121,6 +121,31 @@ public async Task> ReadAll(string i } + + public async Task> Read(string id) + { + + try + { + + var restrictions = await (from ar in context.Applications + where ar.Key == Guid.Parse(id) + select ar).FirstOrDefaultAsync(); + + // Success. + return new(restrictions is null ? Responses.NotRows : Responses.Success, restrictions); + + } + catch (Exception) + { + return new() + { + Response = Responses.ExistAccount + }; + } + } + + public async Task> ExistApp(string id) { diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index efcb850..9c8fd89 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -29,6 +29,12 @@ public class Authentication(Data.Accounts accountData, Data.AccountLogs accountL public AccountModel? Account { get; set; } = null; + /// + /// Aplicación + /// + public ApplicationModel? Application { get; set; } = null; + + /// /// Ajustes. /// @@ -40,7 +46,7 @@ public class Authentication(Data.Accounts accountData, Data.AccountLogs accountL /// /// Usuario. /// Contraseña. - /// Código de app. + /// Código de restrictions. public void SetCredentials(string username, string password, string appCode) { this.User = username; @@ -111,23 +117,27 @@ private async Task GetAccount() private async Task ValidateApp() { - if (Account.IsLINAdmin) - return true; + // Obtener la restrictions. + var appResponse = await applicationRestrictions.Read(AppCode); + Application = appResponse.Model; - // Validar si existe la app. - var appInfo = await applicationRestrictions.ExistApp(AppCode); + // Si no se requiere validar la restrictions. + if (Account!.IsLINAdmin || !Settings.ValidateApp) + return true; - if (appInfo.Response != Responses.Success) + // Validar si la restrictions existe. + if (appResponse.Response != Responses.Success) return false; - var app = await applicationRestrictions.ReadAll(AppCode); + // Obtener las restricciones. + var restrictions = await applicationRestrictions.ReadAll(AppCode); - if (!app.Models.Any()) + if (restrictions.Models.Count == 0) return true; bool isAuthorized = false; - foreach (var e in app.Models) + foreach (var e in restrictions.Models) { switch (Account.AccountType) @@ -143,7 +153,7 @@ private async Task ValidateApp() break; } - // Si la app no permite que sea del tipo de la cuenta. + // Si la restrictions no permite que sea del tipo de la cuenta. if (!isAuthorized) break; @@ -189,6 +199,7 @@ await accountLogs.Create(new() AccountId = Account!.Id, AuthenticationMethod = AuthenticationMethods.Password, Time = DateTime.Now, + Application = Application }); } diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 718b2d6..9e3810f 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -14,13 +14,13 @@ public partial class PassKeyHub(Data.AccountLogs accountLogs) : Hub /// /// Canal de intentos. /// - public static readonly string AttemptsChannel = "#attempts"; + public const string AttemptsChannel = "#attempts"; /// /// Canal de respuestas. /// - public static readonly string ResponseChannel = "#responses"; + public const string ResponseChannel = "#responses"; /// From 5b6b8fbc5c88875372442b21628d1295542cae84 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 12:26:00 -0500 Subject: [PATCH 146/178] up --- LIN.Cloud.Identity/wwwroot/seeds/applications.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LIN.Cloud.Identity/wwwroot/seeds/applications.json b/LIN.Cloud.Identity/wwwroot/seeds/applications.json index 76a3c09..6705985 100644 --- a/LIN.Cloud.Identity/wwwroot/seeds/applications.json +++ b/LIN.Cloud.Identity/wwwroot/seeds/applications.json @@ -103,7 +103,7 @@ }, { "id": 0, - "name": "LIN Console", + "name": "LIN Cloud Service", "key": "ce85a537-41a8-46ad-9ff0-86039850a343", "identity": { "id": 0, From 916accd1b930ada5cd79509932fb99686ad6db50 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 12:33:16 -0500 Subject: [PATCH 147/178] Seed Update --- .../wwwroot/seeds/applications.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/LIN.Cloud.Identity/wwwroot/seeds/applications.json b/LIN.Cloud.Identity/wwwroot/seeds/applications.json index 6705985..508b9a5 100644 --- a/LIN.Cloud.Identity/wwwroot/seeds/applications.json +++ b/LIN.Cloud.Identity/wwwroot/seeds/applications.json @@ -117,5 +117,22 @@ }, "ownerId": 1, "restrictions": [] + }, + { + "id": 0, + "name": "LIN Contacts", + "key": "6101a3bd-b1fb-4815-afc2-1e9a625ba09d", + "identity": { + "id": 0, + "unique": "contacts#lin", + "creationTime": "2025-02-16T10:00:28.998Z", + "effectiveTime": "2025-02-16T10:00:28.998Z", + "expirationTime": "2045-02-16T15:41:28.998Z", + "status": 0, + "type": 0, + "roles": [] + }, + "ownerId": 1, + "restrictions": [] } ] \ No newline at end of file From c20b74c2f69270c2c18c60a46fcfd15de552f732 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 12:44:19 -0500 Subject: [PATCH 148/178] Comentarios --- .../Contexts/DataContext.cs | 13 +++++++++++-- .../Extensions/PersistenceExtensions.cs | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 997efdb..b12d613 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -322,18 +322,23 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) } + /// + /// Data primaria. + /// public void Seed() { + + // Si no hay cuentas. if (!Accounts.Any()) { + // Obtener la data. var jsonData = File.ReadAllText("wwwroot/seeds/users.json"); var users = JsonConvert.DeserializeObject>(jsonData) ?? []; foreach (var user in users) - { user.Password = Global.Utilities.Cryptography.Encrypt(user.Password); - } + // Agregar los modelos. if (users != null && users.Count > 0) { Accounts.AddRange(users); @@ -341,11 +346,14 @@ public void Seed() } } + // Si no hay aplicaciones. if (!Applications.Any()) { + // Obtener la data. var jsonData = File.ReadAllText("wwwroot/seeds/applications.json"); var apps = JsonConvert.DeserializeObject>(jsonData) ?? []; + // Formatear modelos. foreach (var app in apps) { app.Identity.Type = Types.Cloud.Identity.Enumerations.IdentityType.Application; @@ -353,6 +361,7 @@ public void Seed() app.Owner = EntityFramework.AttachOrUpdate(this, app.Owner); } + // Agregar aplicaciones. if (apps != null && apps.Count > 0) { Applications.AddRange(apps); diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 575edb6..4055a3c 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -46,7 +46,7 @@ public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) { var scope = app.ApplicationServices.CreateScope(); var context = scope.ServiceProvider.GetService(); - context?.Database.EnsureCreated(); + bool? created = context?.Database.EnsureCreated(); // Data seed. context?.Seed(); From 5a96dd2fa8353fb49bfedb4833cd767159233e65 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 13:09:49 -0500 Subject: [PATCH 149/178] Update --- .../Areas/Accounts/AccountLogs.cs | 15 ++- .../Applications/ApplicationsController.cs | 4 +- LIN.Cloud.Identity/Data/AccountLogs.cs | 88 +++++++------- LIN.Cloud.Identity/Data/Accounts.cs | 1 - LIN.Cloud.Identity/Data/AllowApps.cs | 6 +- .../Data/ApplicationRestrictions.cs | 109 ++---------------- LIN.Cloud.Identity/Data/Applications.cs | 84 ++++++++++++++ LIN.Cloud.Identity/Data/DirectoryMembers.cs | 24 +--- LIN.Cloud.Identity/Data/Mails.cs | 32 ++--- LIN.Cloud.Identity/Data/Organizations.cs | 1 - .../Services/Auth/Authentication.cs | 4 +- 11 files changed, 175 insertions(+), 193 deletions(-) create mode 100644 LIN.Cloud.Identity/Data/Applications.cs diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs index 8c5b1c8..6eb4a78 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs @@ -10,10 +10,21 @@ public class AccountLogsController(Data.AccountLogs accountData) : Authenticatio /// /// Lista de logs. [HttpGet] - public async Task> ReadAll() + public async Task> ReadAll(DateTime? start, DateTime? end) { + + // Fechas por defecto. + start ??= DateTime.MinValue; + end ??= DateTime.MaxValue; + + if (end < start) + return new(Responses.InvalidParam) + { + Message = "La fecha de fin debe ser mayor a la fecha de inicio." + }; + // Obtiene el usuario - var response = await accountData.ReadAll(UserInformation.AccountId); + var response = await accountData.ReadAll(UserInformation.AccountId, start, end); return response; } diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs index 106f123..e7a7afe 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs @@ -2,7 +2,7 @@ [IdentityToken] [Route("applications")] -public class ApplicationsController(Data.ApplicationRestrictions applicationRestrictions) : AuthenticationBaseController +public class ApplicationsController(Data.Applications application) : AuthenticationBaseController { /// @@ -53,7 +53,7 @@ public async Task Create([FromBody] ApplicationModel app) Services.Formats.Identities.Process(app.Identity); - var create = await applicationRestrictions.Create(app); + var create = await application.Create(app); return create; } diff --git a/LIN.Cloud.Identity/Data/AccountLogs.cs b/LIN.Cloud.Identity/Data/AccountLogs.cs index f9c16dd..698cc43 100644 --- a/LIN.Cloud.Identity/Data/AccountLogs.cs +++ b/LIN.Cloud.Identity/Data/AccountLogs.cs @@ -3,79 +3,77 @@ public class AccountLogs(DataContext context) { + /// + /// Crear un log de inicio de sesión. + /// + /// Modelo. public async Task Create(AccountLog log) { - // Pre. + // Formato del modelo. log.Id = 0; try { - log.Account = new() - { - Id = log.AccountId - }; - context.Attach(log.Account); - if (log.Application is null) - { - log.Application = null; - log.ApplicationId = null; - } - else - { + // Organizar el modelo. + log.Account = new() { Id = log.AccountId }; + + // Ya existe. + log.Account = context.AttachOrUpdate(log.Account); + + // Si hay una app. + if (log.Application is not null) log.Application = context.AttachOrUpdate(log.Application); - } + else + log.ApplicationId = 0; // Guardar la cuenta. await context.AccountLogs.AddAsync(log); context.SaveChanges(); - return new() - { - Response = Responses.Success, - LastID = log.Id - }; - + return new(Responses.Success, log.Id); } catch (Exception) { - return new() - { - Response = Responses.Undefined - }; + return new(Responses.Undefined); } - } - public async Task> ReadAll(int accountId) + /// + /// Obtener los logs de inicio de sesión. + /// + /// Id de la cuenta. + /// Fecha de inicio. + /// Fecha de fin. + public async Task> ReadAll(int accountId, DateTime? start, DateTime? end) { - try { - - var x = await context.AccountLogs - .Where(t => t.AccountId == accountId) - .OrderByDescending(t => t.Time) - .Take(20) - .ToListAsync(); - - return new() - { - Models = x, - Response = Responses.Success - }; - + var logs = await (from log in context.AccountLogs + where log.AccountId == accountId + && log.Time > start && log.Time < end + select new AccountLog + { + Id = log.Id, + Time = log.Time, + AccountId = log.AccountId, + Application = new() + { + Name = log.Application!.Name, + IdentityId = log.Application.IdentityId, + }, + ApplicationId = log.ApplicationId, + AuthenticationMethod = log.AuthenticationMethod + }).ToListAsync(); + + return new(Responses.Success, logs); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(Responses.Undefined); } - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index 634ac4a..c344b2c 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -316,5 +316,4 @@ public async Task UpdatePassword(int accountId, string password) } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/AllowApps.cs b/LIN.Cloud.Identity/Data/AllowApps.cs index 2f9c272..2005edb 100644 --- a/LIN.Cloud.Identity/Data/AllowApps.cs +++ b/LIN.Cloud.Identity/Data/AllowApps.cs @@ -1,6 +1,6 @@ namespace LIN.Cloud.Identity.Data; -public class AllowApps(DataContext context, LIN.Cloud.Identity.Services.Utils.IIdentityService identityService) +public class AllowApps(DataContext context, IIdentityService identityService) { /// @@ -15,7 +15,6 @@ public async Task> Create(AllowApp modelo) try { - // Attach. context.Attach(modelo.Application); context.Attach(modelo.Identity); @@ -47,8 +46,9 @@ public async Task> Create(AllowApp modelo) /// - /// Obtener las apps a las que una identidad tiene acceso o no. + /// btener las apps a las que una identidad tiene acceso o no. /// + /// Id de la identidad. public async Task> ReadAll(int id) { diff --git a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs index b0644d1..9a6dcd8 100644 --- a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs +++ b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs @@ -3,38 +3,6 @@ public class ApplicationRestrictions(DataContext context) { - - public async Task Create(ApplicationModel modelo) - { - // Pre. - modelo.Id = 0; - - try - { - // Modelo ya existe. - modelo.Owner = context.AttachOrUpdate(modelo.Owner); - - // Guardar la identidad. - await context.Applications.AddAsync(modelo); - context.SaveChanges(); - - return new() - { - Response = Responses.Success, - LastID = modelo.Id - }; - } - catch (Exception) - { - return new() - { - Response = Responses.Undefined - }; - } - } - - - /// /// Crear nueva restriccion de aplicación. /// @@ -52,19 +20,11 @@ public async Task Create(ApplicationRestrictionModel modelo) // Guardar la identidad. await context.ApplicationRestrictions.AddAsync(modelo); context.SaveChanges(); - - return new() - { - Response = Responses.Success, - LastID = modelo.Id - }; + return new(Responses.Success, modelo.Id); } catch (Exception) { - return new() - { - Response = Responses.Undefined - }; + return new(Responses.Undefined); } } @@ -75,7 +35,6 @@ public async Task Create(ApplicationRestrictionModel modelo) /// Id de la aplicación. public async Task> ReadAll(int id) { - try { @@ -89,17 +48,17 @@ public async Task> ReadAll(int id) } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(Responses.Undefined); } } + /// + /// Obtener las restricciones de aplicacion. + /// + /// Id de la aplicación. public async Task> ReadAll(string id) { - try { @@ -113,59 +72,7 @@ public async Task> ReadAll(string i } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; - } - } - - - - public async Task> Read(string id) - { - - try - { - - var restrictions = await (from ar in context.Applications - where ar.Key == Guid.Parse(id) - select ar).FirstOrDefaultAsync(); - - // Success. - return new(restrictions is null ? Responses.NotRows : Responses.Success, restrictions); - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - } - - - public async Task> ExistApp(string id) - { - - try - { - - var restrictions = await (from ar in context.Applications - where ar.Key == Guid.Parse(id) - select ar).AnyAsync(); - - // Success. - return new(Responses.Success, restrictions); - - } - catch (Exception) - { - return new() - { - Response = Responses.Undefined - }; + return new(Responses.Undefined); } } diff --git a/LIN.Cloud.Identity/Data/Applications.cs b/LIN.Cloud.Identity/Data/Applications.cs new file mode 100644 index 0000000..9ea2fd5 --- /dev/null +++ b/LIN.Cloud.Identity/Data/Applications.cs @@ -0,0 +1,84 @@ +namespace LIN.Cloud.Identity.Data; + +public class Applications(DataContext context) +{ + + /// + /// Crear una nueva aplicación. + /// + /// Modelo. + public async Task Create(ApplicationModel modelo) + { + // Pre. + modelo.Id = 0; + + try + { + // Modelo ya existe. + modelo.Owner = context.AttachOrUpdate(modelo.Owner); + + // Guardar la identidad. + await context.Applications.AddAsync(modelo); + context.SaveChanges(); + + return new(Responses.Success, modelo.Id); + } + catch (Exception) + { + return new(Responses.Undefined); + } + } + + + /// + /// Obtener una aplicación. + /// + /// Key de la app. + public async Task> Read(string key) + { + try + { + + // Obetener el modelo. + var application = await (from ar in context.Applications + where ar.Key == Guid.Parse(key) + select ar).FirstOrDefaultAsync(); + + // Success. + return new(application is null ? Responses.NotRows : Responses.Success, application! ); + + } + catch (Exception) + { + return new(Responses.Undefined); + } + } + + + /// + /// Validar si existe una app. + /// + /// Key de la app. + public async Task> ExistApp(string key) + { + try + { + + var exist = await (from ar in context.Applications + where ar.Key == Guid.Parse(key) + select ar).AnyAsync(); + + // Success. + return new(Responses.Success, exist); + + } + catch (Exception) + { + return new() + { + Response = Responses.Undefined + }; + } + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/DirectoryMembers.cs b/LIN.Cloud.Identity/Data/DirectoryMembers.cs index 3aa93b5..00669b0 100644 --- a/LIN.Cloud.Identity/Data/DirectoryMembers.cs +++ b/LIN.Cloud.Identity/Data/DirectoryMembers.cs @@ -2,24 +2,18 @@ namespace LIN.Cloud.Identity.Data; - public class DirectoryMembers(DataContext context) { - /// /// Obtener los directorios (Grupos de organización) donde una identidad pertenece. /// /// Identidad /// Organización de contexto. - /// Contexto. public async Task> Read(int id, int organization) { - try { - - var members = await (from gm in context.GroupMembers where gm.IdentityId == id join o in context.Organizations @@ -36,31 +30,19 @@ on gm.GroupId equals o.DirectoryId // Si la cuenta no existe. if (members == null) - return new() - { - Response = Responses.NotRows - }; + return new(Responses.NotRows); // Success. - return new() - { - Response = Responses.Success, - Models = members - }; - + return new(Responses.Success, members); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(Responses.Undefined); } } - /// /// Valida si una identidad es miembro de una organización. /// diff --git a/LIN.Cloud.Identity/Data/Mails.cs b/LIN.Cloud.Identity/Data/Mails.cs index 1dab4e9..84e1966 100644 --- a/LIN.Cloud.Identity/Data/Mails.cs +++ b/LIN.Cloud.Identity/Data/Mails.cs @@ -3,6 +3,10 @@ public class Mails(DataContext context) { + /// + /// Crear modelo de resultado de mail. + /// + /// Modelo. public async Task> Create(MailModel modelo) { try @@ -28,24 +32,17 @@ public async Task> Create(MailModel modelo) }; } - catch (Exception ex) + catch (Exception) { - - - return new() - { - Response = Responses.ResourceExist - }; + return new(Responses.ResourceExist); } - - return new() - { - Response = Responses.Undefined - }; - } + /// + /// Obtener el correo principal. + /// + /// Usuario unico. public async Task> ReadPrincipal(string unique) { try @@ -84,6 +81,13 @@ where mail.IsPrincipal } + /// + /// Validar otp para un correo. + /// + /// Correo. + /// + /// + [Obsolete("Revisar el metodo.")] public async Task ValidateOtpFormail(string email, string code) { try @@ -138,6 +142,4 @@ on mail.Id equals otp.MailId } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index 0df0229..d3e9490 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -2,7 +2,6 @@ namespace LIN.Cloud.Identity.Data; - public class Organizations(DataContext context, IamRoles iam) { diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 9c8fd89..fd92bc3 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -2,7 +2,7 @@ namespace LIN.Cloud.Identity.Services.Auth; -public class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions) : Interfaces.IAuthentication +public class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions, Data.Applications applications) : Interfaces.IAuthentication { /// @@ -118,7 +118,7 @@ private async Task ValidateApp() { // Obtener la restrictions. - var appResponse = await applicationRestrictions.Read(AppCode); + var appResponse = await applications.Read(AppCode); Application = appResponse.Model; // Si no se requiere validar la restrictions. From 2daaeb4953bfe497cb0fc9d922d7d32f35e658ea Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 13:18:24 -0500 Subject: [PATCH 150/178] Update --- .../Areas/Accounts/AccountLogs.cs | 1 + .../ApplicationRestrictionsController.cs | 7 +- .../Authentication/SecurityController.cs | 2 +- .../Areas/Policies/PoliciesController.cs | 2 +- LIN.Cloud.Identity/Data/Accounts.cs | 4 +- LIN.Cloud.Identity/Data/Applications.cs | 10 +-- LIN.Cloud.Identity/Data/Mails.cs | 66 ++++++------------- LIN.Cloud.Identity/Data/OtpService.cs | 4 +- .../Services/Extensions/LocalServices.cs | 1 - LIN.Cloud.Identity/Usings.cs | 7 +- LIN.Identity.Tests/Data/Accounts.cs | 1 - 11 files changed, 41 insertions(+), 64 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs index 6eb4a78..4ab5814 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs @@ -17,6 +17,7 @@ public async Task> ReadAll(DateTime? start, Date start ??= DateTime.MinValue; end ??= DateTime.MaxValue; + // Validar el rango de fecha. if (end < start) return new(Responses.InvalidParam) { diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs index 6705477..f6e447f 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs @@ -5,6 +5,10 @@ public class ApplicationRestrictionsController(Data.ApplicationRestrictions applicationRestrictions) : AuthenticationBaseController { + /// + /// Crear restriccion. + /// + /// Modelo. [HttpPost] public async Task Create([FromBody] ApplicationRestrictionModel app) { @@ -16,13 +20,14 @@ public async Task Create([FromBody] ApplicationRestrictionMo Errors = [new() { Tittle = "Modelo invalido", Description = "El modelo json es invalido." }] }; + // Modelo. app.Application = new() { Id = app.ApplicationId }; + // Crear restricción. var create = await applicationRestrictions.Create(app); - return create; } diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index 47a092d..659ca55 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -90,7 +90,7 @@ public async Task AddMail([FromQuery] string email) public async Task Validate([FromQuery] string mail, [FromQuery] string code) { // Validar OTP. - var response = await mails.ValidateOtpFormail(mail, code); + var response = await mails.ValidateOtpForMail(mail, code); return response; } diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index aa92be1..ec8319e 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -111,7 +111,7 @@ public async Task Delete([FromQuery] string policy) } - + [HttpGet] public async Task> Read([FromQuery] string policy) { diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index c344b2c..341a3aa 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -298,8 +298,8 @@ public async Task UpdatePassword(int accountId, string password) try { var account = await (from a in context.Accounts - where a.Id == accountId - select a).ExecuteUpdateAsync(Accounts => Accounts.SetProperty(t=>t.Password, password)); + where a.Id == accountId + select a).ExecuteUpdateAsync(Accounts => Accounts.SetProperty(t => t.Password, password)); if (account <= 0) return new(Responses.NotExistAccount); diff --git a/LIN.Cloud.Identity/Data/Applications.cs b/LIN.Cloud.Identity/Data/Applications.cs index 9ea2fd5..e207cb0 100644 --- a/LIN.Cloud.Identity/Data/Applications.cs +++ b/LIN.Cloud.Identity/Data/Applications.cs @@ -41,11 +41,11 @@ public async Task> Read(string key) // Obetener el modelo. var application = await (from ar in context.Applications - where ar.Key == Guid.Parse(key) - select ar).FirstOrDefaultAsync(); + where ar.Key == Guid.Parse(key) + select ar).FirstOrDefaultAsync(); // Success. - return new(application is null ? Responses.NotRows : Responses.Success, application! ); + return new(application is null ? Responses.NotRows : Responses.Success, application!); } catch (Exception) @@ -65,8 +65,8 @@ public async Task> ExistApp(string key) { var exist = await (from ar in context.Applications - where ar.Key == Guid.Parse(key) - select ar).AnyAsync(); + where ar.Key == Guid.Parse(key) + select ar).AnyAsync(); // Success. return new(Responses.Success, exist); diff --git a/LIN.Cloud.Identity/Data/Mails.cs b/LIN.Cloud.Identity/Data/Mails.cs index 84e1966..6ad19c1 100644 --- a/LIN.Cloud.Identity/Data/Mails.cs +++ b/LIN.Cloud.Identity/Data/Mails.cs @@ -86,58 +86,34 @@ where mail.IsPrincipal /// /// Correo. /// - /// - [Obsolete("Revisar el metodo.")] - public async Task ValidateOtpFormail(string email, string code) + public async Task ValidateOtpForMail(string email, string code) { try { - var x = (from mail in context.Mails - join otp in context.MailOtp - on mail.Id equals otp.MailId - where otp.OtpDatabaseModel.Code == code - && otp.OtpDatabaseModel.IsUsed == false - && otp.OtpDatabaseModel.ExpireTime > DateTime.Now - select otp); - - - var xx = await x.Select(t => t.MailModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsVerified, true)); - var xx2 = await x.Select(t=>t.OtpDatabaseModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsUsed, true)); - - - if (xx2 <= 0) - return new() - { - Response = Responses.NotRows - }; - - - var acf =await (from mail in context.Mails - select mail.AccountId).FirstOrDefaultAsync(); - - - var exist = await (from m in context.Mails - where m.AccountId == acf - && m.IsPrincipal - select m).AnyAsync(); - - if (!exist) - await x.Select(t => t.MailModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsPrincipal, true)); - - - return new() - { - Response = Responses.Success - }; - + // Obtener modelo. + var otpModel = (from mail in context.Mails + where mail.Mail == email + join otp in context.MailOtp + on mail.Id equals otp.MailId + where otp.OtpDatabaseModel.Code == code + && otp.OtpDatabaseModel.IsUsed == false + && otp.OtpDatabaseModel.ExpireTime > DateTime.Now + select otp); + + // Actualizar. + await otpModel.Select(t => t.MailModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsVerified, true)); + int countUpdate = await otpModel.Select(t => t.OtpDatabaseModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsUsed, true)); + + // Si no se actualizaron. + if (countUpdate <= 0) + return new(Responses.NotRows); + + return new(Responses.Success); } catch (Exception) { - return new() - { - Response = Responses.Undefined - }; + return new(Responses.Undefined); } } diff --git a/LIN.Cloud.Identity/Data/OtpService.cs b/LIN.Cloud.Identity/Data/OtpService.cs index 908c330..41c0d02 100644 --- a/LIN.Cloud.Identity/Data/OtpService.cs +++ b/LIN.Cloud.Identity/Data/OtpService.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Models; - -namespace LIN.Cloud.Identity.Data; +namespace LIN.Cloud.Identity.Data; public class OtpService(DataContext context) { diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 9da8a1b..c06be44 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -1,5 +1,4 @@ using LIN.Cloud.Identity.Services.Auth.Interfaces; -using LIN.Cloud.Identity.Services.Utils; namespace LIN.Cloud.Identity.Services.Extensions; diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 22502d7..9548ed7 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -3,12 +3,15 @@ // Tipos locales. global using LIN.Cloud.Identity.Persistence.Contexts; global using LIN.Cloud.Identity.Persistence.Extensions; +global using LIN.Cloud.Identity.Persistence.Models; global using LIN.Cloud.Identity.Services.Auth; global using LIN.Cloud.Identity.Services.Filters; global using LIN.Cloud.Identity.Services.Iam; global using LIN.Cloud.Identity.Services.Models; +global using LIN.Cloud.Identity.Services.Utils; global using LIN.Types.Cloud.Identity.Enumerations; global using LIN.Types.Cloud.Identity.Models; +global using LIN.Types.Enumerations; // Tipos Generales global using LIN.Types.Responses; global using Microsoft.AspNetCore.Mvc; @@ -23,7 +26,3 @@ global using System.Security.Claims; // Framework. global using System.Text; -global using LIN.Types.Enumerations; -global using Http.Attributes; -global using LIN.Cloud.Identity.Persistence.Models; -global using LIN.Cloud.Identity.Services.Utils; \ No newline at end of file diff --git a/LIN.Identity.Tests/Data/Accounts.cs b/LIN.Identity.Tests/Data/Accounts.cs index b7bff05..6cbd9bd 100644 --- a/LIN.Identity.Tests/Data/Accounts.cs +++ b/LIN.Identity.Tests/Data/Accounts.cs @@ -1,6 +1,5 @@ using LIN.Cloud.Identity.Persistence.Contexts; using LIN.Cloud.Identity.Persistence.Models; -using LIN.Cloud.Identity.Services.Models; using LIN.Types.Cloud.Identity.Models; using LIN.Types.Responses; using Microsoft.EntityFrameworkCore; From c7b98e436084dc97c1469e11690915b17ffe9e17 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 14:54:53 -0500 Subject: [PATCH 151/178] Update --- .../Contexts/DataContext.cs | 27 ++++++++++++++----- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../Applications/ApplicationsController.cs | 2 +- LIN.Cloud.Identity/Data/AllowApps.cs | 7 ++--- .../Services/Auth/AllowService.cs | 2 +- .../Services/Auth/Authentication.cs | 19 +++++++++++++ 6 files changed, 45 insertions(+), 14 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index b12d613..88ff03b 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -185,9 +185,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { entity.ToTable("applications_restrictions"); entity.HasOne(t => t.Application) - .WithMany(t => t.Restrictions) - .HasForeignKey(t => t.ApplicationId) - .OnDelete(DeleteBehavior.NoAction); + .WithOne(t => t.Restriction) + .HasForeignKey(t=>t.RestrictionId); }); // Application Model @@ -205,17 +204,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .WithMany() .HasForeignKey(t => t.OwnerId) .OnDelete(DeleteBehavior.NoAction); + + entity.HasOne(t => t.Restriction) + .WithOne(t => t.Application) + .HasForeignKey(t => t.ApplicationId); }); // Allow Apps Model modelBuilder.Entity(entity => { entity.ToTable("allow_apps"); - entity.HasKey(t => new { t.ApplicationId, t.IdentityId }); + entity.HasKey(t => new { t.ApplicationRestrictionId, t.IdentityId }); entity.HasOne(t => t.Application) - .WithMany() - .HasForeignKey(t => t.ApplicationId) + .WithMany(t=>t.Allowed) + .HasForeignKey(t => t.ApplicationRestrictionId) .OnDelete(DeleteBehavior.NoAction); entity.HasOne(t => t.Identity) @@ -224,6 +227,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.NoAction); }); + // Times. + modelBuilder.Entity(entity => + { + entity.ToTable("restrict_times"); + + entity.HasOne(t => t.ApplicationRestrictionModel) + .WithMany(t => t.Times) + .HasForeignKey(t => t.ApplicationRestrictionId) + .OnDelete(DeleteBehavior.NoAction); + + }); + // Account Logs Model modelBuilder.Entity(entity => { diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 1f5eca7..95275e6 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs index e7a7afe..d1c50b5 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs @@ -42,7 +42,7 @@ public async Task Create([FromBody] ApplicationModel app) // Formatear app. app.Key = Guid.NewGuid(); - app.Restrictions = []; + app.Restriction = new(); app.Identity.Type = IdentityType.Application; app.Identity.Roles = []; diff --git a/LIN.Cloud.Identity/Data/AllowApps.cs b/LIN.Cloud.Identity/Data/AllowApps.cs index 2005edb..9176777 100644 --- a/LIN.Cloud.Identity/Data/AllowApps.cs +++ b/LIN.Cloud.Identity/Data/AllowApps.cs @@ -62,15 +62,12 @@ public async Task> ReadAll(int id) where identities.Contains(allow.IdentityId) select new AllowApp { - ApplicationId = allow.ApplicationId, + ApplicationRestrictionId = allow.ApplicationRestrictionId, IdentityId = allow.IdentityId, IsAllow = allow.IsAllow, Application = new() { - Id = allow.Application.Id, - IdentityId = allow.Application.IdentityId, - Name = allow.Application.Name, - OwnerId = allow.Application.OwnerId, + Id = allow.Application.Id } }).ToListAsync(); diff --git a/LIN.Cloud.Identity/Services/Auth/AllowService.cs b/LIN.Cloud.Identity/Services/Auth/AllowService.cs index 51a9c9a..1ac666e 100644 --- a/LIN.Cloud.Identity/Services/Auth/AllowService.cs +++ b/LIN.Cloud.Identity/Services/Auth/AllowService.cs @@ -15,7 +15,7 @@ public async Task IsAllow(IEnumerable identities, int appId) // Consulta. var isAllow = await (from allow in context.AllowApps - where allow.ApplicationId == appId + where allow.Application.ApplicationId == appId && identities.Contains(allow.Identity.Id) select allow.IsAllow).ToListAsync(); diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index fd92bc3..6083cab 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -169,6 +169,25 @@ private async Task ValidateApp() } + + private bool ValidateRestrictions(List restrictions) + { + + DateTime now = DateTime.Now; + + foreach (var restriction in restrictions) + { + + + + } + + + + return true; + } + + /// /// Validar la contraseña. /// From 41d5db136025b338535cefb0f520bcf42c0df0c2 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 15:56:58 -0500 Subject: [PATCH 152/178] Mejoras --- .../Contexts/DataContext.cs | 6 ++ .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../Data/ApplicationRestrictions.cs | 22 ++--- .../Services/Auth/Authentication.cs | 90 +++++++++++-------- .../Services/Extensions/LocalServices.cs | 1 + .../wwwroot/seeds/applications.json | 56 ++++++++++-- 6 files changed, 119 insertions(+), 58 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 88ff03b..018c55a 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -93,6 +93,12 @@ public class DataContext(DbContextOptions options) : DbContext(opti public DbSet OTPs { get; set; } + /// + /// Restricciones de tiempo. + /// + public DbSet TimeRestriction { get; set; } + + /// /// Correos asociados a las cuentas. /// diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 95275e6..b81ec08 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs index 9a6dcd8..70dc013 100644 --- a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs +++ b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs @@ -33,17 +33,17 @@ public async Task Create(ApplicationRestrictionModel modelo) /// Obtener las restricciones de aplicacion. /// /// Id de la aplicación. - public async Task> ReadAll(int id) + public async Task> Read(string id) { try { - var restrictions = await (from ar in context.ApplicationRestrictions - where ar.ApplicationId == id - select ar).ToListAsync(); + var restriction = await (from ar in context.ApplicationRestrictions + where ar.Application.Key == Guid.Parse(id) + select ar).FirstOrDefaultAsync(); // Success. - return new(Responses.Success, restrictions); + return new(Responses.Success, restriction!); } catch (Exception) @@ -53,18 +53,14 @@ public async Task> ReadAll(int id) } - /// - /// Obtener las restricciones de aplicacion. - /// - /// Id de la aplicación. - public async Task> ReadAll(string id) + public async Task> ReadTimes(int id) { try { - var restrictions = await (from ar in context.ApplicationRestrictions - where ar.Application.Key == Guid.Parse(id) - select ar).ToListAsync(); + var restrictions = await (from tr in context.TimeRestriction + where tr.ApplicationRestrictionModel.ApplicationId == id + select tr).ToListAsync(); // Success. return new(Responses.Success, restrictions); diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 6083cab..3a74d3e 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -2,7 +2,7 @@ namespace LIN.Cloud.Identity.Services.Auth; -public class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions, Data.Applications applications) : Interfaces.IAuthentication +public class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions, Data.Applications applications, Utils.IdentityService identityService, AllowService allowService) : Interfaces.IAuthentication { /// @@ -130,61 +130,79 @@ private async Task ValidateApp() return false; // Obtener las restricciones. - var restrictions = await applicationRestrictions.ReadAll(AppCode); + var restriction = await applicationRestrictions.Read(AppCode); - if (restrictions.Models.Count == 0) - return true; + var x = await ValidateRestrictions(restriction.Model); - bool isAuthorized = false; + // Respuesta. + return x; + + } - foreach (var e in restrictions.Models) - { - switch (Account.AccountType) - { - case AccountTypes.Personal: - if (e.AllowPersonalAccounts) isAuthorized = true; - break; - case AccountTypes.Work: - if (e.AllowWorkAccounts) isAuthorized = true; - break; - case AccountTypes.Education: - if (e.AllowEducationsAccounts) isAuthorized = true; - break; - } - - // Si la restrictions no permite que sea del tipo de la cuenta. - if (!isAuthorized) - break; - // Validar horas. + private async Task ValidateRestrictions(ApplicationRestrictionModel? restriction) + { - // Validar identidades. + if (restriction == null) + return true; + // Validar. + switch (Account.AccountType) + { + case AccountTypes.Personal: + if (!restriction.AllowPersonalAccounts) return false; + break; + case AccountTypes.Work: + if (!restriction.AllowWorkAccounts) return false; + break; + case AccountTypes.Education: + if (!restriction.AllowEducationsAccounts) return false; + break; } - // Respuesta. - return isAuthorized; - } + if (restriction.RestrictedByTime) + { + bool x = await ValidateRestrictionsTimes(); + if (!x) + return false; + } + + if (restriction.RestrictedByIdentities) + { + bool x = await ValidateRestrictionsIdentity(); + if (!x) + return false; + } + return true; + } - private bool ValidateRestrictions(List restrictions) + private async Task ValidateRestrictionsTimes() { + var now = new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, 0); + var times = await applicationRestrictions.ReadTimes(Application!.Id); - DateTime now = DateTime.Now; - - foreach (var restriction in restrictions) + foreach (var time in times.Models) { + if (now > time.StartTime && now < time.EndTime) + return true; + } - + return false; + } - } + private async Task ValidateRestrictionsIdentity() + { - - return true; + var identities = await identityService.GetIdentities(Account.IdentityId); + + bool isAllow = await allowService.IsAllow(identities, Application.Id); + + return isAllow; } diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index c06be44..591042f 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -27,6 +27,7 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Externos services.AddSingleton(); diff --git a/LIN.Cloud.Identity/wwwroot/seeds/applications.json b/LIN.Cloud.Identity/wwwroot/seeds/applications.json index 508b9a5..7872654 100644 --- a/LIN.Cloud.Identity/wwwroot/seeds/applications.json +++ b/LIN.Cloud.Identity/wwwroot/seeds/applications.json @@ -14,7 +14,12 @@ "roles": [] }, "ownerId": 1, - "restrictions": [] + "restriction": { + "id": 0, + "allowPersonalAccounts": true, + "allowWorkAccounts": true, + "allowEducationsAccounts": true + } }, { "id": 0, @@ -31,7 +36,12 @@ "roles": [] }, "ownerId": 1, - "restrictions": [] + "restriction": { + "id": 0, + "allowPersonalAccounts": true, + "allowWorkAccounts": true, + "allowEducationsAccounts": true + } }, { "id": 0, @@ -48,7 +58,12 @@ "roles": [] }, "ownerId": 1, - "restrictions": [] + "restriction": { + "id": 0, + "allowPersonalAccounts": true, + "allowWorkAccounts": true, + "allowEducationsAccounts": true + } }, { "id": 0, @@ -65,7 +80,12 @@ "roles": [] }, "ownerId": 1, - "restrictions": [] + "restriction": { + "id": 0, + "allowPersonalAccounts": true, + "allowWorkAccounts": true, + "allowEducationsAccounts": true + } }, { "id": 0, @@ -82,7 +102,12 @@ "roles": [] }, "ownerId": 1, - "restrictions": [] + "restriction": { + "id": 0, + "allowPersonalAccounts": true, + "allowWorkAccounts": false, + "allowEducationsAccounts": false + } }, { "id": 0, @@ -99,7 +124,12 @@ "roles": [] }, "ownerId": 1, - "restrictions": [] + "restriction": { + "id": 0, + "allowPersonalAccounts": true, + "allowWorkAccounts": true, + "allowEducationsAccounts": true + } }, { "id": 0, @@ -116,7 +146,12 @@ "roles": [] }, "ownerId": 1, - "restrictions": [] + "restriction": { + "id": 0, + "allowPersonalAccounts": true, + "allowWorkAccounts": true, + "allowEducationsAccounts": true + } }, { "id": 0, @@ -133,6 +168,11 @@ "roles": [] }, "ownerId": 1, - "restrictions": [] + "restriction": { + "id": 0, + "allowPersonalAccounts": true, + "allowWorkAccounts": true, + "allowEducationsAccounts": true + } } ] \ No newline at end of file From e333cd67ebb710852f8f9b8bc8b787486bffcb94 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 16:16:17 -0500 Subject: [PATCH 153/178] Mejoras generales --- .../Areas/Accounts/AccountController.cs | 13 +- .../ApplicationRestrictionsController.cs | 2 +- .../Applications/ApplicationsController.cs | 6 +- .../AuthenticationController.cs | 2 +- .../Areas/Policies/PolicyRequirements.cs | 2 +- .../Data/ApplicationRestrictions.cs | 8 +- LIN.Cloud.Identity/Data/Applications.cs | 7 +- .../Auth/Authentication.Application.cs | 123 ++++++++++++++++++ .../Services/Auth/Authentication.cs | 105 +-------------- 9 files changed, 149 insertions(+), 119 deletions(-) create mode 100644 LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 375f130..2561898 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -1,7 +1,7 @@ namespace LIN.Cloud.Identity.Areas.Accounts; [Route("[controller]")] -public class AccountController(Data.Accounts accountData) : AuthenticationBaseController +public class AccountController(Data.Accounts accountData, Data.Applications applications) : AuthenticationBaseController { /// @@ -10,7 +10,7 @@ public class AccountController(Data.Accounts accountData) : AuthenticationBaseCo /// Modelo de la cuenta. /// Retorna el Id asignado a la cuenta. [HttpPost] - public async Task Create([FromBody] AccountModel? modelo) + public async Task Create([FromBody] AccountModel? modelo, [FromHeader] string app) { // Validaciones del modelo. @@ -45,9 +45,16 @@ public async Task Create([FromBody] AccountModel? modelo) Message = "Hubo un error al crear la cuenta." }; + // Obtener información de la app. + var application = await applications.Read(app); + // Obtiene el usuario. - var token = JwtService.Generate(response.Model, 0); + string? token = null; + // Si la aplicación es valida, generamos un token. + if (application.Response == Responses.Success) + token = JwtService.Generate(response.Model, application.Model.Id); + // Retorna el resultado. return new CreateResponse() { diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs index f6e447f..a7ed197 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs @@ -6,7 +6,7 @@ public class ApplicationRestrictionsController(Data.ApplicationRestrictions appl { /// - /// Crear restriccion. + /// Crear restricción. /// /// Modelo. [HttpPost] diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs index d1c50b5..20f6858 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs @@ -30,14 +30,14 @@ public async Task Create([FromBody] ApplicationModel app) if (string.IsNullOrWhiteSpace(app.Identity.Unique)) return new(Responses.InvalidParam) { - Errors = [new() { Tittle = "Unique invalido", Description = "La identidad unica es invalida (No puede ser vacio)." }] + Errors = [new() { Tittle = "Unique invalido", Description = "La identidad unica es invalida (No puede ser vacío)." }] }; - // Validar otros parametros. + // Validar otros parámetros. if (string.IsNullOrWhiteSpace(app.Name)) return new(Responses.InvalidParam) { - Errors = [new() { Tittle = "Nombre invalido", Description = "El nombre de la aplicación no puede estar vacio." }] + Errors = [new() { Tittle = "Nombre invalido", Description = "El nombre de la aplicación no puede estar vacío." }] }; // Formatear app. diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 7de3721..e3ad16c 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -62,7 +62,7 @@ public async Task> Login([FromQuery] string us return new() { Response = Responses.UnauthorizedByApp, - Message = "La aplicación no existe o no permite que inicies sesion en este momento." + Message = "La aplicación no existe o no permite que inicies sesión en este momento." }; // Incorrecto diff --git a/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs b/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs index 5729ff9..da3ef1d 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs @@ -17,7 +17,7 @@ public async Task Create([FromBody] PolicyRequirementModel m var roles = await iamPolicy.Validate(UserInformation.IdentityId, modelo.PolicyId.ToString()); if (roles != IamLevels.Privileged) - return new(Responses.Unauthorized) { Message = $"No tienes permisos c" }; + return new(Responses.Unauthorized) { Message = $"No tienes permisos" }; // Forma var response = await policiesData.Create(modelo); diff --git a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs index 70dc013..63241cc 100644 --- a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs +++ b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs @@ -4,7 +4,7 @@ public class ApplicationRestrictions(DataContext context) { /// - /// Crear nueva restriccion de aplicación. + /// Crear nueva restricción de aplicación. /// /// Modelo. public async Task Create(ApplicationRestrictionModel modelo) @@ -30,7 +30,7 @@ public async Task Create(ApplicationRestrictionModel modelo) /// - /// Obtener las restricciones de aplicacion. + /// Obtener las restricciones de aplicación. /// /// Id de la aplicación. public async Task> Read(string id) @@ -53,6 +53,10 @@ public async Task> Read(string id) } + /// + /// Obtener las restricciones de tiempo. + /// + /// Id de la aplicación. public async Task> ReadTimes(int id) { try diff --git a/LIN.Cloud.Identity/Data/Applications.cs b/LIN.Cloud.Identity/Data/Applications.cs index e207cb0..85849af 100644 --- a/LIN.Cloud.Identity/Data/Applications.cs +++ b/LIN.Cloud.Identity/Data/Applications.cs @@ -39,7 +39,7 @@ public async Task> Read(string key) try { - // Obetener el modelo. + // Obtener el modelo. var application = await (from ar in context.Applications where ar.Key == Guid.Parse(key) select ar).FirstOrDefaultAsync(); @@ -74,10 +74,7 @@ public async Task> ExistApp(string key) } catch (Exception) { - return new() - { - Response = Responses.Undefined - }; + return new(Responses.Undefined); } } diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs new file mode 100644 index 0000000..496312b --- /dev/null +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs @@ -0,0 +1,123 @@ +namespace LIN.Cloud.Identity.Services.Auth; + +public partial class Authentication +{ + + /// + /// Modelo de la aplicación. + /// + public ApplicationModel? Application { get; set; } = null; + + + /// + /// Validar aplicación. + /// + private async Task ValidateApp() + { + + // Obtener la restrictions. + var appResponse = await applications.Read(AppCode); + Application = appResponse.Model; + + // Si no se requiere validar la restrictions. + if (Account!.IsLINAdmin || !Settings.ValidateApp) + return true; + + // Validar si la restrictions existe. + if (appResponse.Response != Responses.Success) + return false; + + // Obtener las restricciones. + var restriction = await applicationRestrictions.Read(AppCode); + + // Validar las restricciones. + var isAuthorized = await ValidateRestrictions(restriction.Model); + + // Respuesta. + return isAuthorized; + } + + + /// + /// Validar restricciones. + /// + /// Modelo de las restricciones. + private async Task ValidateRestrictions(ApplicationRestrictionModel? restriction) + { + + // Si no hay. + if (restriction == null) + return true; + + // Validar por el tipo de cuenta. + switch (Account!.AccountType) + { + case AccountTypes.Personal: + if (!restriction.AllowPersonalAccounts) return false; + break; + case AccountTypes.Work: + if (!restriction.AllowWorkAccounts) return false; + break; + case AccountTypes.Education: + if (!restriction.AllowEducationsAccounts) return false; + break; + } + + // Restricciones de tiempo. + if (restriction.RestrictedByTime) + { + bool isAuthorized = await ValidateRestrictionsTimes(); + if (!isAuthorized) + return false; + } + + // Restricciones de identidad. + if (restriction.RestrictedByIdentities) + { + bool isAuthorized = await ValidateRestrictionsIdentity(); + if (!isAuthorized) + return false; + } + + return true; + } + + + /// + /// Validar restricciones de tiempo. + /// + private async Task ValidateRestrictionsTimes() + { + // Hora actual. + var now = new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, 0); + + // Obtener las restricciones de tiempo. + var times = await applicationRestrictions.ReadTimes(Application!.Id); + + // Validar si alguna encaja. + foreach (var time in times.Models) + { + if (now > time.StartTime && now < time.EndTime) + return true; + } + + return false; + } + + + /// + /// Validar restricciones de identidades. + /// + private async Task ValidateRestrictionsIdentity() + { + + // Obtener las identidades de un usuario. + var identities = await identityService.GetIdentities(Account!.IdentityId); + + // Validar si alguna esta autorizada para acceder a la app. + bool isAllow = await allowService.IsAllow(identities, Application!.Id); + + return isAllow; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 3a74d3e..88a7019 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -2,7 +2,7 @@ namespace LIN.Cloud.Identity.Services.Auth; -public class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions, Data.Applications applications, Utils.IdentityService identityService, AllowService allowService) : Interfaces.IAuthentication +public partial class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions, Data.Applications applications, Utils.IdentityService identityService, AllowService allowService) : Interfaces.IAuthentication { /// @@ -29,12 +29,6 @@ public class Authentication(Data.Accounts accountData, Data.AccountLogs accountL public AccountModel? Account { get; set; } = null; - /// - /// Aplicación - /// - public ApplicationModel? Application { get; set; } = null; - - /// /// Ajustes. /// @@ -96,7 +90,6 @@ public async Task Start(AuthenticationSettings? settings = null) /// private async Task GetAccount() { - // Obtener la cuenta. var account = await accountData.Read(User, new() { @@ -109,100 +102,6 @@ private async Task GetAccount() // Respuesta. return account.Response == Responses.Success; - - } - - - - private async Task ValidateApp() - { - - // Obtener la restrictions. - var appResponse = await applications.Read(AppCode); - Application = appResponse.Model; - - // Si no se requiere validar la restrictions. - if (Account!.IsLINAdmin || !Settings.ValidateApp) - return true; - - // Validar si la restrictions existe. - if (appResponse.Response != Responses.Success) - return false; - - // Obtener las restricciones. - var restriction = await applicationRestrictions.Read(AppCode); - - var x = await ValidateRestrictions(restriction.Model); - - // Respuesta. - return x; - - } - - - - private async Task ValidateRestrictions(ApplicationRestrictionModel? restriction) - { - - if (restriction == null) - return true; - - // Validar. - switch (Account.AccountType) - { - case AccountTypes.Personal: - if (!restriction.AllowPersonalAccounts) return false; - break; - case AccountTypes.Work: - if (!restriction.AllowWorkAccounts) return false; - break; - case AccountTypes.Education: - if (!restriction.AllowEducationsAccounts) return false; - break; - } - - - if (restriction.RestrictedByTime) - { - bool x = await ValidateRestrictionsTimes(); - if (!x) - return false; - } - - if (restriction.RestrictedByIdentities) - { - bool x = await ValidateRestrictionsIdentity(); - if (!x) - return false; - } - - return true; - } - - - private async Task ValidateRestrictionsTimes() - { - var now = new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, 0); - var times = await applicationRestrictions.ReadTimes(Application!.Id); - - foreach (var time in times.Models) - { - if (now > time.StartTime && now < time.EndTime) - return true; - } - - return false; - } - - - private async Task ValidateRestrictionsIdentity() - { - - var identities = await identityService.GetIdentities(Account.IdentityId); - - bool isAllow = await allowService.IsAllow(identities, Application.Id); - - return isAllow; } @@ -244,7 +143,7 @@ await accountLogs.Create(new() /// /// Obtener el token. /// - public string GenerateToken() => JwtService.Generate(Account!, 0); + public string GenerateToken() => JwtService.Generate(Account!, Application!.Id); /// From d355a86087f679990fe2496fd852405ddc22ea94 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 16:29:11 -0500 Subject: [PATCH 154/178] mejoras --- .../Areas/Applications/ApplicationRestrictionsController.cs | 4 ++-- LIN.Cloud.Identity/Services/Auth/Authentication.cs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs index a7ed197..0e78dce 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs @@ -9,8 +9,8 @@ public class ApplicationRestrictionsController(Data.ApplicationRestrictions appl /// Crear restricción. /// /// Modelo. - [HttpPost] - public async Task Create([FromBody] ApplicationRestrictionModel app) + [HttpPut] + public async Task UpdateOrCreate([FromBody] ApplicationRestrictionModel app) { // Si el modelo es nulo. diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 88a7019..02338e7 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -1,8 +1,9 @@ -using LIN.Cloud.Identity.Services.Auth.Models; +using LIN.Cloud.Identity.Services.Auth.Interfaces; +using LIN.Cloud.Identity.Services.Auth.Models; namespace LIN.Cloud.Identity.Services.Auth; -public partial class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions, Data.Applications applications, Utils.IdentityService identityService, AllowService allowService) : Interfaces.IAuthentication +public partial class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions, Data.Applications applications, IIdentityService identityService, IAllowService allowService) : Interfaces.IAuthentication { /// From b2cb5553f886c3a79df6f908c1655cbbce9c9d14 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 17:01:46 -0500 Subject: [PATCH 155/178] Update --- .../wwwroot/seeds/applications.json | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/LIN.Cloud.Identity/wwwroot/seeds/applications.json b/LIN.Cloud.Identity/wwwroot/seeds/applications.json index 7872654..e63605e 100644 --- a/LIN.Cloud.Identity/wwwroot/seeds/applications.json +++ b/LIN.Cloud.Identity/wwwroot/seeds/applications.json @@ -174,5 +174,27 @@ "allowWorkAccounts": true, "allowEducationsAccounts": true } + }, + { + "id": 0, + "name": "LIN Human Directory", + "key": "bc8dc14f-55cb-4f73-839b-95b5480ff774",: null, + "identity": { + "id": 0, + "unique": "humandirectory#lin", + "creationTime": "2025-02-16T10:00:28.998Z", + "effectiveTime": "2025-02-16T10:00:28.998Z", + "expirationTime": "2045-02-16T15:41:28.998Z", + "status": 0, + "type": 0, + "roles": [] + }, + "ownerId": 1, + "restriction": { + "id": 0, + "allowPersonalAccounts": true, + "allowWorkAccounts": true, + "allowEducationsAccounts": true + } } ] \ No newline at end of file From 83f3fdfd7025484db9e6d00826ce233b1f9bf502 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 16 Feb 2025 19:27:53 -0500 Subject: [PATCH 156/178] Fix --- LIN.Cloud.Identity/wwwroot/seeds/applications.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LIN.Cloud.Identity/wwwroot/seeds/applications.json b/LIN.Cloud.Identity/wwwroot/seeds/applications.json index e63605e..03abca6 100644 --- a/LIN.Cloud.Identity/wwwroot/seeds/applications.json +++ b/LIN.Cloud.Identity/wwwroot/seeds/applications.json @@ -178,7 +178,7 @@ { "id": 0, "name": "LIN Human Directory", - "key": "bc8dc14f-55cb-4f73-839b-95b5480ff774",: null, + "key": "bc8dc14f-55cb-4f73-839b-95b5480ff774", "identity": { "id": 0, "unique": "humandirectory#lin", From fb781224e22558e67395451decbc880b7f132bbc Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 22 Feb 2025 23:41:22 -0500 Subject: [PATCH 157/178] =?UTF-8?q?Actualizaci=C3=B3n=20de=20estandar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- LIN.Cloud.Identity/Areas/Accounts/AccountController.cs | 2 +- LIN.Cloud.Identity/Areas/Groups/GroupsController.cs | 2 +- .../Areas/Organizations/OrganizationController.cs | 2 +- .../Areas/Organizations/OrganizationMembersController.cs | 2 +- LIN.Cloud.Identity/Data/Organizations.cs | 2 +- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index b81ec08..3a617e8 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 2561898..3aec403 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -58,7 +58,7 @@ public async Task Create([FromBody] AccountModel? modelo, [F // Retorna el resultado. return new CreateResponse() { - LastID = response.Model.Identity.Id, + LastId = response.Model.Identity.Id, Response = Responses.Success, Token = token, Message = "Cuenta creada satisfactoriamente." diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index b1decd8..81c650f 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -45,7 +45,7 @@ public async Task Create([FromBody] GroupModel group) return new CreateResponse() { Response = Responses.Success, - LastID = response.Model.Id + LastId = response.Model.Id }; } diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 3866bad..28d7c8f 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -43,7 +43,7 @@ public async Task Create([FromBody] OrganizationModel modelo // Retorna el resultado. return new CreateResponse() { - LastID = response.LastID, + LastId = response.LastId, Response = Responses.Success, Message = "Success" }; diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index cc2ce5b..144b78c 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -132,7 +132,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr // Retorna el resultado return new CreateResponse() { - LastID = response.Model.Id, + LastId = response.Model.Id, Response = Responses.Success, Message = "Success" }; diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs index d3e9490..daa108e 100644 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ b/LIN.Cloud.Identity/Data/Organizations.cs @@ -94,7 +94,7 @@ public async Task Create(OrganizationModel modelo) var responseFinal = new CreateResponse() { Response = Responses.Success, - LastID = modelo.Id + LastId = modelo.Id }; diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 0e93dfb..62d77c3 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,10 +9,10 @@ - + - + From 687a1e8f3c379ad8bda1329463f177dde070eb37 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 23 Feb 2025 12:16:23 -0500 Subject: [PATCH 158/178] update --- .../Extensions/PersistenceExtensions.cs | 8 ++++++-- LIN.Cloud.Identity/Areas/Accounts/AccountController.cs | 2 +- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- LIN.Cloud.Identity/Program.cs | 1 + 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 4055a3c..564e199 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace LIN.Cloud.Identity.Persistence.Extensions; @@ -42,9 +43,12 @@ public static IServiceCollection AddPersistence(this IServiceCollection services /// public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) { + + var scope = app.ApplicationServices.CreateScope(); + var logger = scope.ServiceProvider.GetService>(); try { - var scope = app.ApplicationServices.CreateScope(); + var context = scope.ServiceProvider.GetService(); bool? created = context?.Database.EnsureCreated(); @@ -53,7 +57,7 @@ public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) } catch (Exception ex) { - Console.WriteLine(ex.ToString()); + logger?.LogError(ex, "Error al definir la base de datos."); } return app; } diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 3aec403..3d27669 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -49,7 +49,7 @@ public async Task Create([FromBody] AccountModel? modelo, [F var application = await applications.Read(app); // Obtiene el usuario. - string? token = null; + string token = string.Empty; // Si la aplicación es valida, generamos un token. if (application.Response == Responses.Success) diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 62d77c3..86c5b2f 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -12,7 +12,7 @@ - + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index a72af74..54561eb 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -18,6 +18,7 @@ // Servicio de autenticación. builder.Services.AddScoped(); builder.Services.AddPersistence(builder.Configuration); +builder.Host.UseLoggingService(builder.Configuration); var app = builder.Build(); From 419d80dd5b68ff71df1d1b8b1cab0e1fd1816bd4 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 23 Feb 2025 18:45:38 -0500 Subject: [PATCH 159/178] =?UTF-8?q?Integraci=C3=B3n=20logger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LIN.Cloud.Identity/Data/Accounts.cs | 6 +++--- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- LIN.Cloud.Identity/Program.cs | 1 - LIN.Cloud.Identity/Services/Utils/EmailSender.cs | 6 ++++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity/Data/Accounts.cs index 341a3aa..40b85e2 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity/Data/Accounts.cs @@ -1,6 +1,6 @@ namespace LIN.Cloud.Identity.Data; -public class Accounts(DataContext context) +public class Accounts(DataContext context, ILogger logger) { /// @@ -67,8 +67,9 @@ public async Task> Create(AccountModel modelo, int }; } - catch (Exception) + catch (Exception ex) { + logger.LogError(ex, "Error al crear cuenta"); transaction?.Rollback(); return new() { @@ -126,7 +127,6 @@ public async Task> Read(int id, QueryObjectFilter /// Filtros de búsqueda. public async Task> Read(string unique, QueryObjectFilter filters) { - try { diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 86c5b2f..5075aa2 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -12,7 +12,7 @@ - + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 54561eb..6d2ff94 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -21,7 +21,6 @@ builder.Host.UseLoggingService(builder.Configuration); var app = builder.Build(); - app.UseLINHttp(); // Base de datos. diff --git a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs index c193b64..5a5a7b4 100644 --- a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs +++ b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs @@ -1,6 +1,6 @@ namespace LIN.Cloud.Identity.Services.Utils; -public class EmailSender +public class EmailSender(ILogger logger) { /// @@ -21,7 +21,9 @@ public async Task Send(string to, string subject, string body) client.AddParameter("subject", subject); client.AddParameter("mail", to); - var aa = await client.Post(body); + var result = await client.Post(body); + + logger.LogInformation(result); return true; } From 8c38b8bc94f683a9e31e2fa058e4642fbbab5d95 Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sun, 23 Feb 2025 19:20:51 -0500 Subject: [PATCH 160/178] Update --- .../Authentication/SecurityController.cs | 2 +- .../Services/Utils/EmailSender.cs | 27 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index 659ca55..e5ca1ae 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -35,7 +35,7 @@ public async Task AddMail([FromQuery] string email) case Responses.ResourceExist: return new(responseCreate.Response) { - Message = $"Hubo un error al agregar el correo <{email}> a la cuenta {model.Account.Identity.Unique}", + Message = $"Hubo un error al agregar el correo <{email}> a la cuenta con identidad: '{UserInformation.IdentityId}'", Errors = [ new() { Tittle = "Mail duplicado", diff --git a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs index 5a5a7b4..b679841 100644 --- a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs +++ b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs @@ -11,21 +11,28 @@ public class EmailSender(ILogger logger) /// Cuerpo HTML. public async Task Send(string to, string subject, string body) { - - // Servicio. - Global.Http.Services.Client client = new(Http.Services.Configuration.GetConfiguration("hangfire:mail")) + try { - TimeOut = 10 - }; + // Servicio. + Global.Http.Services.Client client = new(Http.Services.Configuration.GetConfiguration("hangfire:mail")) + { + TimeOut = 10 + }; - client.AddParameter("subject", subject); - client.AddParameter("mail", to); + client.AddParameter("subject", subject); + client.AddParameter("mail", to); - var result = await client.Post(body); + var result = await client.Post(body); - logger.LogInformation(result); + logger.LogInformation(result); - return true; + return true; + } + catch (Exception ex) + { + logger.LogError(ex, "Error al enviar correo."); + return false; + } } } \ No newline at end of file From 1d80c8e547a30acb8cfe0070a6b61e687992fb9e Mon Sep 17 00:00:00 2001 From: Alexander Giraldo Date: Sat, 8 Mar 2025 17:24:50 -0500 Subject: [PATCH 161/178] Clean --- LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs | 6 +++--- .../Extensions/PersistenceExtensions.cs | 4 ++-- LIN.Cloud.Identity/Areas/Accounts/AccountController.cs | 2 +- LIN.Cloud.Identity/Data/ApplicationRestrictions.cs | 8 ++++---- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 018c55a..101cd87 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -192,7 +192,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.ToTable("applications_restrictions"); entity.HasOne(t => t.Application) .WithOne(t => t.Restriction) - .HasForeignKey(t=>t.RestrictionId); + .HasForeignKey(t => t.RestrictionId); }); // Application Model @@ -223,7 +223,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasKey(t => new { t.ApplicationRestrictionId, t.IdentityId }); entity.HasOne(t => t.Application) - .WithMany(t=>t.Allowed) + .WithMany(t => t.Allowed) .HasForeignKey(t => t.ApplicationRestrictionId) .OnDelete(DeleteBehavior.NoAction); @@ -237,7 +237,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { entity.ToTable("restrict_times"); - + entity.HasOne(t => t.ApplicationRestrictionModel) .WithMany(t => t.Times) .HasForeignKey(t => t.ApplicationRestrictionId) diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 564e199..888bc09 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -43,12 +43,12 @@ public static IServiceCollection AddPersistence(this IServiceCollection services /// public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) { - + var scope = app.ApplicationServices.CreateScope(); var logger = scope.ServiceProvider.GetService>(); try { - + var context = scope.ServiceProvider.GetService(); bool? created = context?.Database.EnsureCreated(); diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 3d27669..c78f1c5 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -54,7 +54,7 @@ public async Task Create([FromBody] AccountModel? modelo, [F // Si la aplicación es valida, generamos un token. if (application.Response == Responses.Success) token = JwtService.Generate(response.Model, application.Model.Id); - + // Retorna el resultado. return new CreateResponse() { diff --git a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs index 63241cc..1529755 100644 --- a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs +++ b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs @@ -39,8 +39,8 @@ public async Task> Read(string id) { var restriction = await (from ar in context.ApplicationRestrictions - where ar.Application.Key == Guid.Parse(id) - select ar).FirstOrDefaultAsync(); + where ar.Application.Key == Guid.Parse(id) + select ar).FirstOrDefaultAsync(); // Success. return new(Responses.Success, restriction!); @@ -63,8 +63,8 @@ public async Task> ReadTimes(int id) { var restrictions = await (from tr in context.TimeRestriction - where tr.ApplicationRestrictionModel.ApplicationId == id - select tr).ToListAsync(); + where tr.ApplicationRestrictionModel.ApplicationId == id + select tr).ToListAsync(); // Success. return new(Responses.Success, restrictions); diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 5075aa2..bf5c8cc 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -12,7 +12,7 @@ - + From de35af07b97cf8f9fa3323d92d37e97551e42f17 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sat, 10 May 2025 22:16:10 -0500 Subject: [PATCH 162/178] Fix --- LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs | 2 +- LIN.Cloud.Identity/Services/Formats/Account.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 1d5bf99..bd37dff 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -29,7 +29,7 @@ where a.Key.Equals(UserInformation.Unique, StringComparison.CurrentCultureIgnore where I.Expiración > timeNow select I).ToList(); - // Retorna + // Retorna. return new(Responses.Success, intentos); } catch (Exception) diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 659c53c..7659609 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -69,6 +69,7 @@ public static AccountModel Process(AccountModel baseAccount) Visibility = baseAccount.Visibility, IdentityId = 0, IsLINAdmin = false, + AccountType = baseAccount.AccountType, Identity = new() { Id = 0, From b12aa11e5434ab03518f3bd4879b8889ae808187 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sun, 11 May 2025 16:49:19 -0500 Subject: [PATCH 163/178] separacion de controladores, nuevos nombres, repositorios --- .../Contexts/DataContext.cs | 161 +++----- .../Extensions/EntityFramework.cs | 55 --- .../Extensions/PersistenceExtensions.cs | 23 +- .../Formatters/Account.cs | 88 +++++ .../Formatters/Identities.cs | 20 + .../LIN.Cloud.Identity.Persistence.csproj | 7 +- .../Models/OtpDatabaseModel.cs | 2 +- .../Models/QueryIdentityFilter.cs | 8 + .../Models/QueryObjectFilter.cs | 2 - .../Queries/AccountFindable.cs | 40 +- .../Queries/IdentityFindable.cs | 7 +- .../Queries/Interfaces/IFindable.cs | 8 +- .../EntityFramework/AccountLogRepository.cs | 38 +- .../EntityFramework/AccountRepository.cs | 137 ++----- .../EntityFramework/ApplicationRepository.cs | 6 +- .../EntityFramework}/Builders/Account.cs | 11 +- .../EntityFramework/Builders/Identity.cs | 18 +- .../EntityFramework/GroupMemberRepository.cs | 196 ++-------- .../EntityFramework/GroupRepository.cs | 176 ++------- .../EntityFramework/IdentityRepository.cs | 86 +++++ .../IdentityRolesRepository.cs | 72 +--- .../OrganizationMemberRepository.cs | 116 ++++++ .../EntityFramework/OrganizationRepository.cs | 196 ++++++++++ .../EntityFramework/OtpRepository.cs | 68 ++-- .../EntityFramework/PolicyRepository.cs | 146 +++++++ .../Repositories/IAccountLogRepository.cs | 8 + .../Repositories/IAccountRepository.cs | 20 + .../Repositories/IApplicationRepository.cs | 12 + .../Repositories/IGroupMemberRepository.cs | 11 + .../Repositories/IGroupRepository.cs | 41 ++ .../Repositories/IIdentityRepository.cs | 8 + .../Repositories/IIdentityRolesRepository.cs | 8 + .../IOrganizationMemberRepository.cs | 28 ++ .../Repositories/IOrganizationRepository.cs | 14 + .../Repositories/IOtpRepository.cs | 8 + .../Repositories/IPolicyRepository.cs | 11 + LIN.Cloud.Identity.Persistence/Usings.cs | 9 + .../Interfaces/IAuthenticationService.cs | 6 + .../Interfaces/IPolicyEngine.cs | 7 + .../LIN.Cloud.Identity.Services.csproj | 13 + .../Services/ServiceAuthenticationService.cs | 9 + .../Services/UserAuthenticationService.cs | 9 + LIN.Cloud.Identity.Services/usings.cs | 2 + LIN.Cloud.Identity.sln | 42 +- .../Areas/Accounts/AccountController.cs | 6 +- .../Areas/Accounts/AccountLogs.cs | 4 +- .../ApplicationRestrictionsController.cs | 29 +- .../Applications/ApplicationsController.cs | 10 +- .../AuthenticationController.cs | 25 +- .../Areas/Authentication/IntentsController.cs | 3 +- .../Authentication/SecurityController.cs | 306 +++++++-------- .../Areas/Directories/DirectoryController.cs | 39 +- .../Areas/Groups/GroupsController.cs | 8 +- .../Areas/Groups/GroupsMembersController.cs | 45 ++- .../Areas/Organizations/IdentityController.cs | 6 +- .../Organizations/OrganizationController.cs | 6 +- .../OrganizationMembersController.cs | 87 +++-- .../Policies/PoliciesComplacentController.cs | 97 ----- .../Areas/Policies/PoliciesController.cs | 154 +++----- .../Areas/Policies/PolicyRequirements.cs | 27 -- LIN.Cloud.Identity/Data/AllowApps.cs | 83 ---- .../Data/ApplicationRestrictions.cs | 79 ---- LIN.Cloud.Identity/Data/DirectoryMembers.cs | 351 ----------------- LIN.Cloud.Identity/Data/Identities.cs | 130 ------- LIN.Cloud.Identity/Data/Mails.cs | 121 ------ LIN.Cloud.Identity/Data/Organizations.cs | 321 ---------------- LIN.Cloud.Identity/Data/PassKeys.cs | 36 -- LIN.Cloud.Identity/Data/Policies.cs | 360 ------------------ .../Data/PoliciesRequirement.cs | 86 ----- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 8 +- LIN.Cloud.Identity/Program.cs | 2 +- .../Services/Auth/AllowService.cs | 13 +- .../Auth/Authentication.Application.cs | 89 +---- .../Services/Auth/Authentication.cs | 5 +- .../Services/Auth/JwtService.cs | 7 +- .../Services/Extensions/LocalServices.cs | 16 - .../Services/Formats/Account.cs | 1 - LIN.Cloud.Identity/Services/Iam/IamPolicy.cs | 16 +- LIN.Cloud.Identity/Services/Iam/IamRoles.cs | 8 +- .../Services/Realtime/PassKeyHub.cs | 6 +- .../Services/Utils/EmailSender.cs | 4 +- .../Services/Utils/PolicyService.cs | 124 ------ LIN.Cloud.Identity/Usings.cs | 2 + LIN.Identity.Tests/Auth/JwtServiceTests.cs | 69 ---- LIN.Identity.Tests/Data/Accounts.cs | 278 -------------- LIN.Identity.Tests/Data/Groups.cs | 72 ---- LIN.Identity.Tests/LIN.Identity.Tests.csproj | 40 -- LIN.Identity.Tests/Validations.cs | 37 -- README.md | 4 +- 89 files changed, 1611 insertions(+), 3562 deletions(-) delete mode 100644 LIN.Cloud.Identity.Persistence/Extensions/EntityFramework.cs create mode 100644 LIN.Cloud.Identity.Persistence/Formatters/Account.cs create mode 100644 LIN.Cloud.Identity.Persistence/Formatters/Identities.cs create mode 100644 LIN.Cloud.Identity.Persistence/Models/QueryIdentityFilter.cs rename LIN.Cloud.Identity/Data/AccountLogs.cs => LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs (66%) rename LIN.Cloud.Identity/Data/Accounts.cs => LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs (67%) rename LIN.Cloud.Identity/Data/Applications.cs => LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/ApplicationRepository.cs (89%) rename {LIN.Cloud.Identity/Data => LIN.Cloud.Identity.Persistence/Repositories/EntityFramework}/Builders/Account.cs (94%) rename LIN.Cloud.Identity/Data/Builders/Identities.cs => LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Identity.cs (93%) rename LIN.Cloud.Identity/Data/GroupMembers.cs => LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupMemberRepository.cs (50%) rename LIN.Cloud.Identity/Data/Groups.cs => LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupRepository.cs (57%) create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRepository.cs rename LIN.Cloud.Identity/Data/IdentityRoles.cs => LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRolesRepository.cs (79%) create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs rename LIN.Cloud.Identity/Data/OtpService.cs => LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OtpRepository.cs (80%) create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IAccountLogRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IAccountRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IApplicationRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IGroupMemberRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IGroupRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IIdentityRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IIdentityRolesRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IOrganizationRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IOtpRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Usings.cs create mode 100644 LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs create mode 100644 LIN.Cloud.Identity.Services/Interfaces/IPolicyEngine.cs create mode 100644 LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj create mode 100644 LIN.Cloud.Identity.Services/Services/ServiceAuthenticationService.cs create mode 100644 LIN.Cloud.Identity.Services/Services/UserAuthenticationService.cs create mode 100644 LIN.Cloud.Identity.Services/usings.cs delete mode 100644 LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs delete mode 100644 LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs delete mode 100644 LIN.Cloud.Identity/Data/AllowApps.cs delete mode 100644 LIN.Cloud.Identity/Data/ApplicationRestrictions.cs delete mode 100644 LIN.Cloud.Identity/Data/DirectoryMembers.cs delete mode 100644 LIN.Cloud.Identity/Data/Identities.cs delete mode 100644 LIN.Cloud.Identity/Data/Mails.cs delete mode 100644 LIN.Cloud.Identity/Data/Organizations.cs delete mode 100644 LIN.Cloud.Identity/Data/PassKeys.cs delete mode 100644 LIN.Cloud.Identity/Data/Policies.cs delete mode 100644 LIN.Cloud.Identity/Data/PoliciesRequirement.cs delete mode 100644 LIN.Identity.Tests/Auth/JwtServiceTests.cs delete mode 100644 LIN.Identity.Tests/Data/Accounts.cs delete mode 100644 LIN.Identity.Tests/Data/Groups.cs delete mode 100644 LIN.Identity.Tests/LIN.Identity.Tests.csproj delete mode 100644 LIN.Identity.Tests/Validations.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 101cd87..2c6f51f 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -1,6 +1,7 @@ -using LIN.Cloud.Identity.Persistence.Extensions; -using LIN.Cloud.Identity.Persistence.Models; +using LIN.Cloud.Identity.Persistence.Models; using LIN.Types.Cloud.Identity.Models; +using LIN.Types.Cloud.Identity.Models.Identities; +using LIN.Types.Cloud.Identity.Models.Policies; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; @@ -14,103 +15,76 @@ public class DataContext(DbContextOptions options) : DbContext(opti /// public DbSet Identities { get; set; } - /// /// Tabla de cuentas. /// public DbSet Accounts { get; set; } - /// /// Organizaciones. /// public DbSet Organizations { get; set; } - /// /// Grupos. /// public DbSet Groups { get; set; } - /// /// Integrantes de un grupo. /// public DbSet GroupMembers { get; set; } - /// /// RolesIam de grupos. /// public DbSet IdentityRoles { get; set; } - /// /// Aplicaciones. /// public DbSet Applications { get; set; } - - /// - /// Allow apps. - /// - public DbSet AllowApps { get; set; } - - - /// - /// Restricciones de apps. - /// - public DbSet ApplicationRestrictions { get; set; } - - /// /// Logs de accounts. /// public DbSet AccountLogs { get; set; } - /// /// Políticas. /// public DbSet Policies { get; set; } - /// - /// Identidades en Políticas. + /// Políticas por tipo de entidad. /// - public DbSet IdentityOnPolicies { get; set; } - + public DbSet IdentityTypesPolicies { get; set; } /// - /// Requerimientos de políticas. + /// Políticas por acceso IP. /// - public DbSet PolicyRequirements { get; set; } - + public DbSet IpAccessPolicies { get; set; } /// - /// Códigos OTPS. + /// Políticas por acceso de tiempo. /// - public DbSet OTPs { get; set; } - + public DbSet TimeAccessPolicies { get; set; } /// - /// Restricciones de tiempo. + /// Códigos OTPS. /// - public DbSet TimeRestriction { get; set; } - + public DbSet OTPs { get; set; } /// /// Correos asociados a las cuentas. /// public DbSet Mails { get; set; } - /// /// Mail Otp. /// public DbSet MailOtp { get; set; } - /// /// Crear el modelo en BD. /// @@ -121,6 +95,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { entity.ToTable("identities"); entity.HasIndex(t => t.Unique).IsUnique(); + + entity.HasOne(o => o.Owner) + .WithMany() + .HasForeignKey(o => o.OwnerId) + .OnDelete(DeleteBehavior.NoAction); }); // Account Model. @@ -143,12 +122,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // Group Model. modelBuilder.Entity(entity => { - entity.ToTable("groups"); - entity.HasOne(g => g.Owner) - .WithMany(o => o.OwnedGroups) - .HasForeignKey(g => g.OwnerId) - .OnDelete(DeleteBehavior.NoAction); - entity.HasOne(t => t.Identity) .WithMany() .HasForeignKey(t => t.IdentityId) @@ -186,15 +159,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(t => t.OrganizationId); }); - // Application restrictions Model - modelBuilder.Entity(entity => - { - entity.ToTable("applications_restrictions"); - entity.HasOne(t => t.Application) - .WithOne(t => t.Restriction) - .HasForeignKey(t => t.RestrictionId); - }); - // Application Model modelBuilder.Entity(entity => { @@ -211,38 +175,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(t => t.OwnerId) .OnDelete(DeleteBehavior.NoAction); - entity.HasOne(t => t.Restriction) - .WithOne(t => t.Application) - .HasForeignKey(t => t.ApplicationId); - }); - - // Allow Apps Model - modelBuilder.Entity(entity => - { - entity.ToTable("allow_apps"); - entity.HasKey(t => new { t.ApplicationRestrictionId, t.IdentityId }); - - entity.HasOne(t => t.Application) - .WithMany(t => t.Allowed) - .HasForeignKey(t => t.ApplicationRestrictionId) - .OnDelete(DeleteBehavior.NoAction); - - entity.HasOne(t => t.Identity) - .WithMany() - .HasForeignKey(t => t.IdentityId) - .OnDelete(DeleteBehavior.NoAction); - }); - - // Times. - modelBuilder.Entity(entity => - { - entity.ToTable("restrict_times"); - - entity.HasOne(t => t.ApplicationRestrictionModel) - .WithMany(t => t.Times) - .HasForeignKey(t => t.ApplicationRestrictionId) - .OnDelete(DeleteBehavior.NoAction); - + entity.HasMany(t => t.Policies) + .WithOne(); }); // Account Logs Model @@ -264,41 +198,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { entity.ToTable("policies"); - entity.HasOne(t => t.OwnerIdentity) + entity.HasOne(t => t.Owner) .WithMany() - .HasForeignKey(t => t.OwnerIdentityId) + .HasForeignKey(t => t.OwnerId) .OnDelete(DeleteBehavior.NoAction); - entity.HasMany(t => t.ApplyFor) - .WithOne() - .HasForeignKey(t => t.PolicyId); - entity.Property(e => e.Id).IsRequired(); }); - // Identity Allowed on Policy Model - modelBuilder.Entity(entity => - { - entity.HasKey(t => new { t.PolicyId, t.IdentityId }); - - entity.HasOne(t => t.Policy) - .WithMany(t => t.ApplyFor) - .HasForeignKey(t => t.PolicyId); - - entity.HasOne(t => t.Identity) - .WithMany() - .HasForeignKey(t => t.IdentityId); - }); - - // Policy Requirement Model - modelBuilder.Entity(entity => - { - entity.ToTable("policy_requirements"); - entity.HasOne(t => t.Policy) - .WithMany() - .HasForeignKey(t => t.PolicyId); - }); - // Códigos OTPS. modelBuilder.Entity(entity => { @@ -319,6 +226,30 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.HasIndex(t => t.Mail).IsUnique(); }); + modelBuilder.Entity(entity => + { + entity.ToTable("policy_types_identity"); + entity.HasOne(t => t.Policy) + .WithMany() + .HasForeignKey(t => t.PolicyId); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable("ip_access_policy"); + entity.HasOne(t => t.Policy) + .WithMany() + .HasForeignKey(t => t.PolicyId); + }); + + modelBuilder.Entity(entity => + { + entity.ToTable("time_access_policy"); + entity.HasOne(t => t.Policy) + .WithMany() + .HasForeignKey(t => t.PolicyId); + }); + // Mail OTP. modelBuilder.Entity(entity => { @@ -377,9 +308,9 @@ public void Seed() // Formatear modelos. foreach (var app in apps) { - app.Identity.Type = Types.Cloud.Identity.Enumerations.IdentityType.Application; + app.Identity.Type = Types.Cloud.Identity.Enumerations.IdentityType.Service; app.Owner = new() { Id = app.OwnerId }; - app.Owner = EntityFramework.AttachOrUpdate(this, app.Owner); + app.Owner = this.AttachOrUpdate(app.Owner); } // Agregar aplicaciones. diff --git a/LIN.Cloud.Identity.Persistence/Extensions/EntityFramework.cs b/LIN.Cloud.Identity.Persistence/Extensions/EntityFramework.cs deleted file mode 100644 index 740e75c..0000000 --- a/LIN.Cloud.Identity.Persistence/Extensions/EntityFramework.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace LIN.Cloud.Identity.Persistence.Extensions; - -public static class EntityFramework -{ - /// - /// Adjunta una entidad al contexto si no está siendo rastreada. - /// Si ya está siendo rastreada, actualiza sus valores con los de la entidad proporcionada. - /// - /// El tipo de la entidad. - /// El contexto de la base de datos. - /// La entidad a adjuntar o actualizar. - public static TEntity AttachOrUpdate(this DbContext context, TEntity entity) where TEntity : class - { - - // Obtiene la clave primaria de la entidad - var key = context.Model.FindEntityType(typeof(TEntity))?.FindPrimaryKey(); - if (key == null) - { - throw new InvalidOperationException($"La entidad {typeof(TEntity).Name} no tiene una clave primaria definida."); - } - - // Construye un objeto anónimo con los valores de la clave primaria - var keyValues = key.Properties.Select(p => - typeof(TEntity).GetProperty(p.Name).GetValue(entity)).ToArray(); - - // Busca si la entidad ya está siendo rastreada en el contexto - var localEntity = context.Set().Local.FirstOrDefault(e => - { - bool isMatch = true; - for (int i = 0; i < key.Properties.Count; i++) - { - var property = key.Properties[i]; - var value1 = typeof(TEntity).GetProperty(property.Name).GetValue(e); - var value2 = keyValues[i]; - if (!object.Equals(value1, value2)) - { - isMatch = false; - break; - } - } - return isMatch; - }); - - if (localEntity != null) - { - return localEntity; - } - - // Si no está siendo rastreada, la adjunta al contexto - context.Attach(entity); - return entity; - } -} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 888bc09..24b999d 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -1,5 +1,7 @@ using LIN.Cloud.Identity.Persistence.Contexts; using LIN.Cloud.Identity.Persistence.Queries; +using LIN.Cloud.Identity.Persistence.Repositories; +using LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -17,13 +19,13 @@ public static class PersistenceExtensions /// Services. public static IServiceCollection AddPersistence(this IServiceCollection services, IConfigurationManager configuration) { - string? connectionName = "cloud"; + string? connectionName = "cloud-v4"; #if LOCAL - connectionName = "local"; + connectionName = "cloud-v4"; #elif DEBUG_DEV - connectionName = "cloud-dev"; + connectionName = "cloud-v4"; #elif RELEASE_DEV - connectionName = "cloud-dev"; + connectionName = "cloud-v4"; #endif services.AddDbContextPool(options => @@ -34,6 +36,19 @@ public static IServiceCollection AddPersistence(this IServiceCollection services services.AddScoped(); services.AddScoped(); + // Servicios de datos. + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + return services; } diff --git a/LIN.Cloud.Identity.Persistence/Formatters/Account.cs b/LIN.Cloud.Identity.Persistence/Formatters/Account.cs new file mode 100644 index 0000000..dc9ef9a --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Formatters/Account.cs @@ -0,0 +1,88 @@ +using LIN.Types.Models; +using System.Text.RegularExpressions; +using IdentityService = LIN.Types.Cloud.Identity.Enumerations.IdentityService; + +namespace LIN.Cloud.Identity.Persistence.Formatters; + +public class Account +{ + + /// + /// Procesar el modelo. + /// + /// Modelo + public static List Validate(AccountModel baseAccount) + { + + List errors = []; + + if (string.IsNullOrWhiteSpace(baseAccount.Name)) + errors.Add(new ErrorModel() + { + Tittle = "Nombre invalido", + Description = "El nombre del usuario no puede estar vacío.", + }); + + if (baseAccount.Identity == null || string.IsNullOrWhiteSpace(baseAccount.Identity.Unique)) + errors.Add(new ErrorModel() + { + Tittle = "Identidad no valida", + Description = "La cuenta debe tener un identificador único valido.", + }); + + if (!ValidarCadena(baseAccount.Identity.Unique)) + errors.Add(new ErrorModel() + { + Tittle = "Identidad no valida", + Description = "La identidad de la cuenta no puede contener símbolos NO alfanuméricos." + }); + + return errors; + } + + + + static bool ValidarCadena(string cadena) + { + // Patrón de expresión regular para permitir solo letras o números + string patron = "^[a-zA-Z0-9]*$"; + + // Comprobar la coincidencia con el patrón + return Regex.IsMatch(cadena, patron); + } + + + + /// + /// Procesar el modelo. + /// + /// Modelo + public static AccountModel Process(AccountModel baseAccount) + { + return new AccountModel() + { + Id = 0, + IdentityService = IdentityService.LIN, + Name = baseAccount.Name.Trim(), + Profile = baseAccount.Profile, + Password = Global.Utilities.Cryptography.Encrypt(baseAccount.Password), + Visibility = baseAccount.Visibility, + IdentityId = 0, + AccountType = baseAccount.AccountType, + Identity = new() + { + Id = 0, + Status = IdentityStatus.Enable, + Type = IdentityType.Account, + CreationTime = DateTime.Now, + EffectiveTime = DateTime.Now, + ExpirationTime = DateTime.Now.AddYears(5), + Roles = [], + Unique = baseAccount.Identity.Unique.Trim() + } + }; + + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs b/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs new file mode 100644 index 0000000..719804f --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs @@ -0,0 +1,20 @@ +namespace LIN.Cloud.Identity.Persistence.Formatters; + +public class Identities +{ + + /// + /// Procesar el modelo. + /// + /// Modelo + public static void Process(IdentityModel id) + { + id.Id = 0; + id.ExpirationTime = DateTime.Now.AddYears(10); + id.EffectiveTime = DateTime.Now; + id.CreationTime = DateTime.Now; + id.Status = IdentityStatus.Enable; + id.Unique = id.Unique.Trim(); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 3a617e8..c24a02a 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -8,10 +8,11 @@ - - + + - + + diff --git a/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs b/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs index 3d20057..de99617 100644 --- a/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs +++ b/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs @@ -6,6 +6,6 @@ public class OtpDatabaseModel public string Code { get; set; } public DateTime ExpireTime { get; set; } public bool IsUsed { get; set; } - public LIN.Types.Cloud.Identity.Models.AccountModel Account { get; set; } + public LIN.Types.Cloud.Identity.Models.Identities.AccountModel Account { get; set; } public int AccountId { get; set; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Models/QueryIdentityFilter.cs b/LIN.Cloud.Identity.Persistence/Models/QueryIdentityFilter.cs new file mode 100644 index 0000000..6183641 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Models/QueryIdentityFilter.cs @@ -0,0 +1,8 @@ +namespace LIN.Cloud.Identity.Persistence.Models; + +public class QueryIdentityFilter +{ + public FindOn FindOn { get; set; } + public bool IncludeDates { get; set; } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs b/LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs index 89c147a..5ce0efc 100644 --- a/LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs +++ b/LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs @@ -1,6 +1,5 @@ namespace LIN.Cloud.Identity.Persistence.Models; - public class QueryObjectFilter { public int AccountContext { get; set; } @@ -12,7 +11,6 @@ public class QueryObjectFilter } - public enum FindOn { StableAccounts, diff --git a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs index c624cab..e33d087 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/AccountFindable.cs @@ -1,11 +1,8 @@ -using LIN.Cloud.Identity.Persistence.Models; -using LIN.Cloud.Identity.Persistence.Queries.Interfaces; -using LIN.Types.Cloud.Identity.Enumerations; -using LIN.Types.Cloud.Identity.Models; +using LIN.Cloud.Identity.Persistence.Queries.Interfaces; namespace LIN.Cloud.Identity.Persistence.Queries; -public class AccountFindable(Contexts.DataContext context) : IFindable +public class AccountFindable(DataContext context) : IFindable { /// @@ -16,7 +13,7 @@ private IQueryable OnStable() { // Hora actual. - var now = DateTime.Now; + var now = DateTime.UtcNow; // Consulta. var query = from account in context.Accounts @@ -29,7 +26,6 @@ private IQueryable OnStable() } - /// /// Buscar en todas las cuentas. /// @@ -38,7 +34,7 @@ private IQueryable OnAll() { // Hora actual. - var now = DateTime.Now; + var now = DateTime.UtcNow; // Consulta. var query = from account in context.Accounts @@ -49,21 +45,33 @@ private IQueryable OnAll() } + /// + /// Obtener las cuentas según el Id. + /// + /// Id de la cuenta. + /// Filtros. + public IQueryable GetAccounts(int id, QueryObjectFilter filters) + { + // Query general + IQueryable accounts; + accounts = from account in (filters.FindOn == Models.FindOn.StableAccounts) ? OnStable() : OnAll() + where account.Id == id + select account; - public IQueryable GetAccounts(int id, QueryObjectFilter filters) + // Retorno + return accounts; + } + + public IQueryable GetAccounts(string user, QueryObjectFilter filters) { // Query general IQueryable accounts; - if (filters.FindOn == Models.FindOn.StableAccounts) - accounts = from account in (filters.FindOn == Models.FindOn.StableAccounts) ? OnStable() : OnAll() - where account.Id == id - select account; - - // Armar el modelo - accounts = null!;// BuildModel(accounts, filters, context); + accounts = from account in (filters.FindOn == Models.FindOn.StableAccounts) ? OnStable() : OnAll() + where account.Identity.Unique == user + select account; // Retorno return accounts; diff --git a/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs index 0193ff7..5a0ad09 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs @@ -1,5 +1,5 @@ using LIN.Cloud.Identity.Persistence.Queries.Interfaces; -using LIN.Types.Cloud.Identity.Models; +using LIN.Types.Cloud.Identity.Models.Identities; namespace LIN.Cloud.Identity.Persistence.Queries; @@ -14,4 +14,9 @@ public IQueryable GetAccounts(List ids, Models.QueryObjectFil { throw new NotImplementedException(); } + + public IQueryable GetAccounts(string unique, QueryObjectFilter filter) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Queries/Interfaces/IFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/Interfaces/IFindable.cs index 5ddc722..84ec2ac 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/Interfaces/IFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/Interfaces/IFindable.cs @@ -1,17 +1,18 @@ namespace LIN.Cloud.Identity.Persistence.Queries.Interfaces; - public interface IFindable { - /// /// Encontrar por Id. /// /// Id único. public IQueryable GetAccounts(int id, Models.QueryObjectFilter filter); - + /// + /// Encontrar por unique. + /// + public IQueryable GetAccounts(string unique, Models.QueryObjectFilter filter); /// /// Encontrar por Ids. @@ -19,5 +20,4 @@ public interface IFindable /// Lista de ids únicos. public IQueryable GetAccounts(List ids, Models.QueryObjectFilter filter); - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/AccountLogs.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs similarity index 66% rename from LIN.Cloud.Identity/Data/AccountLogs.cs rename to LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs index 698cc43..cc12a19 100644 --- a/LIN.Cloud.Identity/Data/AccountLogs.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs @@ -1,6 +1,6 @@ -namespace LIN.Cloud.Identity.Data; +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; -public class AccountLogs(DataContext context) +internal class AccountLogRepository(DataContext context) : IAccountLogRepository { /// @@ -9,7 +9,6 @@ public class AccountLogs(DataContext context) /// Modelo. public async Task Create(AccountLog log) { - // Formato del modelo. log.Id = 0; @@ -20,7 +19,7 @@ public async Task Create(AccountLog log) log.Account = new() { Id = log.AccountId }; // Ya existe. - log.Account = context.AttachOrUpdate(log.Account); + log.Account = context.AttachOrUpdate(log.Account)!; // Si hay una app. if (log.Application is not null) @@ -76,4 +75,35 @@ public async Task> ReadAll(int accountId, DateTime? } } + + /// + /// Contar los logs de autenticación del dia. + /// + /// Id de la cuenta. + public async Task> Count(int id) + { + try + { + // Tiempo. + var time = DateTime.Now; + + // Contar. + int count = await (from a in context.AccountLogs + where a.AccountId == id + && a.AuthenticationMethod == AuthenticationMethods.Authenticator + where a.Time.Year == time.Year + && a.Time.Month == time.Month + && a.Time.Day == time.Day + select a).CountAsync(); + + // Success. + return new(Responses.Success, count); + } + catch (Exception) + { + return new(Responses.NotRows); + } + + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Accounts.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs similarity index 67% rename from LIN.Cloud.Identity/Data/Accounts.cs rename to LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs index 40b85e2..1db8540 100644 --- a/LIN.Cloud.Identity/Data/Accounts.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs @@ -1,17 +1,15 @@ -namespace LIN.Cloud.Identity.Data; +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; -public class Accounts(DataContext context, ILogger logger) +internal class AccountRepository(DataContext context, Queries.AccountFindable accountFindable) : IAccountRepository { /// - /// Crear nueva cuenta. [Transacción] + /// Crear nueva cuenta. /// /// Modelo de la cuenta. /// Id de la organización. - public async Task> Create(AccountModel modelo, int organization = 0) + public async Task> Create(AccountModel modelo, int organization) { - - // Pre. modelo.Id = 0; modelo.IdentityId = 0; @@ -20,7 +18,6 @@ public async Task> Create(AccountModel modelo, int try { - // Guardar la cuenta. await context.Accounts.AddAsync(modelo); context.SaveChanges(); @@ -29,34 +26,34 @@ public async Task> Create(AccountModel modelo, int if (organization > 0) { - var generalGroup = (from org in context.Organizations - where org.Id == organization - select org.DirectoryId).FirstOrDefault(); + var directory = (from org in context.Organizations + where org.Id == organization + select org.DirectoryId).FirstOrDefault(); - if (generalGroup <= 0) - { - throw new Exception("Organización no encontrada."); - } + // Si la organización no existe. + if (directory <= 0) + return new(Responses.NotFoundDirectory) + { + Message = "El directorio no existe" + }; - var x = new GroupMember() + // Integrarlo a la organización. + var groupMember = new GroupMember() { Group = new() { - Id = generalGroup + Id = directory }, Identity = modelo.Identity, Type = GroupMemberTypes.User }; - context.Attach(x.Group); - - context.GroupMembers.Add(x); - + // El grupo existe. + groupMember.Group = context.AttachOrUpdate(groupMember.Group)!; + context.GroupMembers.Add(groupMember); context.SaveChanges(); - } - // Confirmar los cambios. transaction?.Commit(); @@ -65,18 +62,12 @@ public async Task> Create(AccountModel modelo, int Response = Responses.Success, Model = modelo }; - } - catch (Exception ex) + catch (Exception) { - logger.LogError(ex, "Error al crear cuenta"); transaction?.Rollback(); - return new() - { - Response = Responses.ExistAccount - }; + return new(Responses.ExistAccount); } - } @@ -87,34 +78,21 @@ public async Task> Create(AccountModel modelo, int /// Filtros de búsqueda. public async Task> Read(int id, QueryObjectFilter filters) { - try { - // Consulta de las cuentas. - var account = await Builders.Account.GetAccounts(id, filters, context).FirstOrDefaultAsync(); + var account = await accountFindable.GetAccounts(id, filters).FirstOrDefaultAsync(); // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; + if (account is null) + return new(Responses.NotRows); // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - + return new(Responses.Success, account); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } } @@ -129,33 +107,20 @@ public async Task> Read(string unique, QueryObject { try { - // Consulta de las cuentas. - var account = await Builders.Account.GetAccounts(unique, filters, context).FirstOrDefaultAsync(); + var account = await accountFindable.GetAccounts(unique, filters).FirstOrDefaultAsync(); // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; + if (account is null) + return new(Responses.NotRows); // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - + return new(Responses.Success, account); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } - } @@ -166,36 +131,22 @@ public async Task> Read(string unique, QueryObject /// Filtros de búsqueda. public async Task> ReadByIdentity(int id, QueryObjectFilter filters) { - try { - // Consulta de las cuentas. var account = await Builders.Account.GetAccountsByIdentity(id, filters, context).FirstOrDefaultAsync(); // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; + if (account is null) + return new(Responses.NotRows); // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - + return new(Responses.Success, account); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } - } @@ -206,15 +157,12 @@ public async Task> ReadByIdentity(int id, QueryObj /// Filtros public async Task> Search(string pattern, QueryObjectFilter filters) { - - // Ejecución try { - List accountModels = await Builders.Account.Search(pattern, filters, context).Take(10).ToListAsync(); // Si no existe el modelo - if (accountModels == null || !accountModels.Any()) + if (accountModels == null || accountModels.Count == 0) return new(Responses.NotRows); return new(Responses.Success, accountModels); @@ -244,7 +192,7 @@ public async Task> FindAll(List ids, QueryObj var result = await query.ToListAsync(); // Si no existe el modelo - if (result == null || !result.Any()) + if (result == null || result.Count == 0) return new(Responses.NotRows); return new(Responses.Success, result); @@ -263,18 +211,16 @@ public async Task> FindAll(List ids, QueryObj /// Lista de IDs public async Task> FindAllByIdentities(List ids, QueryObjectFilter filters) { - // Ejecución try { - var query = Builders.Account.FindAllByIdentities(ids, filters, context); // Ejecuta var result = await query.ToListAsync(); // Si no existe el modelo - if (result == null || !result.Any()) + if (result == null || result.Count == 0) return new(Responses.NotRows); return new(Responses.Success, result); @@ -294,7 +240,6 @@ public async Task> FindAllByIdentities(List i /// Nueva contraseña. public async Task UpdatePassword(int accountId, string password) { - try { var account = await (from a in context.Accounts @@ -308,12 +253,8 @@ public async Task UpdatePassword(int accountId, string password) } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Applications.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/ApplicationRepository.cs similarity index 89% rename from LIN.Cloud.Identity/Data/Applications.cs rename to LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/ApplicationRepository.cs index 85849af..e27cdaa 100644 --- a/LIN.Cloud.Identity/Data/Applications.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/ApplicationRepository.cs @@ -1,6 +1,6 @@ -namespace LIN.Cloud.Identity.Data; +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; -public class Applications(DataContext context) +internal class ApplicationRepository(DataContext context) : IApplicationRepository { /// @@ -15,7 +15,7 @@ public async Task Create(ApplicationModel modelo) try { // Modelo ya existe. - modelo.Owner = context.AttachOrUpdate(modelo.Owner); + modelo.Owner = context.AttachOrUpdate(modelo.Owner)!; // Guardar la identidad. await context.Applications.AddAsync(modelo); diff --git a/LIN.Cloud.Identity/Data/Builders/Account.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs similarity index 94% rename from LIN.Cloud.Identity/Data/Builders/Account.cs rename to LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs index d7590e8..26dce88 100644 --- a/LIN.Cloud.Identity/Data/Builders/Account.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs @@ -1,4 +1,4 @@ -namespace LIN.Cloud.Identity.Data.Builders; +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework.Builders; public class Account { @@ -245,11 +245,11 @@ private static IQueryable BuildModel(IQueryable quer select new AccountModel { Id = account.Id, - Name = (filters.IsAdmin + Name = filters.IsAdmin || account.Visibility == Visibility.Visible || filters.AccountContext == account.Id - || (context.GroupMembers.FirstOrDefault(t => t.Group.Members.Any(t => t.IdentityId == filters.IdentityContext)) != null) - ) + || context.GroupMembers.FirstOrDefault(t => t.Group.Members.Any(t => t.IdentityId == filters.IdentityContext)) != null + ? account.Name : "Usuario privado", Identity = new() @@ -266,8 +266,7 @@ private static IQueryable BuildModel(IQueryable quer Profile = filters.IncludePhoto ? string.Empty : selector, IdentityId = account.Identity.Id, IdentityService = account.IdentityService, - AccountType = account.AccountType, - IsLINAdmin = account.IsLINAdmin + AccountType = account.AccountType }; var s = queryFinal.ToQueryString(); diff --git a/LIN.Cloud.Identity/Data/Builders/Identities.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Identity.cs similarity index 93% rename from LIN.Cloud.Identity/Data/Builders/Identities.cs rename to LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Identity.cs index 279eb04..38e63e2 100644 --- a/LIN.Cloud.Identity/Data/Builders/Identities.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Identity.cs @@ -1,10 +1,8 @@ -namespace LIN.Cloud.Identity.Data.Builders; - +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework.Builders; public class Identities { - /// /// Buscar en la identidad estables. /// @@ -13,7 +11,7 @@ public static IQueryable OnStable(DataContext context) { // Hora actual. - var now = DateTime.Now; + var now = DateTime.UtcNow; // Consulta. var query = from identity in context.Identities @@ -27,7 +25,6 @@ public static IQueryable OnStable(DataContext context) } - /// /// Buscar en todas las identidades. /// @@ -36,7 +33,7 @@ public static IQueryable OnAll(DataContext context) { // Hora actual. - var now = DateTime.Now; + var now = DateTime.UtcNow; // Consulta. var query = from identity in context.Identities @@ -47,7 +44,6 @@ public static IQueryable OnAll(DataContext context) } - /// /// Obtener identidades. /// @@ -78,14 +74,13 @@ public static IQueryable GetIds(int id, QueryIdentityFilter filte } - /// /// Obtener identidades. /// /// Unique /// Filtros /// Contexto - public static IQueryable GetIds(string unique, Services.Models.QueryIdentityFilter filters, DataContext context) + public static IQueryable GetIds(string unique, QueryIdentityFilter filters, DataContext context) { // Query general @@ -109,13 +104,12 @@ public static IQueryable GetIds(string unique, Services.Models.Qu } - /// /// Construir el modelo. /// /// Consulta base. /// Filtros. - private static IQueryable BuildModel(IQueryable query, Services.Models.QueryIdentityFilter filters) + private static IQueryable BuildModel(IQueryable query, QueryIdentityFilter filters) { var final = from id in query @@ -133,6 +127,4 @@ private static IQueryable BuildModel(IQueryable qu } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/GroupMembers.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupMemberRepository.cs similarity index 50% rename from LIN.Cloud.Identity/Data/GroupMembers.cs rename to LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupMemberRepository.cs index 8d10dea..250de8a 100644 --- a/LIN.Cloud.Identity/Data/GroupMembers.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupMemberRepository.cs @@ -1,51 +1,38 @@ -namespace LIN.Cloud.Identity.Data; +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; - -public class GroupMembers(DataContext context) +internal class GroupMemberRepository(DataContext context) : IGroupMemberRepository { - /// /// Crear nuevo integrante en un grupo. /// /// Modelo. - /// Contexto de conexión. public async Task Create(GroupMember modelo) { try { - // Ya existen. - context.Attach(modelo.Group); - context.Attach(modelo.Identity); + modelo.Group = context.AttachOrUpdate(modelo.Group)!; + modelo.Identity = context.AttachOrUpdate(modelo.Identity)!; // Guardar la identidad. await context.GroupMembers.AddAsync(modelo); context.SaveChanges(); - return new() - { - Response = Responses.Success - }; - + return new(Responses.Success); } catch (Exception) { - return new() - { - Response = Responses.Undefined - }; + return new(); } } - /// - /// Crear nuevos integrante en un grupo. + /// Crear nuevos integrantes en un grupo. /// /// Modelos. - /// Contexto de conexión. public async Task Create(IEnumerable modelos) { try @@ -57,15 +44,15 @@ public async Task Create(IEnumerable modelos) try { await context.Database.ExecuteSqlRawAsync(""" - INSERT INTO [dbo].[group_members] - ([IdentityId] - ,[GroupId] - ,[Type]) - VALUES - ({0} - ,{1} - ,{2}) - """, member.Identity.Id, member.Group.Id, (int)member.Type); + INSERT INTO [dbo].[group_members] + ([IdentityId] + ,[GroupId] + ,[Type]) + VALUES + ({0} + ,{1} + ,{2}) + """, member.Identity.Id, member.Group.Id, (int)member.Type); } catch (Exception) @@ -90,18 +77,14 @@ INSERT INTO [dbo].[group_members] } - /// - /// Obtener los miembros de un grupo. + /// Obtener los integrantes de un grupo. /// /// Id del grupo. - /// Contexto de base de datos. public async Task> ReadAll(int id) { - try { - // Consulta. var members = await (from gm in context.GroupMembers where gm.GroupId == id @@ -115,212 +98,101 @@ public async Task> ReadAll(int id) // Si la cuenta no existe. - if (members == null) - return new() - { - Response = Responses.NotRows - }; + if (members is null) + return new(Responses.NotRows); // Success. - return new() - { - Response = Responses.Success, - Models = members - }; - + return new(Responses.Success, members); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } - } - /// /// Buscar en los integrantes de un grupo. /// /// Patron de búsqueda. /// Id del grupo. - /// Contexto de base de datos. public async Task> Search(string pattern, int group) { - try { - // Consulta. var members = await (from g in context.GroupMembers where g.GroupId == @group && g.Identity.Unique.Contains(pattern.ToLower()) select g.Identity).ToListAsync(); - - // Si la cuenta no existe. - if (members == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Models = members - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Buscar en los integrantes de un grupo. - /// - /// Patron de búsqueda. - /// Id del grupo. - /// Contexto de base de datos. - public async Task> SearchGroups(string pattern, int group) - { - - try - { - - // Consulta. - var members = await (from g in context.GroupMembers - where g.GroupId == @group - && g.Identity.Unique.Contains(pattern, StringComparison.CurrentCultureIgnoreCase) - && g.Identity.Type == IdentityType.Group - select g.Identity).ToListAsync(); - - // Si la cuenta no existe. - if (members == null) - return new() - { - Response = Responses.NotRows - }; + if (members is null) + return new(Responses.NotRows); // Success. - return new() - { - Response = Responses.Success, - Models = members - }; - + return new(Responses.Success, members); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } - } - /// /// Eliminar un integrante de un grupo. /// /// Identidad. /// Id del grupo. - /// Contexto de base de datos. public async Task Delete(int identity, int group) { - try { - - var response = await (from g in context.GroupMembers where g.GroupId == @group && g.IdentityId == identity select g).ExecuteDeleteAsync(); - - // Success. - return new() - { - Response = Responses.Success - }; - + return new(Responses.Success); } catch (Exception) { - return new() - { - Response = Responses.Undefined - }; + return new(); } - } - /// /// Obtener los grupos donde una identidad esta de integrante /// - /// - /// - /// + /// ¿ public async Task> OnMembers(int organization, int identity) { - try { - // Consulta. var groups = await (from g in context.GroupMembers - where g.Group.OwnerId == organization + where g.Group.Identity.OwnerId == organization && g.IdentityId == identity select new GroupModel { Id = g.Group.Id, Identity = g.Group.Identity, - Name = g.Group.Name, - OwnerId = g.Group.OwnerId + Name = g.Group.Name }).ToListAsync(); // Si la cuenta no existe. - if (groups == null) - return new() - { - Response = Responses.NotRows - }; + if (groups is null) + return new(Responses.NotRows); // Success. - return new() - { - Response = Responses.Success, - Models = groups - }; - + return new(Responses.Success, groups); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } - } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Groups.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupRepository.cs similarity index 57% rename from LIN.Cloud.Identity/Data/Groups.cs rename to LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupRepository.cs index 0c38444..78fc45c 100644 --- a/LIN.Cloud.Identity/Data/Groups.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupRepository.cs @@ -1,17 +1,14 @@ -namespace LIN.Cloud.Identity.Data; +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; -public class Groups(DataContext context) +internal class GroupRepository(DataContext context) : IGroupRepository { - /// /// Crear nuevo grupo. /// /// Modelo. - /// Contexto de conexión. public async Task> Create(GroupModel modelo) { - // Pre. modelo.Id = 0; // Transacción. @@ -19,97 +16,77 @@ public async Task> Create(GroupModel modelo) try { - // Miembros. foreach (var e in modelo.Members) { e.Group = modelo; - context.Attach(e.Identity); + e.Identity = context.AttachOrUpdate(e.Identity)!; } // Fijar la organización. - modelo.Owner = new() + modelo.Identity.Owner = new() { - Id = modelo.OwnerId ?? 0 + Id = modelo.Identity.OwnerId ?? 0 }; - // Si no existe. - if (modelo.Owner != null) - context.Attach(modelo.Owner); + modelo.Identity.Owner = context.AttachOrUpdate(modelo.Identity.Owner); // Guardar la identidad. await context.Groups.AddAsync(modelo); - // Obtener el directorio general. - var dir = (from org in context.Organizations - where org.Id == modelo.OwnerId + var generalGroupInformation = (from org in context.Organizations + where org.Id == modelo.Identity.OwnerId select new { org.DirectoryId, org.Directory.Identity.Unique }).FirstOrDefault(); // Si no se encontró el directorio. - if (dir == null) + if (generalGroupInformation is null) { transaction.Rollback(); - return new() - { - Response = Responses.NotRows - }; + return new(Responses.NotRows); } // Nueva identidad. - modelo.Identity.Unique = $"{modelo.Identity.Unique}@{dir.Unique}"; + modelo.Identity.Unique = $"{modelo.Identity.Unique}@{generalGroupInformation.Unique}"; // Guardar. context.SaveChanges(); - - var dirModel = new GroupModel + // Agregar el grupo al directorio general. + var generalDirectory = new GroupModel { - Id = dir.DirectoryId + Id = generalGroupInformation.DirectoryId }; - context.Attach(dirModel); + // Si no se encontró el directorio. + generalDirectory = context.AttachOrUpdate(generalDirectory); context.GroupMembers.Add(new() { - Group = dirModel, + Group = generalDirectory!, Identity = modelo.Identity, Type = GroupMemberTypes.Group }); - context.SaveChanges(); - transaction.Commit(); - return new() - { - Response = Responses.Success, - Model = modelo - }; - + return new(Responses.Success, modelo); } catch (Exception) { transaction.Rollback(); - return new() - { - Response = Responses.Undefined - }; + return new(); } - } - /// /// Obtener un grupo según el Id. /// /// Id. - /// Contexto de base de datos. public async Task> Read(int id) { - try { // Consulta. @@ -125,40 +102,25 @@ public async Task> Read(int id) }).FirstOrDefaultAsync(); // Si la cuenta no existe. - if (group == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = group - }; + if (group is null) + return new(Responses.NotRows); + return new(Responses.Success, group); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } } - /// /// Obtener un grupo según el Id de la identidad. /// /// Identidad. - /// Contexto de base de datos. public async Task> ReadByIdentity(int id) { - try { // Consulta. @@ -174,158 +136,102 @@ public async Task> ReadByIdentity(int id) }).FirstOrDefaultAsync(); // Si la cuenta no existe. - if (group == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = group - }; + if (group is null) + return new(Responses.NotRows); + return new(Responses.Success, group); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } } - /// /// Obtener los grupos asociados a una organización. /// /// Organización. - /// Contexto de base de datos. public async Task> ReadAll(int organization) { - try { - // Consulta. var groups = await (from g in context.Groups - where g.OwnerId == organization + where g.Identity.OwnerId == organization select new GroupModel { Id = g.Id, Identity = g.Identity, - Name = g.Name, - OwnerId = g.OwnerId + Name = g.Name }).ToListAsync(); // Success. - return new() - { - Response = Responses.Success, - Models = groups ?? [] - }; - + return new(Responses.Success, groups ?? []); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } - } - /// /// Obtener la organización propietaria de un grupo. /// /// Id del grupo. - /// Contexto de base de datos. public async Task> GetOwner(int id) { - try { // Consulta. var ownerId = await (from g in context.Groups where g.Id == id - select g.OwnerId).FirstOrDefaultAsync(); + select g.Identity.OwnerId).FirstOrDefaultAsync(); // Si la cuenta no existe. - if (ownerId == null) - return new() - { - Response = Responses.NotRows - }; + if (ownerId is null || ownerId.Value <= 0) + return new(Responses.NotRows); // Success. - return new() - { - Response = ownerId == null ? Responses.NotRows : Responses.Success, - Model = ownerId ?? 0 - }; - + return new(Responses.Success, ownerId ?? 0); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } } - /// /// Obtener la organización propietaria de un grupo. /// /// Id de la identidad. - /// Contexto de base de datos. public async Task> GetOwnerByIdentity(int id) { - try { // Consulta. var ownerId = await (from g in context.Groups where g.IdentityId == id - select g.OwnerId).FirstOrDefaultAsync(); + select g.Identity.OwnerId).FirstOrDefaultAsync(); // Si la cuenta no existe. - if (ownerId == null) - return new() - { - Response = Responses.NotRows - }; + if (ownerId is null || ownerId.Value <= 0) + return new(Responses.NotRows); // Success. - return new() - { - Response = ownerId == null ? Responses.NotRows : Responses.Success, - Model = ownerId ?? 0 - }; - + return new(Responses.Success, ownerId ?? 0); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRepository.cs new file mode 100644 index 0000000..74469dd --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRepository.cs @@ -0,0 +1,86 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; + +internal class IdentityRepository(DataContext context) : IIdentityRepository +{ + + /// + /// Crear nueva identidad. + /// + public async Task> Create(IdentityModel modelo) + { + modelo.Id = 0; + try + { + foreach (var rol in modelo.Roles) + rol.Identity = modelo; + + // Organización propietaria. + if (modelo.Owner is not null) + modelo.Owner = context.AttachOrUpdate(modelo.Owner); + + // Guardar la identidad. + await context.Identities.AddAsync(modelo); + context.SaveChanges(); + + return new(Responses.Success, modelo); + } + catch (Exception) + { + return new(Responses.ResourceExist); + } + } + + + /// + /// Obtener una identidad según el Id. + /// + /// Id de la identidad. + /// Filtros. + public async Task> Read(int id, QueryIdentityFilter filters) + { + try + { + // Consulta de las cuentas. + var identity = await Builders.Identities.GetIds(id, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (identity == null) + return new(Responses.NotRows); + + // Success. + return new(Responses.Success, identity); + } + catch (Exception) + { + return new(Responses.ExistAccount); + } + + } + + + /// + /// Obtener una identidad según el Unique. + /// + /// Unique. + /// Filtros de búsqueda. + public async Task> Read(string unique, QueryIdentityFilter filters) + { + try + { + // Consulta de las cuentas. + var identity = await Builders.Identities.GetIds(unique, filters, context).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (identity == null) + return new(Responses.NotRows); + + // Success. + return new(Responses.Success, identity); + } + catch (Exception) + { + return new(Responses.ExistAccount); + } + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/IdentityRoles.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRolesRepository.cs similarity index 79% rename from LIN.Cloud.Identity/Data/IdentityRoles.cs rename to LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRolesRepository.cs index a81c9b1..eb85191 100644 --- a/LIN.Cloud.Identity/Data/IdentityRoles.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRolesRepository.cs @@ -1,19 +1,16 @@ -namespace LIN.Cloud.Identity.Data; +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; -public class IdentityRoles(DataContext context) +internal class IdentityRolesRepository(DataContext context) : IIdentityRolesRepository { /// /// Crear nuevo rol en identidad. /// /// Modelo. - /// Contexto de conexión. public async Task Create(IdentityRolesModel modelo) { - try { - // Attach. context.Attach(modelo.Identity); context.Attach(modelo.Organization); @@ -22,18 +19,11 @@ public async Task Create(IdentityRolesModel modelo) await context.IdentityRoles.AddAsync(modelo); context.SaveChanges(); - return new() - { - Response = Responses.Success - }; - + return new(Responses.Success); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } } @@ -44,47 +34,31 @@ public async Task Create(IdentityRolesModel modelo) /// /// Identidad. /// Organización. - /// Contexto de base de datos. public async Task> ReadAll(int identity, int organization) { - try { - List Roles = []; await RolesOn(identity, organization, [], Roles); - return new() - { - Models = Roles, - Response = Responses.Success - }; - + return new(Responses.Success, Roles); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } - } - - /// /// Eliminar el rol de una identidad en una organización. /// /// Identidad. /// Rol. /// Organización. - /// Contexto de base de datos. public async Task Remove(int identity, Roles rol, int organization) { - try { @@ -95,35 +69,23 @@ public async Task Remove(int identity, Roles rol, int organization && ir.OrganizationId == organization select ir).ExecuteDeleteAsync(); - return new() - { - Response = Responses.Success - }; - + return new(Responses.Success); } catch (Exception) { - return new() - { - Response = Responses.ExistAccount - }; + return new(); } } - - - - - - - - - - - - + /// + /// Obtener y buscar las identidades y roles en forma jerárquica. + /// + /// Identidad base. + /// Organización. + /// Identidades recolectadas. + /// Roles recolectados. private async Task RolesOn(int identity, int organization, List ids, List roles) { @@ -175,11 +137,7 @@ private async Task RolesOn(int identity, int organization, List ids, List + /// Valida si una identidad es miembro de una organización. + /// + /// Identidad. + /// Id de la organización. + public async Task> IamIn(int id, int organization) + { + try + { + // Consulta. + var query = await (from org in context.Organizations + where org.Id == organization + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + where gm.IdentityId == id + select new + { + gm.Type + }).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (query is null) + { + + var directory = await (from A in context.Organizations + where A.Directory.IdentityId == id + && A.Id == organization + select A).AnyAsync(); + + if (!directory) + return new(Responses.NotRows); + } + + + // Success. + return new(Responses.Success, query?.Type ?? GroupMemberTypes.Group); + } + catch (Exception) + { + return new(); + } + + } + + + /// + /// Expulsar identidades de la organización. + /// + /// Lista de identidades. + /// Id de la organización. + /// Respuesta del proceso. + public async Task Expulse(IEnumerable ids, int organization) + { + try + { + // Desactivar identidades (Solo creadas dentro de la propia organización). + var baseQuery = (from member in context.GroupMembers + where ids.Contains(member.IdentityId) + join org in context.Organizations + on member.Identity.OwnerId equals org.Id + where org.Id == organization + select member); + + // Desactivar identidades (Solo creadas dentro de la propia organización). + await baseQuery.Where(m => m.Type != GroupMemberTypes.Guest).Select(m => m.Identity).ExecuteUpdateAsync(t => t.SetProperty(t => t.Status, IdentityStatus.Disable)); + + // Eliminar accesos (Tanto propios de la organización como los externos). + await baseQuery.ExecuteDeleteAsync(); + + // Eliminar roles asociados. + await (from rol in context.IdentityRoles + where ids.Contains(rol.IdentityId) + && rol.OrganizationId == organization + select rol).ExecuteDeleteAsync(); + + // Success. + return new(Responses.Success); + } + catch (Exception) + { + return new(); + } + + } + + + /// + /// Obtener los integrantes de una organización. + /// + /// Id de la organización. + public async Task> ReadAll(int id) + { + try + { + // Consulta. + var query = await (from org in context.Organizations + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + where gm.IdentityId == id + select org).ToListAsync(); + + // Success. + return new(Responses.Success, query); + } + catch (Exception) + { + return new(); + } + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs new file mode 100644 index 0000000..b390ede --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs @@ -0,0 +1,196 @@ +using LIN.Cloud.Identity.Persistence.Formatters; + +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; + +internal class OrganizationRepository(DataContext context) : IOrganizationRepository +{ + + /// + /// Crear nueva organización. + /// + /// Modelo de la organización. + public async Task Create(OrganizationModel modelo) + { + // Aislar el contexto de la base de datos. + using var transaction = context.Database.BeginTransaction(); + + try + { + // Metadata. + modelo.Directory.Name = "Directorio General"; + modelo.Directory.Description = "Directorio General de la organización"; + modelo.Directory.Identity.Owner = modelo; + modelo.Directory.Identity.Type = IdentityType.Group; + + // Agregar la organización. + await context.Organizations.AddAsync(modelo); + context.SaveChanges(); + + // Crear la cuenta administrativa. + var account = new AccountModel() + { + Id = 0, + Visibility = Visibility.Hidden, + Name = "Admin", + Password = $"pwd@{DateTime.UtcNow.Year}", + IdentityService = IdentityService.LIN, + Identity = new IdentityModel() + { + Status = IdentityStatus.Enable, + CreationTime = DateTime.UtcNow, + EffectiveTime = DateTime.UtcNow, + ExpirationTime = DateTime.UtcNow.AddYears(10), + Unique = $"admin@{modelo.Directory.Identity.Unique}" + } + }; + + // Formatear la cuenta. + account = Account.Process(account); + + await context.Accounts.AddAsync(account); + + context.SaveChanges(); + + // IamRoles. + var rol = new IdentityRolesModel + { + Identity = account.Identity, + Organization = modelo, + Rol = Roles.Administrator + }; + + await context.IdentityRoles.AddAsync(rol); + + context.SaveChanges(); + + modelo.Directory.Identity.Owner = modelo; + modelo.Directory.Members.Add(new() + { + Group = modelo.Directory, + Identity = account.Identity, + Type = GroupMemberTypes.User + }); + + context.SaveChanges(); + transaction.Commit(); + + return new(Responses.Success, modelo.Id); + + } + catch (Exception) + { + transaction.Rollback(); + return new(); + } + + } + + + /// + /// Obtener una organización por su Id. + /// + /// Id de la organización. + public async Task> Read(int id) + { + try + { + // Consultar. + var org = await (from g in context.Organizations + where g.Id == id + select g).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (org is null) + return new(Responses.NotRows); + + // Success. + return new(Responses.Success, org); + } + catch (Exception) + { + return new(Responses.ExistAccount); + } + + } + + + /// + /// Obtener el dominio (Identidad principal) de la organización. + /// + /// Id de la organización. + public async Task> GetDomain(int id) + { + try + { + var org = await (from g in context.Organizations + where g.Id == id + select g.Directory.Identity).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (org is null) + return new(Responses.NotRows); + + return new(Responses.Success, org); + } + catch (Exception) + { + return new(Responses.ExistAccount); + } + + } + + + /// + /// Obtener el id del directorio (Grupo principal) de la organización. + /// + /// Id de la organización. + public async Task> ReadDirectory(int id) + { + try + { + var groupId = await (from g in context.Organizations + where g.Id == id + select g.DirectoryId).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (groupId <= 0) + return new(Responses.NotRows); + + // Success. + return new(Responses.Success, groupId); + } + catch (Exception) + { + return new(); + } + + } + + + /// + /// Obtener id de la identidad del directorio (Grupo principal) de la organización. + /// + /// Id de la organización. + public async Task> ReadDirectoryIdentity(int id) + { + try + { + var identityId = await (from g in context.Organizations + where g.Id == id + select g.Directory.IdentityId).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (identityId <= 0) + return new(Responses.NotRows); + + // Success. + return new(Responses.Success, identityId); + } + catch (Exception) + { + return new(); + } + + } + +} diff --git a/LIN.Cloud.Identity/Data/OtpService.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OtpRepository.cs similarity index 80% rename from LIN.Cloud.Identity/Data/OtpService.cs rename to LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OtpRepository.cs index 41c0d02..be272fd 100644 --- a/LIN.Cloud.Identity/Data/OtpService.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OtpRepository.cs @@ -1,11 +1,13 @@ -namespace LIN.Cloud.Identity.Data; +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; -public class OtpService(DataContext context) +public class OtpRepository(DataContext context) : IOtpRepository { + /// + /// Crear OTP. + /// public async Task Create(OtpDatabaseModel model) { - try { model.Account = context.AttachOrUpdate(model.Account); @@ -15,65 +17,65 @@ public async Task Create(OtpDatabaseModel model) context.SaveChanges(); return new(Responses.Success); - } catch (Exception) { } return new(Responses.Undefined); - } - public async Task ReadAndUpdate(int accountId, string code) + /// + /// Crear OTP. + /// + public async Task Create(MailOtpDatabaseModel model) { - try { + model.OtpDatabaseModel.Account = context.AttachOrUpdate(model.OtpDatabaseModel.Account); + model.MailModel = new() + { + Id = model.MailModel.Id + }; + model.MailModel = context.AttachOrUpdate(model.MailModel); - var update = await (from A in context.OTPs - where A.AccountId == accountId - && A.Code == code - && A.ExpireTime > DateTime.Now - && A.IsUsed == false - select A).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsUsed, true)); - - - if (update <= 0) - return new(Responses.NotRows); - + // Guardar OTP. + await context.MailOtp.AddAsync(model); + context.SaveChanges(); return new(Responses.Success); - } catch (Exception) { } return new(Responses.Undefined); - } - - public async Task Create(MailOtpDatabaseModel model) + /// + /// Leer y actualizar el estado del otp. + /// + /// Cuenta. + /// Código. + public async Task ReadAndUpdate(int accountId, string code) { try { - // A - model.OtpDatabaseModel.Account = context.AttachOrUpdate(model.OtpDatabaseModel.Account); - model.MailModel = new() - { - Id = model.MailModel.Id - }; - model.MailModel = context.AttachOrUpdate(model.MailModel); + var update = await (from A in context.OTPs + where A.AccountId == accountId + && A.Code == code + && A.ExpireTime > DateTime.Now + && A.IsUsed == false + select A).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsUsed, true)); + + + if (update <= 0) + return new(Responses.NotRows); - // Guardar OTP. - await context.MailOtp.AddAsync(model); - context.SaveChanges(); return new(Responses.Success); @@ -85,6 +87,4 @@ public async Task Create(MailOtpDatabaseModel model) } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs new file mode 100644 index 0000000..7fef39f --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs @@ -0,0 +1,146 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; + +internal class PolicyRepository(DataContext context) : IPolicyRepository +{ + + /// + /// Crear nueva política de acceso general. + /// + public async Task Create(PolicyModel model) + { + try + { + model.CreatedBy = context.AttachOrUpdate(model.CreatedBy)!; + model.Owner = context.AttachOrUpdate(model.Owner); + + // Guardar la cuenta. + await context.Policies.AddAsync(model); + context.SaveChanges(); + + return new() + { + Response = Responses.Success, + LastId = model.Id + }; + } + catch (Exception) + { + } + return new(); + } + + + /// + /// Agregar una política de acceso por tiempo. + /// + public async Task Add(TimeAccessPolicy policyModel) + { + try + { + policyModel.Policy = context.AttachOrUpdate(policyModel.Policy)!; + + context.TimeAccessPolicies.Add(policyModel); + await context.SaveChangesAsync(); + + return new(Responses.Success, policyModel.Id); + } + catch (Exception) + { + } + return new(); + } + + + /// + /// Agregar una política de acceso por IP. + /// + public async Task Add(IpAccessPolicy policyModel) + { + try + { + policyModel.Policy = context.AttachOrUpdate(policyModel.Policy)!; + + context.IpAccessPolicies.Add(policyModel); + await context.SaveChangesAsync(); + + return new(Responses.Success, policyModel.Id); + } + catch (Exception) + { + } + return new(); + } + + + /// + /// Agregar una política de acceso por tipo de identidad. + /// + public async Task Add(IdentityTypePolicy policyModel) + { + try + { + policyModel.Policy = context.AttachOrUpdate(policyModel.Policy)!; + + context.IdentityTypesPolicies.Add(policyModel); + await context.SaveChangesAsync(); + + return new(Responses.Success, policyModel.Id); + } + catch (Exception) + { + } + return new(); + } + + + /// + /// Eliminar una política de acceso. + /// + /// Id de la política. + public async Task Delete(int id) + { + try + { + // Eliminar las políticas de acceso. + await context.IpAccessPolicies.Where(x => x.PolicyId == id).ExecuteDeleteAsync(); + await context.IdentityTypesPolicies.Where(x => x.PolicyId == id).ExecuteDeleteAsync(); + await context.TimeAccessPolicies.Where(x => x.PolicyId == id).ExecuteDeleteAsync(); + await context.Policies.Where(x => x.Id == id).ExecuteDeleteAsync(); + + return new(Responses.Success); + } + catch (Exception) + { + } + return new(); + } + + + /// + /// Obtener una política de acceso por id. + /// + /// Id de la política. + public async Task> Read(int id, bool includeDetails) + { + try + { + var model = await (from p in context.Policies + where p.Id == id + select p) + .IncludeIf(includeDetails, t => t.Include(t => t.TimeAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.IpAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.IdentityTypePolicies)) + .FirstOrDefaultAsync(); + + if (model is null) + return new(Responses.NotRows); + + return new(Responses.Success, model); + } + catch (Exception) + { + } + return new(); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IAccountLogRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IAccountLogRepository.cs new file mode 100644 index 0000000..8fdc8b9 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IAccountLogRepository.cs @@ -0,0 +1,8 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IAccountLogRepository +{ + Task Create(AccountLog log); + Task> ReadAll(int accountId, DateTime? start, DateTime? end); + Task> Count(int id); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IAccountRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IAccountRepository.cs new file mode 100644 index 0000000..122c49c --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IAccountRepository.cs @@ -0,0 +1,20 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IAccountRepository +{ + Task> Create(AccountModel modelo, int organization); + + Task> Read(int id, QueryObjectFilter filters); + + Task> Read(string unique, QueryObjectFilter filters); + + Task> ReadByIdentity(int id, QueryObjectFilter filters); + + Task> Search(string pattern, QueryObjectFilter filters); + + Task> FindAll(List ids, QueryObjectFilter filters); + + Task> FindAllByIdentities(List ids, QueryObjectFilter filters); + + Task UpdatePassword(int accountId, string password); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IApplicationRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IApplicationRepository.cs new file mode 100644 index 0000000..e1ca1eb --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IApplicationRepository.cs @@ -0,0 +1,12 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IApplicationRepository +{ + + Task Create(ApplicationModel modelo); + + Task> Read(string key); + + Task> ExistApp(string key); + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IGroupMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IGroupMemberRepository.cs new file mode 100644 index 0000000..ad3df5a --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IGroupMemberRepository.cs @@ -0,0 +1,11 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IGroupMemberRepository +{ + Task Create(GroupMember modelo); + Task Create(IEnumerable modelos); + Task> ReadAll(int id); + Task> Search(string pattern, int group); + Task Delete(int identity, int group); + Task> OnMembers(int organization, int identity); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IGroupRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IGroupRepository.cs new file mode 100644 index 0000000..88b9841 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IGroupRepository.cs @@ -0,0 +1,41 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IGroupRepository +{ + + /// + /// Crear nuevo grupo. + /// + Task> Create(GroupModel modelo); + + /// + /// Obtener un grupo según el Id. + /// + /// Id del grupo. + Task> Read(int id); + + /// + /// Obtener un grupo según el Id de la identidad. + /// + /// Identidad. + Task> ReadByIdentity(int id); + + /// + /// Obtener los grupos asociados a una organización. + /// + /// Organización. + Task> ReadAll(int organization); + + /// + /// Obtener la organización propietaria de un grupo. + /// + /// Id del grupo. + Task> GetOwner(int id); + + /// + /// Obtener la organización propietaria de un grupo. + /// + /// Id de la identidad del grupo. + Task> GetOwnerByIdentity(int id); + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRepository.cs new file mode 100644 index 0000000..3b5ef16 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRepository.cs @@ -0,0 +1,8 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IIdentityRepository +{ + Task> Create(IdentityModel modelo); + Task> Read(int id, QueryIdentityFilter filters); + Task> Read(string unique, QueryIdentityFilter filters); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRolesRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRolesRepository.cs new file mode 100644 index 0000000..2f80ad2 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRolesRepository.cs @@ -0,0 +1,8 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IIdentityRolesRepository +{ + Task Create(IdentityRolesModel modelo); + Task> ReadAll(int identity, int organization); + Task Remove(int identity, Roles rol, int organization); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs new file mode 100644 index 0000000..2421360 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs @@ -0,0 +1,28 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IOrganizationMemberRepository +{ + + /// + /// Obtener organizaciones donde una identidad es integrante. + /// + /// Id de la identidad. + Task> ReadAll(int id); + + + /// + /// Validar si una identidad es integrante de una organización. + /// + /// Id de la identidad. + /// Id de la organización. + Task> IamIn(int id, int organization); + + + /// + /// Expulsar a una identidad de una organización. + /// + /// Lista de identidades. + /// Id de la organización. + Task Expulse(IEnumerable ids, int organization); + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationRepository.cs new file mode 100644 index 0000000..642e4db --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationRepository.cs @@ -0,0 +1,14 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IOrganizationRepository +{ + Task Create(OrganizationModel modelo); + + Task> Read(int id); + + Task> GetDomain(int id); + + Task> ReadDirectory(int id); + + Task> ReadDirectoryIdentity(int id); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IOtpRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IOtpRepository.cs new file mode 100644 index 0000000..58d2cca --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IOtpRepository.cs @@ -0,0 +1,8 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IOtpRepository +{ + Task Create(OtpDatabaseModel model); + Task Create(MailOtpDatabaseModel model); + Task ReadAndUpdate(int accountId, string code); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs new file mode 100644 index 0000000..651be5f --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs @@ -0,0 +1,11 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IPolicyRepository +{ + Task Create(PolicyModel policyModel); + Task Add(TimeAccessPolicy policyModel); + Task Add(IpAccessPolicy policyModel); + Task Add(IdentityTypePolicy policyModel); + Task> Read(int id, bool includeDetails); + Task Delete(int id); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Usings.cs b/LIN.Cloud.Identity.Persistence/Usings.cs new file mode 100644 index 0000000..7b0d521 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Usings.cs @@ -0,0 +1,9 @@ +global using LIN.Types.Cloud.Identity.Models; +global using LIN.Types.Responses; +global using LIN.Types.Cloud.Identity.Models.Policies; +global using LIN.Types.Cloud.Identity.Models.Identities; +global using LIN.Cloud.Identity.Persistence.Contexts; +global using Microsoft.EntityFrameworkCore; +global using LIN.Cloud.Identity.Persistence.Models; +global using LIN.Types.Cloud.Identity.Enumerations; +global using LIN.Cloud.Identity.Persistence.Extensions; \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs b/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs new file mode 100644 index 0000000..78e5753 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs @@ -0,0 +1,6 @@ +namespace LIN.Cloud.Identity.Services.Interfaces; + +public interface IAuthenticationService +{ + public Task Authenticate(); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Interfaces/IPolicyEngine.cs b/LIN.Cloud.Identity.Services/Interfaces/IPolicyEngine.cs new file mode 100644 index 0000000..f4d2c2c --- /dev/null +++ b/LIN.Cloud.Identity.Services/Interfaces/IPolicyEngine.cs @@ -0,0 +1,7 @@ +namespace LIN.Cloud.Identity.Services.Interfaces; + +public interface IPolicyEngine +{ + public Task IsAuthorized(int identity, int organization); + public Task IsAuthorizedForService(int identity, int serviceId); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj new file mode 100644 index 0000000..1a7614e --- /dev/null +++ b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/LIN.Cloud.Identity.Services/Services/ServiceAuthenticationService.cs b/LIN.Cloud.Identity.Services/Services/ServiceAuthenticationService.cs new file mode 100644 index 0000000..e892523 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/ServiceAuthenticationService.cs @@ -0,0 +1,9 @@ +namespace LIN.Cloud.Identity.Services.Services; + +internal class ServiceAuthenticationService : IAuthenticationService +{ + public Task Authenticate() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/UserAuthenticationService.cs b/LIN.Cloud.Identity.Services/Services/UserAuthenticationService.cs new file mode 100644 index 0000000..031a701 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/UserAuthenticationService.cs @@ -0,0 +1,9 @@ +namespace LIN.Cloud.Identity.Services.Services; + +internal class UserAuthenticationService : IAuthenticationService +{ + public Task Authenticate() + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/usings.cs b/LIN.Cloud.Identity.Services/usings.cs new file mode 100644 index 0000000..ed6d181 --- /dev/null +++ b/LIN.Cloud.Identity.Services/usings.cs @@ -0,0 +1,2 @@ +global using LIN.Types.Responses; +global using LIN.Cloud.Identity.Services.Interfaces; \ No newline at end of file diff --git a/LIN.Cloud.Identity.sln b/LIN.Cloud.Identity.sln index 53232ae..f77b4df 100644 --- a/LIN.Cloud.Identity.sln +++ b/LIN.Cloud.Identity.sln @@ -7,7 +7,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity", "LIN.C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Cloud.Identity.Persistence", "LIN.Cloud.Identity.Persistence\LIN.Cloud.Identity.Persistence.csproj", "{6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIN.Identity.Tests", "LIN.Identity.Tests\LIN.Identity.Tests.csproj", "{157863A4-209B-42A0-AE80-F6C403692480}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LIN.Cloud.Identity.Services", "LIN.Cloud.Identity.Services\LIN.Cloud.Identity.Services.csproj", "{DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -63,26 +63,26 @@ Global {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release-dev|Any CPU.Build.0 = Release-dev|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release-dev|x86.ActiveCfg = Release-dev|Any CPU {6E05CD1C-E4F0-4BCB-9BDE-CEB9278C6EB4}.Release-dev|x86.Build.0 = Release-dev|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Debug|Any CPU.Build.0 = Debug|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Debug|x86.ActiveCfg = Debug|x86 - {157863A4-209B-42A0-AE80-F6C403692480}.Debug|x86.Build.0 = Debug|x86 - {157863A4-209B-42A0-AE80-F6C403692480}.Debug-dev|Any CPU.ActiveCfg = Debug-dev|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Debug-dev|Any CPU.Build.0 = Debug-dev|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Debug-dev|x86.ActiveCfg = Debug-dev|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Debug-dev|x86.Build.0 = Debug-dev|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Local|Any CPU.ActiveCfg = Local|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Local|Any CPU.Build.0 = Local|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Local|x86.ActiveCfg = Local|x86 - {157863A4-209B-42A0-AE80-F6C403692480}.Local|x86.Build.0 = Local|x86 - {157863A4-209B-42A0-AE80-F6C403692480}.Release|Any CPU.ActiveCfg = Release|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Release|Any CPU.Build.0 = Release|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Release|x86.ActiveCfg = Release|x86 - {157863A4-209B-42A0-AE80-F6C403692480}.Release|x86.Build.0 = Release|x86 - {157863A4-209B-42A0-AE80-F6C403692480}.Release-dev|Any CPU.ActiveCfg = Release-dev|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Release-dev|Any CPU.Build.0 = Release-dev|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Release-dev|x86.ActiveCfg = Release-dev|Any CPU - {157863A4-209B-42A0-AE80-F6C403692480}.Release-dev|x86.Build.0 = Release-dev|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Debug|x86.Build.0 = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Debug-dev|Any CPU.ActiveCfg = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Debug-dev|Any CPU.Build.0 = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Debug-dev|x86.ActiveCfg = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Debug-dev|x86.Build.0 = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Local|Any CPU.ActiveCfg = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Local|Any CPU.Build.0 = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Local|x86.ActiveCfg = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Local|x86.Build.0 = Debug|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Release|Any CPU.Build.0 = Release|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Release|x86.ActiveCfg = Release|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Release|x86.Build.0 = Release|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Release-dev|Any CPU.ActiveCfg = Release|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Release-dev|Any CPU.Build.0 = Release|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Release-dev|x86.ActiveCfg = Release|Any CPU + {DB8A6B1A-0D70-4FF3-9494-10FFF5649F82}.Release-dev|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index c78f1c5..2f320ea 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -1,7 +1,9 @@ +using LIN.Cloud.Identity.Persistence.Repositories; + namespace LIN.Cloud.Identity.Areas.Accounts; [Route("[controller]")] -public class AccountController(Data.Accounts accountData, Data.Applications applications) : AuthenticationBaseController +public class AccountController(IAccountRepository accountData, IApplicationRepository applications) : AuthenticationBaseController { /// @@ -36,7 +38,7 @@ public async Task Create([FromBody] AccountModel? modelo, [F modelo = Services.Formats.Account.Process(modelo); // Creación del usuario. - var response = await accountData.Create(modelo); + var response = await accountData.Create(modelo, 0); // Evaluación. if (response.Response != Responses.Success) diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs index 4ab5814..0257852 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountLogs.cs @@ -1,8 +1,10 @@ +using LIN.Cloud.Identity.Persistence.Repositories; + namespace LIN.Cloud.Identity.Areas.Accounts; [IdentityToken] [Route("account/logs")] -public class AccountLogsController(Data.AccountLogs accountData) : AuthenticationBaseController +public class AccountLogsController(IAccountLogRepository accountData) : AuthenticationBaseController { /// diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs index 0e78dce..ee485ec 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs @@ -2,33 +2,8 @@ [IdentityToken] [Route("applications/restrictions")] -public class ApplicationRestrictionsController(Data.ApplicationRestrictions applicationRestrictions) : AuthenticationBaseController +public class ApplicationRestrictionsController() : AuthenticationBaseController { - /// - /// Crear restricción. - /// - /// Modelo. - [HttpPut] - public async Task UpdateOrCreate([FromBody] ApplicationRestrictionModel app) - { - - // Si el modelo es nulo. - if (app is null) - return new(Responses.InvalidParam) - { - Errors = [new() { Tittle = "Modelo invalido", Description = "El modelo json es invalido." }] - }; - - // Modelo. - app.Application = new() - { - Id = app.ApplicationId - }; - - // Crear restricción. - var create = await applicationRestrictions.Create(app); - return create; - } - + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs index 20f6858..355b373 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs @@ -1,8 +1,10 @@ -namespace LIN.Cloud.Identity.Areas.Applications; +using LIN.Cloud.Identity.Persistence.Repositories; + +namespace LIN.Cloud.Identity.Areas.Applications; [IdentityToken] [Route("applications")] -public class ApplicationsController(Data.Applications application) : AuthenticationBaseController +public class ApplicationsController(IApplicationRepository application) : AuthenticationBaseController { /// @@ -42,8 +44,8 @@ public async Task Create([FromBody] ApplicationModel app) // Formatear app. app.Key = Guid.NewGuid(); - app.Restriction = new(); - app.Identity.Type = IdentityType.Application; + app.Policies = new(); + app.Identity.Type = IdentityType.Service; app.Identity.Roles = []; app.Owner = new() diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index e3ad16c..f6e82fa 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,9 +1,10 @@ +using LIN.Cloud.Identity.Persistence.Repositories; using LIN.Cloud.Identity.Services.Auth.Interfaces; namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class AuthenticationController(IAuthentication authentication, Data.Accounts accountData, Data.Policies policyData) : AuthenticationBaseController +public class AuthenticationController(IAuthentication authentication, IAccountRepository accountData, IPolicyRepository policyData) : AuthenticationBaseController { /// @@ -120,11 +121,11 @@ public async Task> LoginWithToken() /// Contraseña. /// Id de la política. [HttpGet("validate/policy")] - public async Task ValidatePolicy([FromQuery] string user, [FromQuery] string password, [FromHeader] string policy) + public async Task ValidatePolicy([FromQuery] string user, [FromQuery] string password, [FromHeader] int policy) { // Validación de parámetros. - if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(policy)) + if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(password)) return new(Responses.InvalidParam) { Message = "Uno o varios parámetros son invalido." @@ -171,16 +172,20 @@ public async Task ValidatePolicy([FromQuery] string user, [Fro }; } - // Validar política. - var isAllow = await policyData.HasFor(authentication.GetData().IdentityId, policy); + //// Validar política. + //var isAllow = await policyData.HasFor(authentication.GetData().IdentityId, policy); - // Respuesta. - var http = new ResponseBase + //// Respuesta. + //var http = new ResponseBase + //{ + // Response = isAllow.Response + //}; + + //return http; + return new() { - Response = isAllow.Response + Response = Responses.Success }; - - return http; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index bd37dff..4f5ba9e 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -1,10 +1,11 @@ +using LIN.Cloud.Identity.Persistence.Repositories; using LIN.Cloud.Identity.Services.Realtime; namespace LIN.Cloud.Identity.Areas.Authentication; [IdentityToken] [Route("[controller]")] -public class IntentsController(Data.PassKeys passkeyData) : AuthenticationBaseController +public class IntentsController(IAccountLogRepository passkeyData) : AuthenticationBaseController { /// diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index e5ca1ae..7ebb395 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -1,159 +1,161 @@ -namespace LIN.Cloud.Identity.Areas.Authentication; +using LIN.Cloud.Identity.Persistence.Repositories; + +namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class SecurityController(Data.Accounts accountsData, Data.OtpService otpService, EmailSender emailSender, Data.Mails mails) : AuthenticationBaseController +public class SecurityController(IAccountRepository accountsData,IOtpRepository otpService, EmailSender emailSender) : AuthenticationBaseController { - /// - /// Agregar un correo a una cuenta. - /// - /// Correo. - [HttpPost("mail")] - [IdentityToken] - public async Task AddMail([FromQuery] string email) - { - // Generar modelo del correo. - var model = new MailModel() - { - Mail = email, - AccountId = UserInformation.AccountId, - IsPrincipal = false, - IsVerified = false - }; - - // Respuesta. - var responseCreate = await mails.Create(model); - - // Si hubo un error. - switch (responseCreate.Response) - { - // Correcto. - case Responses.Success: - break; - - // Ya estaba registrado. - case Responses.ResourceExist: - return new(responseCreate.Response) - { - Message = $"Hubo un error al agregar el correo <{email}> a la cuenta con identidad: '{UserInformation.IdentityId}'", - Errors = [ - new() { - Tittle = "Mail duplicado", - Description = "El correo ya se encuentra registrado en el sistema." - } - ] - }; - default: - return new(responseCreate.Response) - { - Message = $"Hubo un error al agregar el correo <{email}> a la cuenta {model.Account.Identity.Unique}" - }; - } - - // Generar Otp. - var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); - - // Guardar OTP. - var otpCreateResponse = await otpService.Create(new MailOtpDatabaseModel - { - MailModel = responseCreate.Model, - OtpDatabaseModel = new() - { - Account = new() { Id = UserInformation.AccountId }, - Code = otpCode, - ExpireTime = DateTime.Now.AddMinutes(10), - IsUsed = false - } - }); - - // Enviar correo de verificación. - if (otpCreateResponse.Response != Responses.Success) - return new() - { - Message = "Hubo un error al guardar el código OTP." - }; - - // Enviar correo. - var success = await emailSender.Send(email, "Verificar", $"Verificar tu correo {otpCode}"); - - return new(success ? Responses.Success : Responses.UnavailableService); - - } - - - /// - /// Validar un correo. - /// - /// Correo a validar. - /// Código OTP. - [HttpPost("validate")] - public async Task Validate([FromQuery] string mail, [FromQuery] string code) - { - // Validar OTP. - var response = await mails.ValidateOtpForMail(mail, code); - return response; - } - - - /// - /// Si un usuario olvido la contraseña. - /// - /// Usuario que olvido. - [HttpPost("forget/password")] - public async Task ForgetPassword([FromQuery] string user) - { - - // Validar estado del usuario. - var account = await accountsData.Read(user, new() - { - FindOn = FindOn.StableAccounts, - IncludePhoto = false - }); - - if (account.Response != Responses.Success) - return new(Responses.NotExistAccount) - { - Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." - }; - - // Obtener mail principal. - var mail = await mails.ReadPrincipal(user); - - if (mail.Response != Responses.Success) - return new(Responses.NotRows) - { - Message = "Esta cuenta no tiene un correo principal establecido." - }; - - // Generar OTP. - var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); - - // Guardar OTP. - var modelo = new OtpDatabaseModel - { - Account = account.Model, - AccountId = account.Model.Id, - Code = otpCode, - ExpireTime = DateTime.Now.AddMinutes(10), - IsUsed = false - }; - - // Crear OTP. - var created = await otpService.Create(modelo); - - // Si hubo un error. - if (created.Response != Responses.Success) - return new(created.Response) - { - Message = "No se pudo crear el código de verificación." - }; - - // Enviar mail. - var success = await emailSender.Send(mail.Model.Mail, "Recuperación de contraseña", $"Su código de verificación es: {otpCode}"); - - return new(success ? Responses.Success : Responses.UnavailableService); - - } + ///// + ///// Agregar un correo a una cuenta. + ///// + ///// Correo. + //[HttpPost("mail")] + //[IdentityToken] + //public async Task AddMail([FromQuery] string email) + //{ + // // Generar modelo del correo. + // var model = new MailModel() + // { + // Mail = email, + // AccountId = UserInformation.AccountId, + // IsPrincipal = false, + // IsVerified = false + // }; + + // // Respuesta. + // var responseCreate = await mails.Create(model); + + // // Si hubo un error. + // switch (responseCreate.Response) + // { + // // Correcto. + // case Responses.Success: + // break; + + // // Ya estaba registrado. + // case Responses.ResourceExist: + // return new(responseCreate.Response) + // { + // Message = $"Hubo un error al agregar el correo <{email}> a la cuenta con identidad: '{UserInformation.IdentityId}'", + // Errors = [ + // new() { + // Tittle = "Mail duplicado", + // Description = "El correo ya se encuentra registrado en el sistema." + // } + // ] + // }; + // default: + // return new(responseCreate.Response) + // { + // Message = $"Hubo un error al agregar el correo <{email}> a la cuenta {model.Account.Identity.Unique}" + // }; + // } + + // // Generar Otp. + // var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); + + // // Guardar OTP. + // var otpCreateResponse = await otpService.Create(new MailOtpDatabaseModel + // { + // MailModel = responseCreate.Model, + // OtpDatabaseModel = new() + // { + // Account = new() { Id = UserInformation.AccountId }, + // Code = otpCode, + // ExpireTime = DateTime.Now.AddMinutes(10), + // IsUsed = false + // } + // }); + + // // Enviar correo de verificación. + // if (otpCreateResponse.Response != Responses.Success) + // return new() + // { + // Message = "Hubo un error al guardar el código OTP." + // }; + + // // Enviar correo. + // var success = await emailSender.Send(email, "Verificar", $"Verificar tu correo {otpCode}"); + + // return new(success ? Responses.Success : Responses.UnavailableService); + + //} + + + ///// + ///// Validar un correo. + ///// + ///// Correo a validar. + ///// Código OTP. + //[HttpPost("validate")] + //public async Task Validate([FromQuery] string mail, [FromQuery] string code) + //{ + // // Validar OTP. + // var response = await mails.ValidateOtpForMail(mail, code); + // return response; + //} + + + ///// + ///// Si un usuario olvido la contraseña. + ///// + ///// Usuario que olvido. + //[HttpPost("forget/password")] + //public async Task ForgetPassword([FromQuery] string user) + //{ + + // // Validar estado del usuario. + // var account = await accountsData.Read(user, new() + // { + // FindOn = FindOn.StableAccounts, + // IncludePhoto = false + // }); + + // if (account.Response != Responses.Success) + // return new(Responses.NotExistAccount) + // { + // Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." + // }; + + // // Obtener mail principal. + // var mail = await mails.ReadPrincipal(user); + + // if (mail.Response != Responses.Success) + // return new(Responses.NotRows) + // { + // Message = "Esta cuenta no tiene un correo principal establecido." + // }; + + // // Generar OTP. + // var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); + + // // Guardar OTP. + // var modelo = new OtpDatabaseModel + // { + // Account = account.Model, + // AccountId = account.Model.Id, + // Code = otpCode, + // ExpireTime = DateTime.Now.AddMinutes(10), + // IsUsed = false + // }; + + // // Crear OTP. + // var created = await otpService.Create(modelo); + + // // Si hubo un error. + // if (created.Response != Responses.Success) + // return new(created.Response) + // { + // Message = "No se pudo crear el código de verificación." + // }; + + // // Enviar mail. + // var success = await emailSender.Send(mail.Model.Mail, "Recuperación de contraseña", $"Su código de verificación es: {otpCode}"); + + // return new(success ? Responses.Success : Responses.UnavailableService); + + //} /// diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index 1a8217d..edefff5 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -1,8 +1,10 @@ -namespace LIN.Cloud.Identity.Areas.Directories; +using LIN.Cloud.Identity.Persistence.Repositories; + +namespace LIN.Cloud.Identity.Areas.Directories; [IdentityToken] [Route("[controller]")] -public class DirectoryController(Data.DirectoryMembers directoryMembersData, Data.Groups groupsData, IamRoles rolesIam) : AuthenticationBaseController +public class DirectoryController(IOrganizationMemberRepository directoryMembersData, IGroupRepository groupsData, IamRoles rolesIam) : AuthenticationBaseController { /// @@ -11,7 +13,7 @@ public class DirectoryController(Data.DirectoryMembers directoryMembersData, Dat /// Id de la organización. /// Retorna la lista de integrantes./returns> [HttpGet("read/all")] - public async Task> ReadAll([FromHeader] int organization) + public async Task> ReadAll([FromHeader] int organization) { // Validar organización. if (organization <= 0) @@ -22,7 +24,8 @@ public async Task> ReadAll([FromHeader] int org }; // Obtiene el usuario. - var response = await directoryMembersData.Read(UserInformation.IdentityId, organization); + var response = await directoryMembersData.ReadAll( + organization); // Si es erróneo. if (response.Response != Responses.Success) @@ -70,18 +73,22 @@ public async Task> ReadMembers([FromQuery] int Response = Responses.Unauthorized }; - // Obtiene el usuario. - var response = await directoryMembersData.ReadMembers(directory); - - // Si es erróneo - if (response.Response != Responses.Success) - return new() - { - Response = response.Response - }; - - // Retorna el resultado - return response; + //// Obtiene el usuario. + //var response = await directoryMembersData.ReadMembers(directory); + + //// Si es erróneo + //if (response.Response != Responses.Success) + // return new() + // { + // Response = response.Response + // }; + + //// Retorna el resultado + //return response; + return new() + { + Response = Responses.Success + }; } diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index 81c650f..cc76f4f 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -1,8 +1,10 @@ -namespace LIN.Cloud.Identity.Areas.Groups; +using LIN.Cloud.Identity.Persistence.Repositories; + +namespace LIN.Cloud.Identity.Areas.Groups; [IdentityToken] [Route("[controller]")] -public class GroupsController(Data.Groups groupData, IamRoles rolesIam) : AuthenticationBaseController +public class GroupsController(IGroupRepository groupData, IamRoles rolesIam) : AuthenticationBaseController { /// @@ -14,7 +16,7 @@ public async Task Create([FromBody] GroupModel group) { // Confirmar el rol. - var roles = await rolesIam.Validate(UserInformation.IdentityId, group.OwnerId ?? 0); + var roles = await rolesIam.Validate(UserInformation.IdentityId, group.Identity.OwnerId ?? 0); // Iam. bool iam = ValidateRoles.ValidateAlterMembers(roles); diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index d9a5874..f5ef4b4 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -1,8 +1,10 @@ -namespace LIN.Cloud.Identity.Areas.Groups; +using LIN.Cloud.Identity.Persistence.Repositories; + +namespace LIN.Cloud.Identity.Areas.Groups; [IdentityToken] [Route("Groups/members")] -public class GroupsMembersController(Data.Groups groupsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, IamRoles rolesIam) : AuthenticationBaseController +public class GroupsMembersController(IGroupRepository groupsData, IOrganizationMemberRepository directoryMembersData, IGroupMemberRepository groupMembers, IamRoles rolesIam) : AuthenticationBaseController { /// @@ -98,27 +100,30 @@ public async Task Create([FromHeader] int group, [FromBody] // Solo elementos distintos. ids = ids.Distinct().ToList(); - // Valida si el usuario pertenece a la organización. - var (successIds, failureIds) = await directoryMembersData.IamIn(ids, orgId.Model); + //// Valida si el usuario pertenece a la organización. + //var (successIds, failureIds) = await directoryMembersData.IamIn(ids, orgId.Model); - // Crear el usuario. - var response = await groupMembers.Create(successIds.Select(id => new GroupMember - { - Group = new() - { - Id = group, - }, - Identity = new() - { - Id = id - } - })); + //// Crear el usuario. + //var response = await groupMembers.Create(successIds.Select(id => new GroupMember + //{ + // Group = new() + // { + // Id = group, + // }, + // Identity = new() + // { + // Id = id + // } + //})); - response.Message = $"Se agregaron {successIds.Count()} integrantes y se omitieron {failureIds.Count} debido a que no pertenecen a esta organización."; + // response.Message = $"Se agregaron {successIds.Count()} integrantes y se omitieron {failureIds.Count} debido a que no pertenecen a esta organización."; // Retorna el resultado - return response; - + // return response; + return new() + { + Message = "No se implementó la función de agregar múltiples integrantes." + }; } @@ -259,7 +264,7 @@ public async Task> SearchOnGroups([FromHeader }; // Obtiene los miembros. - var members = await groupMembers.SearchGroups(pattern, group); + var members = await groupMembers.Search(pattern, group); // Error al obtener los integrantes. if (members.Response != Responses.Success) diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index 63d6276..c87537c 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -1,8 +1,10 @@ -namespace LIN.Cloud.Identity.Areas.Organizations; +using LIN.Cloud.Identity.Persistence.Repositories; + +namespace LIN.Cloud.Identity.Areas.Organizations; [IdentityToken] [Route("[controller]")] -public class IdentityController(Data.DirectoryMembers directoryMembersData, Data.IdentityRoles identityRolesData, IamRoles rolesIam) : AuthenticationBaseController +public class IdentityController(IOrganizationMemberRepository directoryMembersData, IIdentityRolesRepository identityRolesData, IamRoles rolesIam) : AuthenticationBaseController { /// diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 28d7c8f..60cc644 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -1,7 +1,9 @@ +using LIN.Cloud.Identity.Persistence.Repositories; + namespace LIN.Cloud.Identity.Areas.Organizations; [Route("[controller]")] -public class OrganizationsController(Data.Organizations organizationsData, Data.DirectoryMembers directoryMembersData) : AuthenticationBaseController +public class OrganizationsController(IOrganizationRepository organizationsData, IOrganizationMemberRepository directoryMembersData) : AuthenticationBaseController { /// @@ -113,7 +115,7 @@ public async Task> ReadAll() { // Obtiene la organización - var response = await organizationsData.ReadAll(UserInformation.IdentityId); + var response = await directoryMembersData.ReadAll(UserInformation.IdentityId); return response; diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index 144b78c..1fc19d8 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -1,10 +1,11 @@ +using LIN.Cloud.Identity.Persistence.Repositories; using LIN.Types.Cloud.Identity.Abstracts; namespace LIN.Cloud.Identity.Areas.Organizations; [IdentityToken] [Route("orgs/members")] -public class OrganizationMembersController(Data.Organizations organizationsData, Data.Accounts accountsData, Data.DirectoryMembers directoryMembersData, Data.GroupMembers groupMembers, IamRoles rolesIam) : AuthenticationBaseController +public class OrganizationMembersController(IOrganizationRepository organizationsData, IAccountRepository accountsData, IOrganizationMemberRepository directoryMembersData, IGroupMemberRepository groupMembers, IamRoles rolesIam) : AuthenticationBaseController { /// @@ -34,29 +35,32 @@ public async Task AddExternalMembers([FromQuery] int organiz // Solo elementos distintos. ids = ids.Distinct().ToList(); - // Valida si el usuario pertenece a la organización. - var (existentes, noUpdated) = await directoryMembersData.IamIn(ids, organization); - - var directoryId = await organizationsData.ReadDirectory(organization); - - // Crear el usuario. - var response = await groupMembers.Create(noUpdated.Select(id => new GroupMember + //// Valida si el usuario pertenece a la organización. + //var (existentes, noUpdated) = await directoryMembersData.IamIn(ids, organization); + + //var directoryId = await organizationsData.ReadDirectory(organization); + + //// Crear el usuario. + //var response = await groupMembers.Create(noUpdated.Select(id => new GroupMember + //{ + // Group = new() + // { + // Id = directoryId.Model, + // }, + // Identity = new() + // { + // Id = id + // }, + // Type = GroupMemberTypes.Guest + //})); + + //response.Message = $"Se agregaron {noUpdated.Count} integrantes como invitados y se omitieron {existentes.Count()} debido a que ya pertenecen a esta organización."; + + //// Retorna el resultado + //return response; + return new() { - Group = new() - { - Id = directoryId.Model, - }, - Identity = new() - { - Id = id - }, - Type = GroupMemberTypes.Guest - })); - - response.Message = $"Se agregaron {noUpdated.Count} integrantes como invitados y se omitieron {existentes.Count()} debido a que ya pertenecen a esta organización."; - - // Retorna el resultado - return response; + }; } @@ -161,19 +165,22 @@ public async Task>> ReadAll([FromH Response = Responses.Unauthorized }; - // Obtiene los miembros. - var members = await directoryMembersData.ReadMembersByOrg(organization); + //// Obtiene los miembros. + //var members = await directoryMembersData.ReadAll(organization); - // Error al obtener los integrantes. - if (members.Response != Responses.Success) - return new ReadAllResponse> - { - Message = "No se encontró la organización.", - Response = Responses.Unauthorized - }; + //// Error al obtener los integrantes. + //if (members.Response != Responses.Success) + // return new ReadAllResponse> + // { + // Message = "No se encontró la organización.", + // Response = Responses.Unauthorized + // }; - // Retorna el resultado - return members; + //// Retorna el resultado + //return members; + return new() + { + }; } @@ -205,14 +212,16 @@ public async Task Expulse([FromQuery] int organization, [FromB // Solo elementos distintos. ids = ids.Distinct(); - // Valida si el usuario pertenece a la organización. - var (existentes, _) = await directoryMembersData.IamIn(ids, organization); + //// Valida si el usuario pertenece a la organización. + //var (existentes, _) = await directoryMembersData.IamIn(ids, organization); - var response = await directoryMembersData.Expulse(existentes, organization); + //var response = await directoryMembersData.Expulse(existentes, organization); // Retorna el resultado - return response; - + //return response; + return new() + { + }; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs deleted file mode 100644 index 1d5a131..0000000 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesComplacentController.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace LIN.Cloud.Identity.Areas.Policies; - -[IdentityToken] -[Route("policies/complacent")] -public class PoliciesComplacentController(Data.Policies policiesData, IamRoles iam, IamPolicy iamPolicy) : AuthenticationBaseController -{ - - - [HttpGet("applicable")] - public async Task Applicants([FromHeader] int identity) - { - - if (identity != UserInformation.IdentityId) - { - var iamResult = await iam.IamIdentity(UserInformation.IdentityId, identity); - if (iamResult != Types.Enumerations.IamLevels.Privileged) - return new ResponseBase(Responses.Unauthorized) - { - Message = "No tienes permisos para ver los solicitantes de la política." - }; - } - - var response = await policiesData.ApplicablePolicies(identity); - return response; - } - - - /// - /// Agregar integrantes a una política. - /// - /// Id de la política. - /// Id de la identidad a agregar. - [HttpPost] - public async Task AddMember([FromQuery] string policy, [FromHeader] int identity) - { - - // Validar Iam. - var iamResult = await iamPolicy.Validate(UserInformation.IdentityId, policy); - - if (iamResult != Types.Enumerations.IamLevels.Privileged) - return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para agregar integrantes a la política." }; - - // Agregar integrante - var response = await policiesData.AddMember(identity, policy); - - return response; - } - - - /// - /// Eliminar integrantes a una política. - /// - /// Id de la política. - /// Id de la identidad a agregar. - [HttpDelete] - public async Task DeleteMember([FromQuery] string policy, [FromHeader] int identity) - { - - // Validar Iam. - var iamResult = await iamPolicy.Validate(UserInformation.IdentityId, policy); - - if (iamResult != Types.Enumerations.IamLevels.Privileged) - return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para eliminar integrantes de la política." }; - - // Agregar integrante - var response = await policiesData.RemoveMember(identity, policy); - - return response; - } - - - /// - /// Validar si tiene autorización. - /// - /// Id de la política. - [HttpGet] - public async Task IsAllow([FromQuery] string policy) - { - var response = await policiesData.HasFor(UserInformation.IdentityId, policy); - return response; - } - - - /// - /// Validar si tiene acceso a una política. - /// - /// Id de la política. - /// Id de la identidad. - [HttpGet("identity")] - public async Task IsAllow([FromQuery] string policy, [FromHeader] int identity) - { - var response = await policiesData.HasFor(identity, policy); - return response; - } - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index ec8319e..dd66dd5 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -1,8 +1,10 @@ -namespace LIN.Cloud.Identity.Areas.Policies; +using LIN.Cloud.Identity.Persistence.Repositories; + +namespace LIN.Cloud.Identity.Areas.Policies; [IdentityToken] [Route("[controller]")] -public class PoliciesController(Data.Policies policiesData, Data.Groups groups, IamRoles iam, Data.Organizations organizations, IamPolicy iamPolicy) : AuthenticationBaseController +public class PoliciesController(IPolicyRepository policiesData, IGroupRepository groups, IamRoles iam,IOrganizationRepository organizations, IamPolicy iamPolicy) : AuthenticationBaseController { /// @@ -13,110 +15,64 @@ public class PoliciesController(Data.Policies policiesData, Data.Groups groups, public async Task Create([FromBody] PolicyModel modelo, [FromHeader] int? organization, [FromHeader] bool assign) { - // Si ya tiene una identidad. - if (modelo.OwnerIdentityId > 0 && (organization is null || organization <= 0)) - { - // Obtener detalles. - var owner = await groups.GetOwnerByIdentity(modelo.OwnerIdentityId); - - if (owner.Response != Responses.Success) - return new(Responses.NotRows) { Message = $"No se encontró la organización del grupo con identidad {modelo.OwnerIdentityId}" }; - - // Validar roles. - var roles = await iam.Validate(UserInformation.IdentityId, owner.Model); - - bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); - - if (!hasPermission) - return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{owner.Model}." }; - - } - else if (organization is not null && organization > 0) - { - // Validar roles. - var roles = await iam.Validate(UserInformation.IdentityId, organization.Value); - - bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); - - if (!hasPermission) - return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{organization}." }; - - // - var directoryIdentity = await organizations.ReadDirectoryIdentity(organization.Value); - modelo.OwnerIdentityId = directoryIdentity.Model; - } - else - { - // Establecer propietario al usuario que realiza la solicitud. - modelo.OwnerIdentityId = UserInformation.IdentityId; - } - - // Formatear. - modelo.OwnerIdentity = new() - { - Id = modelo.OwnerIdentityId - }; - - modelo.ApplyFor = []; - if (assign) - modelo.ApplyFor = [new() { - Identity = new(){ - Id = modelo.OwnerIdentityId - } - }]; + //// Si ya tiene una identidad. + //if (modelo.OwnerIdentityId > 0 && (organization is null || organization <= 0)) + //{ + // // Obtener detalles. + // var owner = await groups.GetOwnerByIdentity(modelo.OwnerIdentityId); + + // if (owner.Response != Responses.Success) + // return new(Responses.NotRows) { Message = $"No se encontró la organización del grupo con identidad {modelo.OwnerIdentityId}" }; + + // // Validar roles. + // var roles = await iam.Validate(UserInformation.IdentityId, owner.Model); + + // bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); + + // if (!hasPermission) + // return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{owner.Model}." }; + + //} + //else if (organization is not null && organization > 0) + //{ + // // Validar roles. + // var roles = await iam.Validate(UserInformation.IdentityId, organization.Value); + + // bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); + + // if (!hasPermission) + // return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{organization}." }; + + // // + // var directoryIdentity = await organizations.ReadDirectoryIdentity(organization.Value); + // modelo.OwnerIdentityId = directoryIdentity.Model; + //} + //else + //{ + // // Establecer propietario al usuario que realiza la solicitud. + // modelo.OwnerIdentityId = UserInformation.IdentityId; + //} + + //// Formatear. + //modelo.OwnerIdentity = new() + //{ + // Id = modelo.OwnerIdentityId + //}; + + //modelo.ApplyFor = []; + //if (assign) + // modelo.ApplyFor = [new() { + // Identity = new(){ + // Id = modelo.OwnerIdentityId + // } + // }]; var response = await policiesData.Create(modelo); return response; } - /// - /// Obtener políticas asociadas a una cuenta. - /// - [HttpGet("all")] - public async Task> All() - { - var response = await policiesData.ReadAllOwn(UserInformation.IdentityId); - return response; - } - - - /// - /// Obtener políticas asociadas a una organización. - /// - [HttpGet("organization/all")] - public async Task> OrganizationAll([FromHeader] int organization) - { - var response = await policiesData.ReadAll(organization); - return response; - } - /// - /// Eliminar política. - /// - /// Id de la política. - [HttpDelete] - public async Task Delete([FromQuery] string policy) - { - - // Validar Iam. - var iamResult = await iamPolicy.Validate(UserInformation.IdentityId, policy); - - if (iamResult != Types.Enumerations.IamLevels.Privileged) - return new ResponseBase(Responses.Unauthorized) { Message = "No tienes permisos para eliminar la política." }; - - var response = await policiesData.Remove(policy); - return response; - } - - - - [HttpGet] - public async Task> Read([FromQuery] string policy) - { - var response = await policiesData.Read(Guid.Parse(policy)); - return response; - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs b/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs deleted file mode 100644 index da3ef1d..0000000 --- a/LIN.Cloud.Identity/Areas/Policies/PolicyRequirements.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace LIN.Cloud.Identity.Areas.Policies; - -[IdentityToken] -[Route("[controller]")] -public class PolicyRequirementsController(Data.PoliciesRequirement policiesData, IamPolicy iamPolicy) : AuthenticationBaseController -{ - - /// - /// Crear nueva política. - /// - /// Modelo de la identidad. - [HttpPost] - public async Task Create([FromBody] PolicyRequirementModel modelo) - { - - // Validar roles. - var roles = await iamPolicy.Validate(UserInformation.IdentityId, modelo.PolicyId.ToString()); - - if (roles != IamLevels.Privileged) - return new(Responses.Unauthorized) { Message = $"No tienes permisos" }; - - // Forma - var response = await policiesData.Create(modelo); - return response; - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/AllowApps.cs b/LIN.Cloud.Identity/Data/AllowApps.cs deleted file mode 100644 index 9176777..0000000 --- a/LIN.Cloud.Identity/Data/AllowApps.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace LIN.Cloud.Identity.Data; - -public class AllowApps(DataContext context, IIdentityService identityService) -{ - - /// - /// Crear acceso a app. - /// - /// Modelo. - public async Task> Create(AllowApp modelo) - { - - // Transacción. - using var transaction = context.Database.BeginTransaction(); - - try - { - // Attach. - context.Attach(modelo.Application); - context.Attach(modelo.Identity); - - // Guardar la cuenta. - await context.AllowApps.AddAsync(modelo); - context.SaveChanges(); - - // Confirmar los cambios. - transaction.Commit(); - - return new() - { - Response = Responses.Success, - Model = modelo - }; - - } - catch (Exception) - { - transaction.Rollback(); - return new() - { - Response = Responses.ResourceExist - }; - } - - } - - - /// - /// btener las apps a las que una identidad tiene acceso o no. - /// - /// Id de la identidad. - public async Task> ReadAll(int id) - { - - // Ejecución - try - { - - var identities = await identityService.GetIdentities(id); - - var query = await (from allow in context.AllowApps - where identities.Contains(allow.IdentityId) - select new AllowApp - { - ApplicationRestrictionId = allow.ApplicationRestrictionId, - IdentityId = allow.IdentityId, - IsAllow = allow.IsAllow, - Application = new() - { - Id = allow.Application.Id - } - }).ToListAsync(); - - return new(Responses.Success, query); - - } - catch (Exception) - { - } - return new(); - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs b/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs deleted file mode 100644 index 1529755..0000000 --- a/LIN.Cloud.Identity/Data/ApplicationRestrictions.cs +++ /dev/null @@ -1,79 +0,0 @@ -namespace LIN.Cloud.Identity.Data; - -public class ApplicationRestrictions(DataContext context) -{ - - /// - /// Crear nueva restricción de aplicación. - /// - /// Modelo. - public async Task Create(ApplicationRestrictionModel modelo) - { - // Pre. - modelo.Id = 0; - - try - { - // Modelo ya existe. - modelo.Application = context.AttachOrUpdate(modelo.Application); - - // Guardar la identidad. - await context.ApplicationRestrictions.AddAsync(modelo); - context.SaveChanges(); - return new(Responses.Success, modelo.Id); - } - catch (Exception) - { - return new(Responses.Undefined); - } - } - - - /// - /// Obtener las restricciones de aplicación. - /// - /// Id de la aplicación. - public async Task> Read(string id) - { - try - { - - var restriction = await (from ar in context.ApplicationRestrictions - where ar.Application.Key == Guid.Parse(id) - select ar).FirstOrDefaultAsync(); - - // Success. - return new(Responses.Success, restriction!); - - } - catch (Exception) - { - return new(Responses.Undefined); - } - } - - - /// - /// Obtener las restricciones de tiempo. - /// - /// Id de la aplicación. - public async Task> ReadTimes(int id) - { - try - { - - var restrictions = await (from tr in context.TimeRestriction - where tr.ApplicationRestrictionModel.ApplicationId == id - select tr).ToListAsync(); - - // Success. - return new(Responses.Success, restrictions); - - } - catch (Exception) - { - return new(Responses.Undefined); - } - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/DirectoryMembers.cs b/LIN.Cloud.Identity/Data/DirectoryMembers.cs deleted file mode 100644 index 00669b0..0000000 --- a/LIN.Cloud.Identity/Data/DirectoryMembers.cs +++ /dev/null @@ -1,351 +0,0 @@ -using LIN.Types.Cloud.Identity.Abstracts; - -namespace LIN.Cloud.Identity.Data; - -public class DirectoryMembers(DataContext context) -{ - - /// - /// Obtener los directorios (Grupos de organización) donde una identidad pertenece. - /// - /// Identidad - /// Organización de contexto. - public async Task> Read(int id, int organization) - { - try - { - var members = await (from gm in context.GroupMembers - where gm.IdentityId == id - join o in context.Organizations - on gm.GroupId equals o.DirectoryId - where o.Id == organization - select new GroupMember - { - Type = gm.Type, - Group = gm.Group, - GroupId = gm.GroupId, - IdentityId = gm.IdentityId, - }).ToListAsync(); - - - // Si la cuenta no existe. - if (members == null) - return new(Responses.NotRows); - - // Success. - return new(Responses.Success, members); - } - catch (Exception) - { - return new(Responses.Undefined); - } - - } - - - /// - /// Valida si una identidad es miembro de una organización. - /// - /// Identidad - /// Id de la organización - public async Task> IamIn(int id, int organization) - { - - try - { - - // Consulta. - var query = await (from org in context.Organizations - where org.Id == organization - join gm in context.GroupMembers - on org.DirectoryId equals gm.GroupId - where gm.IdentityId == id - select new - { - gm.Type - }).FirstOrDefaultAsync(); - - - // Si la cuenta no existe. - if (query == null) - { - - var x = await (from A in context.Organizations - where A.Directory.IdentityId == id - && A.Id == organization - select A).AnyAsync(); - - - if (!x) - return new() - { - Response = Responses.NotRows - }; - - } - - - // Success. - return new() - { - Response = Responses.Success, - Model = query?.Type ?? GroupMemberTypes.Group - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.NotRows - }; - } - - } - - - - /// - /// Valida si una lista de identidades son miembro de una organización. - /// - /// Identidades - /// Id de la organización - /// Contexto - public async Task<(IEnumerable success, List failure)> IamIn(IEnumerable ids, int organization) - { - - try - { - - // Consulta. - var query = await (from org in context.Organizations - where org.Id == organization - join gm in context.GroupMembers - on org.DirectoryId equals gm.GroupId - where ids.Contains(gm.IdentityId) - select gm.IdentityId).ToListAsync(); - - // Lista. - List success = [.. query]; - List failure = [.. ids.Except(success)]; - - return (success, failure); - } - catch (Exception) - { - } - return ([], []); - } - - - - /// - /// Valida si una identidad es miembro de una organización. - /// - /// Identidad - /// Id del directorio - /// Contexto - public async Task> IamInByDir(int id, int directory) - { - - try - { - - // Consulta. - var query = await (from org in context.Organizations - where org.DirectoryId == directory - join gm in context.GroupMembers - on org.DirectoryId equals gm.GroupId - where gm.IdentityId == id - select new - { - gm.Type - }).FirstOrDefaultAsync(); - - - // Si la cuenta no existe. - if (query == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = query.Type - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.NotRows - }; - } - - } - - - - /// - /// - /// - /// Directorio - /// Contexto - public async Task> ReadMembers(int id) - { - - try - { - - var members = await (from org in context.Organizations - where org.DirectoryId == id - join gm in context.GroupMembers - on org.DirectoryId equals gm.GroupId - select new GroupMember - { - GroupId = gm.GroupId, - Identity = gm.Identity, - Type = gm.Type, - IdentityId = gm.IdentityId - }).ToListAsync(); - - // Si la cuenta no existe. - if (members == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Models = members - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// - /// - /// Directorio - /// Contexto - public async Task>> ReadMembersByOrg(int id) - { - - try - { - - var members = await (from org in context.Organizations - where org.Id == id - join gm in context.GroupMembers - on org.DirectoryId equals gm.GroupId - join a in context.Accounts - on gm.IdentityId equals a.IdentityId - select new SessionModel - { - Account = new() - { - Id = a.Id, - Name = a.Name, - Visibility = a.Visibility, - IdentityService = a.IdentityService, - Identity = new() - { - Id = a.Identity.Id, - Unique = a.Identity.Unique - } - }, - Profile = gm - }).ToListAsync(); - - - // Si la cuenta no existe. - if (members == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Models = members - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - /// - /// Expulsar identidades de la organización. - /// - /// Lista de identidades. - /// Id de la organización. - /// Respuesta del proceso. - public async Task Expulse(IEnumerable ids, int organization) - { - - try - { - - // Desactivar identidades (Solo creadas dentro de la propia organización). - var baseQuery = (from member in context.GroupMembers - where ids.Contains(member.IdentityId) - where member.Group.OwnerId == organization - select member); - - // Desactivar identidades (Solo creadas dentro de la propia organización). - await baseQuery.Where(m => m.Type != GroupMemberTypes.Guest).Select(m => m.Identity).ExecuteUpdateAsync(t => t.SetProperty(t => t.Status, IdentityStatus.Disable)); - - // Eliminar accesos (Tanto propios de la organización como los externos). - await baseQuery.ExecuteDeleteAsync(); - - // Eliminar roles asociados. - await (from rol in context.IdentityRoles - where ids.Contains(rol.IdentityId) - && rol.OrganizationId == organization - select rol).ExecuteDeleteAsync(); - - // Success. - return new() - { - Response = Responses.Success - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.Undefined - }; - } - - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Identities.cs b/LIN.Cloud.Identity/Data/Identities.cs deleted file mode 100644 index 45ac61c..0000000 --- a/LIN.Cloud.Identity/Data/Identities.cs +++ /dev/null @@ -1,130 +0,0 @@ -namespace LIN.Cloud.Identity.Data; - -public class Identities(DataContext context) -{ - - - /// - /// Crear nueva identidad. - /// - /// Modelo. - /// Contexto de conexión. - public async Task> Create(IdentityModel modelo) - { - // Pre. - modelo.Id = 0; - - try - { - - foreach (var e in modelo.Roles) - e.Identity = modelo; - - // Guardar la identidad. - await context.Identities.AddAsync(modelo); - context.SaveChanges(); - - return new() - { - Response = Responses.Success, - Model = modelo - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Obtener una identidad según el Id. - /// - /// Id de la identidad. - /// Filtros de búsqueda. - /// Contexto de base de datos. - public async Task> Read(int id, QueryIdentityFilter filters) - { - - try - { - - // Consulta de las cuentas. - var account = await Builders.Identities.GetIds(id, filters, context).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Obtener una identidad según el Unique. - /// - /// Unique. - /// Filtros de búsqueda. - /// Contexto de base de datos. - public async Task> Read(string unique, QueryIdentityFilter filters) - { - - try - { - - // Consulta de las cuentas. - var account = await Builders.Identities.GetIds(unique, filters, context).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (account == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = account - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Mails.cs b/LIN.Cloud.Identity/Data/Mails.cs deleted file mode 100644 index 6ad19c1..0000000 --- a/LIN.Cloud.Identity/Data/Mails.cs +++ /dev/null @@ -1,121 +0,0 @@ -namespace LIN.Cloud.Identity.Data; - -public class Mails(DataContext context) -{ - - /// - /// Crear modelo de resultado de mail. - /// - /// Modelo. - public async Task> Create(MailModel modelo) - { - try - { - - modelo.Id = 0; - modelo.Account = new() - { - Id = modelo.AccountId, - }; - - // Attach. - context.Attach(modelo.Account); - - // Guardar la cuenta. - await context.Mails.AddAsync(modelo); - context.SaveChanges(); - - return new() - { - Response = Responses.Success, - Model = modelo - }; - - } - catch (Exception) - { - return new(Responses.ResourceExist); - } - } - - - /// - /// Obtener el correo principal. - /// - /// Usuario unico. - public async Task> ReadPrincipal(string unique) - { - try - { - - var x = await (from mail in context.Mails - join account in context.Accounts - on mail.Account.IdentityId equals account.IdentityId - where account.Identity.Unique == unique - where mail.IsPrincipal - && mail.IsVerified - select mail).FirstOrDefaultAsync(); - - if (x is null) - return new() - { - Response = Responses.NotRows - }; - - - return new() - { - Response = Responses.Success, - Model = x - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.Undefined - }; - } - - } - - - /// - /// Validar otp para un correo. - /// - /// Correo. - /// - public async Task ValidateOtpForMail(string email, string code) - { - try - { - - // Obtener modelo. - var otpModel = (from mail in context.Mails - where mail.Mail == email - join otp in context.MailOtp - on mail.Id equals otp.MailId - where otp.OtpDatabaseModel.Code == code - && otp.OtpDatabaseModel.IsUsed == false - && otp.OtpDatabaseModel.ExpireTime > DateTime.Now - select otp); - - // Actualizar. - await otpModel.Select(t => t.MailModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsVerified, true)); - int countUpdate = await otpModel.Select(t => t.OtpDatabaseModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsUsed, true)); - - // Si no se actualizaron. - if (countUpdate <= 0) - return new(Responses.NotRows); - - return new(Responses.Success); - } - catch (Exception) - { - return new(Responses.Undefined); - } - - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Organizations.cs b/LIN.Cloud.Identity/Data/Organizations.cs deleted file mode 100644 index daa108e..0000000 --- a/LIN.Cloud.Identity/Data/Organizations.cs +++ /dev/null @@ -1,321 +0,0 @@ -using IdentityService = LIN.Types.Cloud.Identity.Enumerations.IdentityService; - -namespace LIN.Cloud.Identity.Data; - -public class Organizations(DataContext context, IamRoles iam) -{ - - - /// - /// Crear nueva organización. [Transacción] - /// - /// Modelo. - /// Contexto de conexión. - public async Task Create(OrganizationModel modelo) - { - - using var transaction = context.Database.BeginTransaction(); - - try - { - - // Metadata. - modelo.Directory.Name = "Directorio General"; - modelo.Directory.Description = "Directorio General de la organización"; - modelo.Directory.Owner = null; - modelo.Directory.OwnerId = null; - modelo.Directory.Identity.Type = IdentityType.Group; - - await context.Organizations.AddAsync(modelo); - - context.SaveChanges(); - - // Cuenta de usuario - var account = new AccountModel() - { - Id = 0, - Visibility = Visibility.Hidden, - Name = "Admin", - Password = $"pwd@{DateTime.Now.Year}", - IdentityService = IdentityService.LIN, - Identity = new IdentityModel() - { - Status = IdentityStatus.Enable, - CreationTime = DateTime.Now, - EffectiveTime = DateTime.Now, - ExpirationTime = DateTime.Now.AddYears(10), - Unique = $"admin@{modelo.Directory.Identity.Unique}" - } - }; - - - var resultAccount = new AccountModel() - { - Password = account.Password, - Identity = new() - { - Unique = account.Identity.Unique, - } - }; - - account = Services.Formats.Account.Process(account); - - await context.Accounts.AddAsync(account); - - context.SaveChanges(); - - // IamRoles. - var rol = new IdentityRolesModel - { - Identity = account.Identity, - Organization = modelo, - Rol = Roles.Administrator - }; - - - await context.IdentityRoles.AddAsync(rol); - - context.SaveChanges(); - - modelo.Directory.Owner = modelo; - modelo.Directory.Members.Add(new() - { - Group = modelo.Directory, - Identity = account.Identity, - Type = GroupMemberTypes.User - }); - - context.SaveChanges(); - - - transaction.Commit(); - - - var responseFinal = new CreateResponse() - { - Response = Responses.Success, - LastId = modelo.Id - }; - - - return responseFinal; - - } - catch (Exception) - { - transaction.Rollback(); - return new() - { - Response = Responses.Undefined - }; - } - - } - - - - /// - /// Obtener una organización según el Id. - /// - /// Id. - /// Contexto de base de datos. - public async Task> Read(int id) - { - - try - { - - var org = await (from g in context.Organizations - where g.Id == id - select g).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (org == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = org - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - /// - /// Obtener las organizaciones donde una identidad pertenece. - /// - /// Identidad - public async Task> ReadAll(int id) - { - - try - { - - // Consulta. - var query = await (from org in context.Organizations - join gm in context.GroupMembers - on org.DirectoryId equals gm.GroupId - where gm.IdentityId == id - select org).ToListAsync(); - - - // Si la cuenta no existe. - if (query == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Models = query - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.NotRows - }; - } - - } - - - - /// - /// Obtener el dominio de una organización. - /// - /// Id de la organización. - /// Contexto de base de datos. - public async Task> GetDomain(int id) - { - - try - { - - var org = await (from g in context.Organizations - where g.Id == id - select g.Directory.Identity).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (org == null) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = org - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - public async Task> ReadDirectory(int id) - { - - try - { - - var org = await (from g in context.Organizations - where g.Id == id - select g.DirectoryId).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (org <= 0) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = org - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - public async Task> ReadDirectoryIdentity(int id) - { - - try - { - - var org = await (from g in context.Organizations - where g.Id == id - select g.Directory.IdentityId).FirstOrDefaultAsync(); - - // Si la cuenta no existe. - if (org <= 0) - return new() - { - Response = Responses.NotRows - }; - - // Success. - return new() - { - Response = Responses.Success, - Model = org - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ExistAccount - }; - } - - } - - - - - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/PassKeys.cs b/LIN.Cloud.Identity/Data/PassKeys.cs deleted file mode 100644 index f466892..0000000 --- a/LIN.Cloud.Identity/Data/PassKeys.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace LIN.Cloud.Identity.Data; - -public class PassKeys(DataContext context) -{ - - /// - /// Contar los logs de autenticación del dia. - /// - /// Id de la cuenta. - public async Task> Count(int id) - { - try - { - // Tiempo. - var time = DateTime.Now; - - // Contar. - int count = await (from a in context.AccountLogs - where a.AccountId == id - && a.AuthenticationMethod == AuthenticationMethods.Authenticator - where a.Time.Year == time.Year - && a.Time.Month == time.Month - && a.Time.Day == time.Day - select a).CountAsync(); - - // Success. - return new(Responses.Success, count); - } - catch (Exception) - { - return new(Responses.NotRows); - } - - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/Policies.cs b/LIN.Cloud.Identity/Data/Policies.cs deleted file mode 100644 index 7585182..0000000 --- a/LIN.Cloud.Identity/Data/Policies.cs +++ /dev/null @@ -1,360 +0,0 @@ -namespace LIN.Cloud.Identity.Data; - -public class Policies(DataContext context, Data.PoliciesRequirement policiesRequirement, Services.Utils.IIdentityService identityService, PolicyService policyService) -{ - - /// - /// Crear nueva política. - /// - /// Modelo. - /// Retorna el id. - public async Task Create(PolicyModel modelo) - { - try - { - - modelo.Id = Guid.NewGuid(); - - // Attach. - context.Attach(modelo.OwnerIdentity); - - foreach (var e in modelo.ApplyFor) - { - e.Identity = context.AttachOrUpdate(e.Identity); - e.Policy = modelo; - } - - // Guardar la cuenta. - await context.Policies.AddAsync(modelo); - context.SaveChanges(); - - return new() - { - Response = Responses.Success, - LastUnique = modelo.Id.ToString() - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ResourceExist - }; - } - - } - - - /// - /// Obtener las políticas donde el usuario es dueño. - /// - /// Id de la identidad. - /// Retorna la lista de políticas. - public async Task> ReadAllOwn(int id) - { - - // Ejecución - try - { - - // Políticas. - var policies = await (from policy in context.Policies - where policy.OwnerIdentityId == id - select policy).ToListAsync(); - - return new(Responses.Success, policies); - - } - catch (Exception) - { - } - return new(); - } - - - /// - /// Obtener una política. - /// - /// Id. - public async Task> Read(Guid guid) - { - - // Ejecución - try - { - - // Políticas. - var policie = await (from policy in context.Policies - where policy.Id == guid - select new PolicyModel - { - Description = policy.Description, - Id = policy.Id, - Name = policy.Name, - OwnerIdentity = new() - { - Id = policy.OwnerIdentityId, - Unique = policy.OwnerIdentity.Unique, - Type = policy.OwnerIdentity.Type - } - }).FirstOrDefaultAsync(); - - if (policie == null) - return new(Responses.NotRows); - - return new(Responses.Success, policie); - - } - catch (Exception) - { - } - return new(); - } - - - /// - /// Obtener las políticas asociadas a una organización. - /// - /// Id de la organización. - public async Task> ReadAll(int id) - { - - // Ejecución - try - { - - // Políticas. - var policies = await (from policy in context.Policies - join gr in context.Groups - on id equals gr.OwnerId - where policy.OwnerIdentityId == gr.IdentityId - select new PolicyModel - { - Description = policy.Description, - Id = policy.Id, - Name = policy.Name, - OwnerIdentity = new() - { - Id = policy.OwnerIdentityId, - Unique = policy.OwnerIdentity.Unique, - Type = policy.OwnerIdentity.Type - } - }).Distinct().ToListAsync(); - - return new(Responses.Success, policies); - - } - catch (Exception) - { - } - return new(); - } - - - /// - /// Obtener las políticas aplicables a una identidad. - /// - /// Id de la identidad. - public async Task> ApplicablePolicies(int id) - { - - // Ejecución - try - { - - // Obtener las identidades - var identities = await identityService.GetIdentities(id); - - // Políticas. - var policies = await (from policy in context.IdentityOnPolicies - where identities.Contains(policy.IdentityId) - select policy.Policy).Distinct().ToListAsync(); - - return new(Responses.Success, policies); - - } - catch (Exception) - { - } - return new(); - } - - - /// - /// Validar si una identidad y sus padres tienen acceso a una política. - /// - /// Id de la identidad base. - /// Id de la política. - public async Task HasFor(int id, string policyId) - { - - // Ejecución - try - { - - // Convertir el id. - var policyResult = Guid.TryParse(policyId, out Guid result); - - // Si hubo un error. - if (!policyResult) - return new(Responses.InvalidParam); - - // Obtener identidades base. - var ids = await identityService.GetIdentities(id); - - // Políticas. - var have = await (from policy in context.Policies - where policy.Id == result - && policy.ApplyFor.Any(t => ids.Contains(t.IdentityId)) - select policy).AnyAsync(); - - // No tiene acceso. - if (!have) - return new(Responses.Unauthorized); - - // Obtener requerimientos. - var requirements = await policiesRequirement.ReadAll(result); - - // Validar. - var validate = policyService.Validate(requirements.Models); - - // Respuesta. - return new(validate is null ? Responses.Success : Responses.Unauthorized) - { - Message = "Error", - Errors = [validate] - }; - - } - catch (Exception) - { - } - return new(); - } - - - /// - /// Eliminar una política. - /// - /// Id de la política. - public async Task Remove(string policyId) - { - - // Ejecución - try - { - - // Convertir el id. - var policyResult = Guid.TryParse(policyId, out Guid result); - - // Si hubo un error. - if (!policyResult) - return new(Responses.InvalidParam); - - // Eliminar vinculos a políticas. - var deleted = await (from policy in context.IdentityOnPolicies - where policy.PolicyId == result - select policy).ExecuteDeleteAsync(); - - // Políticas. - deleted = await (from policy in context.Policies - where policy.Id == result - select policy).ExecuteDeleteAsync(); - - // Respuesta. - return new(Responses.Success); - - } - catch (Exception) - { - } - return new(); - } - - - /// - /// Agregar una identidad a una política. - /// - /// Id de la identidad base. - /// Id de la política. - public async Task AddMember(int id, string policyId) - { - - // Ejecución - try - { - - // Convertir el id. - var policyResult = Guid.TryParse(policyId, out Guid result); - - // Si hubo un error. - if (!policyResult) - return new(Responses.InvalidParam); - - IdentityAllowedOnPolicyModel allow = new() - { - Policy = new() - { - Id = result - }, - Identity = new() - { - Id = id - } - }; - - context.Attach(allow.Policy); - context.Attach(allow.Identity); - - await context.IdentityOnPolicies.AddAsync(allow); - - context.SaveChanges(); - - // Respuesta. - return new(Responses.Success); - - } - catch (Exception) - { - } - return new(); - } - - - /// - /// Eliminar una identidad a una política. - /// - /// Id de la identidad base. - /// Id de la política. - public async Task RemoveMember(int id, string policyId) - { - - // Ejecución - try - { - - // Convertir el id. - var policyResult = Guid.TryParse(policyId, out Guid result); - - // Si hubo un error. - if (!policyResult) - return new(Responses.InvalidParam); - - // Eliminar vínculos a políticas. - var deleted = await (from policy in context.IdentityOnPolicies - where policy.PolicyId == result - && policy.IdentityId == id - select policy).ExecuteDeleteAsync(); - - // Respuesta. - return new(Responses.Success); - - } - catch (Exception) - { - } - return new(); - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Data/PoliciesRequirement.cs b/LIN.Cloud.Identity/Data/PoliciesRequirement.cs deleted file mode 100644 index 7d9c4da..0000000 --- a/LIN.Cloud.Identity/Data/PoliciesRequirement.cs +++ /dev/null @@ -1,86 +0,0 @@ -namespace LIN.Cloud.Identity.Data; - -public class PoliciesRequirement(DataContext context) -{ - - public async Task Create(PolicyRequirementModel modelo) - { - try - { - - modelo.Policy = new() - { - Id = modelo.PolicyId - }; - - // Attach. - context.Attach(modelo.Policy); - - modelo.Requirement ??= System.Text.Json.JsonSerializer.Serialize(modelo.Requirement); - - // Guardar la cuenta. - await context.PolicyRequirements.AddAsync(modelo); - context.SaveChanges(); - - return new() - { - Response = Responses.Success, - LastUnique = modelo.Id.ToString() - }; - - } - catch (Exception) - { - return new() - { - Response = Responses.ResourceExist - }; - } - - } - - - public async Task> ReadAll(Guid policy) - { - - // Ejecución - try - { - - // Políticas. - var policies = await (from policyRequirement in context.PolicyRequirements - where policyRequirement.PolicyId == policy - select policyRequirement).Distinct().ToListAsync(); - - return new(Responses.Success, policies); - - } - catch (Exception) - { - } - return new(); - } - - - public async Task Remove(int policyRequirement) - { - - // Ejecución - try - { - - var deleted = await (from policy in context.PolicyRequirements - where policy.Id == policyRequirement - select policy).ExecuteDeleteAsync(); - - // Respuesta. - return new(Responses.Success); - - } - catch (Exception) - { - } - return new(); - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index bf5c8cc..a8b720e 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,10 +9,10 @@ - - - - + + + + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 6d2ff94..e472509 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -18,7 +18,7 @@ // Servicio de autenticación. builder.Services.AddScoped(); builder.Services.AddPersistence(builder.Configuration); -builder.Host.UseLoggingService(builder.Configuration); +//builder.Host.UseLoggingService(builder.Configuration); var app = builder.Build(); app.UseLINHttp(); diff --git a/LIN.Cloud.Identity/Services/Auth/AllowService.cs b/LIN.Cloud.Identity/Services/Auth/AllowService.cs index 1ac666e..d71b636 100644 --- a/LIN.Cloud.Identity/Services/Auth/AllowService.cs +++ b/LIN.Cloud.Identity/Services/Auth/AllowService.cs @@ -13,13 +13,14 @@ public class AllowService(DataContext context) : IAllowService public async Task IsAllow(IEnumerable identities, int appId) { - // Consulta. - var isAllow = await (from allow in context.AllowApps - where allow.Application.ApplicationId == appId - && identities.Contains(allow.Identity.Id) - select allow.IsAllow).ToListAsync(); + //// Consulta. + //var isAllow = await (from allow in context.AllowApps + // where allow.Application.ApplicationId == appId + // && identities.Contains(allow.Identity.Id) + // select allow.IsAllow).ToListAsync(); - return isAllow.Contains(true); + //return isAllow.Contains(true); + return false; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs index 496312b..4764e8f 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs @@ -20,104 +20,17 @@ private async Task ValidateApp() Application = appResponse.Model; // Si no se requiere validar la restrictions. - if (Account!.IsLINAdmin || !Settings.ValidateApp) + if (!Settings.ValidateApp) return true; // Validar si la restrictions existe. if (appResponse.Response != Responses.Success) return false; - // Obtener las restricciones. - var restriction = await applicationRestrictions.Read(AppCode); - - // Validar las restricciones. - var isAuthorized = await ValidateRestrictions(restriction.Model); - // Respuesta. - return isAuthorized; - } - - - /// - /// Validar restricciones. - /// - /// Modelo de las restricciones. - private async Task ValidateRestrictions(ApplicationRestrictionModel? restriction) - { - - // Si no hay. - if (restriction == null) - return true; - - // Validar por el tipo de cuenta. - switch (Account!.AccountType) - { - case AccountTypes.Personal: - if (!restriction.AllowPersonalAccounts) return false; - break; - case AccountTypes.Work: - if (!restriction.AllowWorkAccounts) return false; - break; - case AccountTypes.Education: - if (!restriction.AllowEducationsAccounts) return false; - break; - } - - // Restricciones de tiempo. - if (restriction.RestrictedByTime) - { - bool isAuthorized = await ValidateRestrictionsTimes(); - if (!isAuthorized) - return false; - } - - // Restricciones de identidad. - if (restriction.RestrictedByIdentities) - { - bool isAuthorized = await ValidateRestrictionsIdentity(); - if (!isAuthorized) - return false; - } - return true; } - /// - /// Validar restricciones de tiempo. - /// - private async Task ValidateRestrictionsTimes() - { - // Hora actual. - var now = new TimeSpan(DateTime.Now.Hour, DateTime.Now.Minute, 0); - - // Obtener las restricciones de tiempo. - var times = await applicationRestrictions.ReadTimes(Application!.Id); - - // Validar si alguna encaja. - foreach (var time in times.Models) - { - if (now > time.StartTime && now < time.EndTime) - return true; - } - - return false; - } - - - /// - /// Validar restricciones de identidades. - /// - private async Task ValidateRestrictionsIdentity() - { - - // Obtener las identidades de un usuario. - var identities = await identityService.GetIdentities(Account!.IdentityId); - - // Validar si alguna esta autorizada para acceder a la app. - bool isAllow = await allowService.IsAllow(identities, Application!.Id); - - return isAllow; - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs index 02338e7..0d46c7b 100644 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ b/LIN.Cloud.Identity/Services/Auth/Authentication.cs @@ -1,9 +1,10 @@ -using LIN.Cloud.Identity.Services.Auth.Interfaces; +using LIN.Cloud.Identity.Persistence.Repositories; +using LIN.Cloud.Identity.Services.Auth.Interfaces; using LIN.Cloud.Identity.Services.Auth.Models; namespace LIN.Cloud.Identity.Services.Auth; -public partial class Authentication(Data.Accounts accountData, Data.AccountLogs accountLogs, Data.ApplicationRestrictions applicationRestrictions, Data.Applications applications, IIdentityService identityService, IAllowService allowService) : Interfaces.IAuthentication +public partial class Authentication(IAccountRepository accountData, IAccountLogRepository accountLogs, IApplicationRepository applications, IIdentityService identityService, IAllowService allowService) : Interfaces.IAuthentication { /// diff --git a/LIN.Cloud.Identity/Services/Auth/JwtService.cs b/LIN.Cloud.Identity/Services/Auth/JwtService.cs index 30fb284..06a43a5 100644 --- a/LIN.Cloud.Identity/Services/Auth/JwtService.cs +++ b/LIN.Cloud.Identity/Services/Auth/JwtService.cs @@ -12,9 +12,9 @@ public class JwtService /// /// Inicia el servicio JwtService /// - public static void Open() + public static void Open(IConfiguration configuration) { - JwtKey = Http.Services.Configuration.GetConfiguration("jwt:key"); + JwtKey =configuration["jwt:key"]; } @@ -25,9 +25,6 @@ public static void Open() public static string Generate(AccountModel user, int appID) { - if (JwtKey == string.Empty) - Open(); - // Configuración var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtKey)); diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 591042f..dba8c71 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -13,22 +13,6 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic { // Servicios de datos. - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - // Externos services.AddSingleton(); diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 7659609..a8c1ef4 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -68,7 +68,6 @@ public static AccountModel Process(AccountModel baseAccount) Password = Global.Utilities.Cryptography.Encrypt(baseAccount.Password), Visibility = baseAccount.Visibility, IdentityId = 0, - IsLINAdmin = false, AccountType = baseAccount.AccountType, Identity = new() { diff --git a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs index 1f0b785..dc667b5 100644 --- a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs @@ -1,6 +1,8 @@ -namespace LIN.Cloud.Identity.Services.Iam; +using LIN.Cloud.Identity.Persistence.Repositories; -public class IamPolicy(DataContext context, Data.Groups groups, IamRoles rolesIam) +namespace LIN.Cloud.Identity.Services.Iam; + +public class IamPolicy(DataContext context, IGroupRepository groups, IamRoles rolesIam) { /// @@ -8,13 +10,13 @@ public class IamPolicy(DataContext context, Data.Groups groups, IamRoles rolesIa /// /// Id de la identidad. /// Id de la política. - public async Task Validate(int identity, string policy) + public async Task Validate(int identity, int policy) { // Si la identidad es la administradora de la política. var isOwner = await (from pol in context.Policies - where pol.OwnerIdentityId == identity - && pol.Id == Guid.Parse(policy) + where pol.Owner.Directory.Identity.Id == identity + && pol.Id == policy select pol).AnyAsync(); // Es el creador. @@ -23,8 +25,8 @@ public async Task Validate(int identity, string policy) // Obtener la identidad del dueño de la política. var ownerPolicy = await (from pol in context.Policies - where pol.Id == Guid.Parse(policy) - select pol.OwnerIdentityId).FirstOrDefaultAsync(); + where pol.Id == policy + select pol.Owner.Directory.IdentityId).FirstOrDefaultAsync(); // Obtener la organización. var organizationId = await groups.GetOwnerByIdentity(ownerPolicy); diff --git a/LIN.Cloud.Identity/Services/Iam/IamRoles.cs b/LIN.Cloud.Identity/Services/Iam/IamRoles.cs index 0d6a711..00d38a9 100644 --- a/LIN.Cloud.Identity/Services/Iam/IamRoles.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamRoles.cs @@ -1,6 +1,8 @@ -namespace LIN.Cloud.Identity.Services.Iam; +using LIN.Cloud.Identity.Persistence.Repositories; -public class IamRoles(DataContext context, Data.Groups groups, IIdentityService identityService) +namespace LIN.Cloud.Identity.Services.Iam; + +public class IamRoles(DataContext context, IGroupRepository groups, IIdentityService identityService) { /// @@ -33,7 +35,7 @@ public async Task IamIdentity(int identity1, int identity2) var organizations = await (from z in context.Groups where z.Members.Any(x => x.IdentityId == identity1) && z.Members.Any(x => x.IdentityId == identity2) - select z.Owner!.Id).Distinct().ToListAsync(); + select z.Identity.Owner!.Id).Distinct().ToListAsync(); // Si es un grupo. var organization = await groups.GetOwnerByIdentity(identity2); diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 9e3810f..5eedbbf 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -1,6 +1,8 @@ -namespace LIN.Cloud.Identity.Services.Realtime; +using LIN.Cloud.Identity.Persistence.Repositories; -public partial class PassKeyHub(Data.AccountLogs accountLogs) : Hub +namespace LIN.Cloud.Identity.Services.Realtime; + +public partial class PassKeyHub(IAccountLogRepository accountLogs) : Hub { /// diff --git a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs index b679841..e6d6f71 100644 --- a/LIN.Cloud.Identity/Services/Utils/EmailSender.cs +++ b/LIN.Cloud.Identity/Services/Utils/EmailSender.cs @@ -1,6 +1,6 @@ namespace LIN.Cloud.Identity.Services.Utils; -public class EmailSender(ILogger logger) +public class EmailSender(ILogger logger, IConfiguration configuration) { /// @@ -14,7 +14,7 @@ public async Task Send(string to, string subject, string body) try { // Servicio. - Global.Http.Services.Client client = new(Http.Services.Configuration.GetConfiguration("hangfire:mail")) + Global.Http.Services.Client client = new(configuration["hangfire:mail"]) { TimeOut = 10 }; diff --git a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs index 912f173..7c9ec84 100644 --- a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs +++ b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs @@ -8,128 +8,4 @@ public class PolicyService(IamPolicy iamPolicy) - public ErrorModel? Validate(IEnumerable requirements) - { - - bool isValid = Time(requirements); - - if (!isValid) - return new() - { - Tittle = "Política no complaciente", - Description = "No tienes acceso a la política por la hora actual." - }; - - // Validar contraseña. - isValid = PasswordTime(requirements); - - if (!isValid) - return new() - { - Tittle = "Política no complaciente", - Description = "No tienes acceso a la política debido a que no has cambiado la contraseña en los últimos N dias." - }; - - isValid = TFA(requirements); - - if (!isValid) - return new() - { - Tittle = "Política no complaciente", - Description = "No tienes acceso a la política debido a que no tienes el doble factor de autenticación" - }; - - return null; - - } - - - bool Time(IEnumerable requirements) - { - - // Solo de tiempo. - requirements = requirements.Where(x => x.Type == PolicyRequirementTypes.Time); - - // Si no hay políticas de tiempo. - if (!requirements.Any()) - return true; - - // Validar los requerimientos de tiempo. - foreach (var requirement in requirements) - { - - // Objeto. - JObject jsonObject = JObject.Parse(requirement.Requirement ?? ""); - - // Obtener los ticks. - var startTicks = Convert.ToInt64(jsonObject["start"]); - var endTicks = Convert.ToInt64(jsonObject["end"]); - - // Parsear a tiempo. - var start = TimeSpan.FromTicks(startTicks); - var end = TimeSpan.FromTicks(endTicks); - - // var days = TimeSpan.FromTicks((requirement.Requirement as dynamic).days); - - bool valid = iamPolicy.PolicyRequirement(start, end); - - if (valid) - return true; - - - } - - return false; - } - - - bool PasswordTime(IEnumerable requirements) - { - - // Solo de tiempo. - requirements = requirements.Where(x => x.Type == PolicyRequirementTypes.PasswordTime); - - // Si no hay políticas de tiempo. - if (!requirements.Any()) - return true; - - // Validar los requerimientos de tiempo. - foreach (var requirement in requirements) - { - - // Objeto. - JObject jsonObject = JObject.Parse(requirement.Requirement ?? ""); - - // Obtener los ticks. - var days = Convert.ToInt64(jsonObject["days"]); - - - // Dias. - if (days <= 30) - { - return true; - } - - } - - return false; - } - - - bool TFA(IEnumerable requirements) - { - - // Solo de tiempo. - requirements = requirements.Where(x => x.Type == PolicyRequirementTypes.TFA); - - // Si no hay políticas de tiempo. - if (!requirements.Any()) - return true; - - // Validar doble facto. - return false; - } - - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 9548ed7..46ef86c 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -11,6 +11,8 @@ global using LIN.Cloud.Identity.Services.Utils; global using LIN.Types.Cloud.Identity.Enumerations; global using LIN.Types.Cloud.Identity.Models; +global using LIN.Types.Cloud.Identity.Models.Identities; +global using LIN.Types.Cloud.Identity.Models.Policies; global using LIN.Types.Enumerations; // Tipos Generales global using LIN.Types.Responses; diff --git a/LIN.Identity.Tests/Auth/JwtServiceTests.cs b/LIN.Identity.Tests/Auth/JwtServiceTests.cs deleted file mode 100644 index 17becdb..0000000 --- a/LIN.Identity.Tests/Auth/JwtServiceTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -using LIN.Cloud.Identity.Services.Auth; -using LIN.Types.Cloud.Identity.Models; - -namespace LIN.Identity.Tests.Auth; - -public class JwtServiceTests -{ - private const string TestJwtKey = "wTgdLCN4GuV37K5I1H6C331Z146tylLINkiZt69nokIdwTgdLCN4GuV37K5I1H6C331Z146tylLINkiZt69nokId"; - - public JwtServiceTests() - { - // Configurar la llave JWT para las pruebas - typeof(JwtService).GetProperty("JwtKey", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)?.SetValue(null, TestJwtKey); - } - - [Fact] - public void GenerateValidToken() - { - // Arrange - var user = new AccountModel - { - Id = 1, - Identity = new IdentityModel { Unique = "unique_user" } - }; - int appID = 123; - - // Act - var token = JwtService.Generate(user, appID); - - // Assert - Assert.False(string.IsNullOrEmpty(token)); - } - - [Fact] - public void ValidateTrueToken() - { - // Arrange - var user = new AccountModel - { - Id = 1, - Identity = new IdentityModel { Unique = "unique_user" } - }; - int appID = 123; - var token = JwtService.Generate(user, appID); - - // Act - var result = JwtService.Validate(token); - - // Assert - Assert.True(result.IsAuthenticated); - Assert.Equal(user.Id, result.AccountId); - Assert.Equal(user.Identity.Unique, result.Unique); - Assert.Equal(appID, result.ApplicationId); - } - - [Fact] - public void ValidateFalseToken() - { - // Arrange - var invalidToken = "invalid_token"; - - // Act - var result = JwtService.Validate(invalidToken); - - // Assert - Assert.False(result.IsAuthenticated); - } - -} \ No newline at end of file diff --git a/LIN.Identity.Tests/Data/Accounts.cs b/LIN.Identity.Tests/Data/Accounts.cs deleted file mode 100644 index 6cbd9bd..0000000 --- a/LIN.Identity.Tests/Data/Accounts.cs +++ /dev/null @@ -1,278 +0,0 @@ -using LIN.Cloud.Identity.Persistence.Contexts; -using LIN.Cloud.Identity.Persistence.Models; -using LIN.Types.Cloud.Identity.Models; -using LIN.Types.Responses; -using Microsoft.EntityFrameworkCore; - -namespace LIN.Identity.Tests.Data; - -public class Accounts -{ - - /// - /// Obtener base de datos en memoria. - /// - /// - private static DataContext GetInMemoryDbContext() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: "TestDatabase") - .Options; - - return new DataContext(options); - } - - [Fact] - public async Task Create_ShouldReturnSuccessResponse_WhenAccountIsCreated() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - var accountModel = new AccountModel { Name = "Test Account" }; - - // Act - var response = await accounts.Create(accountModel); - - // Assert - Assert.Equal(Responses.Success, response.Response); - Assert.NotNull(response.Model); - Assert.Equal("Test Account", response.Model.Name); - } - - [Fact] - public async Task Create_ShouldReturnExistAccountResponse_WhenOrganizationNotFound() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - var accountModel = new AccountModel { Name = "Test Account" }; - - // Act - var response = await accounts.Create(accountModel, organization: 999); - - // Assert - Assert.Equal(Responses.ExistAccount, response.Response); - } - - [Fact] - public async Task Read_ShouldReturnNotRowsResponse_WhenAccountDoesNotExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - - // Act - var response = await accounts.Read(999, new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.NotRows, response.Response); - } - - [Fact] - public async Task ReadByUnique_ShouldReturnSuccessResponse_WhenAccountExists() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - var accountModel = new AccountModel - { - Name = "Test Account", - Identity = new IdentityModel - { - Unique = "unique-id", - EffectiveTime = DateTime.Now, - ExpirationTime = DateTime.Now.AddDays(1) - } - }; - await context.Accounts.AddAsync(accountModel); - await context.SaveChangesAsync(); - - // Act - var response = await accounts.Read("unique-id", new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.Success, response.Response); - Assert.NotNull(response.Model); - Assert.Equal("Test Account", response.Model.Name); - } - - [Fact] - public async Task ReadByUnique_ShouldReturnNotRowsResponse_WhenAccountDoesNotExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - - // Act - var response = await accounts.Read("non-existent-id", new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.NotRows, response.Response); - } - - [Fact] - public async Task ReadByIdentity_ShouldReturnSuccessResponse_WhenAccountExists() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - var accountModel = new AccountModel - { - Name = "Test Account", - Identity = new IdentityModel - { - Unique = "unique-id", - EffectiveTime = DateTime.Now, - ExpirationTime = DateTime.Now.AddDays(1) - } - }; - await context.Accounts.AddAsync(accountModel); - await context.SaveChangesAsync(); - - // Act - var response = await accounts.ReadByIdentity(1, new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.Success, response.Response); - Assert.NotNull(response.Model); - Assert.Equal("Test Account", response.Model.Name); - } - - [Fact] - public async Task ReadByIdentity_ShouldReturnNotRowsResponse_WhenAccountDoesNotExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - - // Act - var response = await accounts.ReadByIdentity(999, new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.NotRows, response.Response); - } - - [Fact] - public async Task Search_ShouldReturnSuccessResponse_WhenAccountsMatchPattern() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - var accountModel = new AccountModel - { - Name = "Test Account", - Identity = new IdentityModel - { - Unique = "Test", - EffectiveTime = DateTime.Now, - ExpirationTime = DateTime.Now.AddDays(1) - } - }; - await context.Accounts.AddAsync(accountModel); - await context.SaveChangesAsync(); - - // Act - var response = await accounts.Search("Test", new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.Success, response.Response); - Assert.NotEmpty(response.Models); - } - - [Fact] - public async Task Search_ShouldReturnNotRowsResponse_WhenNoAccountsMatchPattern() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - - // Act - var response = await accounts.Search("NonExistentPattern", new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.NotRows, response.Response); - } - - [Fact] - public async Task FindAll_ShouldReturnSuccessResponse_WhenAccountsExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - var accountModel = new AccountModel - { - Name = "Test Account", - Identity = new() - { - Unique = "test", - EffectiveTime = DateTime.Now, - ExpirationTime = DateTime.Now.AddDays(1) - } - }; - await context.Accounts.AddAsync(accountModel); - await context.SaveChangesAsync(); - - // Act - var response = await accounts.FindAll([accountModel.Id], new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.Success, response.Response); - Assert.NotEmpty(response.Models); - } - - [Fact] - public async Task FindAll_ShouldReturnNotRowsResponse_WhenAccountsDoNotExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - - // Act - var response = await accounts.FindAll([999], new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.NotRows, response.Response); - } - - [Fact] - public async Task FindAllByIdentities_ShouldReturnSuccessResponse_WhenAccountsExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - var accountModel = new AccountModel - { - Name = "Test Account", - Identity = new() - { - Unique = "test", - EffectiveTime = DateTime.Now, - ExpirationTime = DateTime.Now.AddDays(1) - } - }; - await context.Accounts.AddAsync(accountModel); - await context.SaveChangesAsync(); - - // Act - var response = await accounts.FindAllByIdentities([1], new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.Success, response.Response); - Assert.NotEmpty(response.Models); - } - - [Fact] - public async Task FindAllByIdentities_ShouldReturnNotRowsResponse_WhenAccountsDoNotExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var accounts = new Cloud.Identity.Data.Accounts(context); - - // Act - var response = await accounts.FindAllByIdentities([999], new QueryObjectFilter()); - - // Assert - Assert.Equal(Responses.NotRows, response.Response); - } - -} \ No newline at end of file diff --git a/LIN.Identity.Tests/Data/Groups.cs b/LIN.Identity.Tests/Data/Groups.cs deleted file mode 100644 index c367902..0000000 --- a/LIN.Identity.Tests/Data/Groups.cs +++ /dev/null @@ -1,72 +0,0 @@ -using LIN.Cloud.Identity.Data; -using LIN.Cloud.Identity.Persistence.Contexts; -using LIN.Types.Cloud.Identity.Models; -using LIN.Types.Responses; -using Microsoft.EntityFrameworkCore; - -namespace LIN.Identity.Tests.Data; - -public class GroupsTests -{ - private static DataContext GetInMemoryDbContext() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) - .Options; - - return new DataContext(options); - } - - [Fact] - public async Task ReadByIdentity_ShouldReturnSuccessResponse_WhenGroupExists() - { - // Arrange - var context = GetInMemoryDbContext(); - var groups = new Groups(context); - var groupModel = new GroupModel - { - Name = "Test Group", - Identity = new IdentityModel { Unique = "unique", Id = 0 } - }; - - context.Groups.Add(groupModel); - await context.SaveChangesAsync(); - - // Act - var response = await groups.ReadByIdentity(1); - - // Assert - Assert.Equal(Responses.Success, response.Response); - Assert.NotNull(response.Model); - Assert.Equal("Test Group", response.Model.Name); - } - - [Fact] - public async Task ReadByIdentity_ShouldReturnNotRowsResponse_WhenGroupDoesNotExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var groups = new Groups(context); - - // Act - var response = await groups.ReadByIdentity(999); - - // Assert - Assert.Equal(Responses.NotRows, response.Response); - } - - [Fact] - public async Task ReadAll_ShouldReturnNotRowsResponse_WhenNoGroupsExist() - { - // Arrange - var context = GetInMemoryDbContext(); - var groups = new Groups(context); - - // Act - var response = await groups.ReadAll(999); - - // Assert - Assert.Empty(response.Models); - } - -} \ No newline at end of file diff --git a/LIN.Identity.Tests/LIN.Identity.Tests.csproj b/LIN.Identity.Tests/LIN.Identity.Tests.csproj deleted file mode 100644 index 94a5a57..0000000 --- a/LIN.Identity.Tests/LIN.Identity.Tests.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - net9.0 - enable - enable - false - true - Debug;Release;Local;Release-dev;Debug-dev - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/LIN.Identity.Tests/Validations.cs b/LIN.Identity.Tests/Validations.cs deleted file mode 100644 index 8e864f8..0000000 --- a/LIN.Identity.Tests/Validations.cs +++ /dev/null @@ -1,37 +0,0 @@ -using LIN.Cloud.Identity.Services.Formats; -using LIN.Types.Cloud.Identity.Enumerations; -using LIN.Types.Cloud.Identity.Models; - -namespace LIN.Identity.Tests; - -public class AccountTests -{ - [Fact] - public void Process_ShouldReturnProcessedAccountModel() - { - // Arrange - var account = new AccountModel - { - Name = " Test Account ", - Profile = string.Empty, - Password = "password", - Visibility = Visibility.Visible, - Identity = new IdentityModel - { - Unique = "uniqueuser" - } - }; - - // Act - var processedAccount = Account.Process(account); - - // Assert - Assert.Equal("Test Account", processedAccount.Name); - Assert.NotEqual("password", processedAccount.Password); // Assuming encryption changes the password - Assert.Equal(Visibility.Visible, processedAccount.Visibility); - Assert.Equal("uniqueuser", processedAccount.Identity.Unique); - Assert.Equal(IdentityStatus.Enable, processedAccount.Identity.Status); - Assert.Equal(IdentityType.Account, processedAccount.Identity.Type); - } - -} diff --git a/README.md b/README.md index a3b5c4f..186f2df 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Proyecto de Autenticación de Usuarios LIN -Este es un proyecto desarrollado en C# y .NET 8 que proporciona funcionalidades de autenticación de usuarios para sistemas LIN. El proyecto se centra en garantizar la seguridad y la gestión de usuarios en entornos LIN, permitiendo un acceso controlado a la información. +Este es un proyecto desarrollado en C# y .NET 9 que proporciona funcionalidades de autenticación de usuarios para sistemas LIN. El proyecto se centra en garantizar la seguridad y la gestión de usuarios en entornos LIN, permitiendo un acceso controlado a la información. # Características @@ -15,7 +15,7 @@ Este es un proyecto desarrollado en C# y .NET 8 que proporciona funcionalidades ## Requisitos del Sistema -- [.NET 8 Runtime](https://dotnet.microsoft.com/download/dotnet/8.0) +- [.NET 9 Runtime](https://dotnet.microsoft.com/download/dotnet/9.0) - Base de datos compatible con Entity Framework (SQL Server) ## Configuración From 6ecc8fa3f56145f83c5aa01171bcd78a825d11c3 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sat, 17 May 2025 15:53:31 -0500 Subject: [PATCH 164/178] Motor de politicas --- .../Contexts/DataContext.cs | 28 +++- .../Extensions/PersistenceExtensions.cs | 4 +- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../Models/MailOtpDatabaseModel.cs | 4 +- .../Models/QueryObjectFilter.cs | 1 + .../Queries/IdentityFindable.cs | 1 - .../EntityFramework/AccountRepository.cs | 9 +- .../EntityFramework/Builders/Account.cs | 2 +- .../EntityFramework/GroupRepository.cs | 4 +- .../EntityFramework/OrganizationRepository.cs | 16 +- .../EntityFramework/PolicyRepository.cs | 57 +++++++ .../Repositories/IPolicyRepository.cs | 2 + LIN.Cloud.Identity.Persistence/Usings.cs | 14 +- .../Extensions/ServiceExtensions.cs | 31 ++++ .../AuthenticationPipelineComponent.cs | 5 + .../IAuthenticationAccountService.cs | 9 + .../Interfaces/IAuthenticationService.cs | 6 +- .../Interfaces/IIdentityService.cs | 9 + .../Interfaces/IPolicyOrchestrator.cs | 8 + .../Models/AuthenticationRequest.cs | 12 ++ .../Models/JwtModel.cs | 0 .../Models/PolicyValidationResult.cs | 7 + .../Models/PolicyValidatorContext.cs | 9 + .../Services/AccountAuthenticationService.cs | 52 ++++++ .../ApplicationValidationService.cs | 34 ++++ .../IdentityValidationService.cs | 53 ++++++ .../OrganizationValidationService.cs | 26 +++ .../Services/IdentityService.cs | 142 ++++++++++++++++ .../Services}/JwtService.cs | 14 +- .../Policies/IdentityTypePolicyValidator.cs | 40 +++++ .../Policies/TimeAccessPolicyValidator.cs | 42 +++++ .../Services/PolicyOrchestrator.cs | 70 ++++++++ .../Services/ServiceAuthenticationService.cs | 9 - .../Services/UserAuthenticationService.cs | 9 - LIN.Cloud.Identity.Services/usings.cs | 5 +- .../Areas/Accounts/AccountController.cs | 2 +- .../ApplicationRestrictionsController.cs | 2 +- .../AuthenticationController.cs | 107 +++--------- .../Authentication/SecurityController.cs | 2 +- .../Areas/Policies/PoliciesController.cs | 2 +- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 1 + LIN.Cloud.Identity/Program.cs | 3 +- .../Auth/Authentication.Application.cs | 36 ---- .../Services/Auth/Authentication.cs | 156 ------------------ .../Auth/Interfaces/IAuthentication.cs | 34 ---- .../Filters/IdentityTokenAttribute.cs | 3 +- .../Services/Models/QueryIdentityFilter.cs | 14 -- .../Services/Realtime/PassKeyHub.cs | 1 + .../Services/Realtime/PassKeyHubActions.cs | 4 +- .../Services/Utils/IIdentityService.cs | 6 - .../Services/Utils/IdentityService.cs | 56 ------- .../Services/Utils/PolicyService.cs | 11 -- LIN.Cloud.Identity/Usings.cs | 6 - 53 files changed, 712 insertions(+), 470 deletions(-) create mode 100644 LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs create mode 100644 LIN.Cloud.Identity.Services/Interfaces/AuthenticationPipelineComponent.cs create mode 100644 LIN.Cloud.Identity.Services/Interfaces/IAuthenticationAccountService.cs create mode 100644 LIN.Cloud.Identity.Services/Interfaces/IIdentityService.cs create mode 100644 LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs create mode 100644 LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs rename {LIN.Cloud.Identity/Services => LIN.Cloud.Identity.Services}/Models/JwtModel.cs (100%) create mode 100644 LIN.Cloud.Identity.Services/Models/PolicyValidationResult.cs create mode 100644 LIN.Cloud.Identity.Services/Models/PolicyValidatorContext.cs create mode 100644 LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs create mode 100644 LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs create mode 100644 LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs create mode 100644 LIN.Cloud.Identity.Services/Services/Authentication/OrganizationValidationService.cs create mode 100644 LIN.Cloud.Identity.Services/Services/IdentityService.cs rename {LIN.Cloud.Identity/Services/Auth => LIN.Cloud.Identity.Services/Services}/JwtService.cs (89%) create mode 100644 LIN.Cloud.Identity.Services/Services/Policies/IdentityTypePolicyValidator.cs create mode 100644 LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs create mode 100644 LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs delete mode 100644 LIN.Cloud.Identity.Services/Services/ServiceAuthenticationService.cs delete mode 100644 LIN.Cloud.Identity.Services/Services/UserAuthenticationService.cs delete mode 100644 LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs delete mode 100644 LIN.Cloud.Identity/Services/Auth/Authentication.cs delete mode 100644 LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs delete mode 100644 LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs delete mode 100644 LIN.Cloud.Identity/Services/Utils/IIdentityService.cs delete mode 100644 LIN.Cloud.Identity/Services/Utils/IdentityService.cs delete mode 100644 LIN.Cloud.Identity/Services/Utils/PolicyService.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 2c6f51f..67fe20b 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -1,9 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Models; -using LIN.Types.Cloud.Identity.Models; -using LIN.Types.Cloud.Identity.Models.Identities; -using LIN.Types.Cloud.Identity.Models.Policies; -using Microsoft.EntityFrameworkCore; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace LIN.Cloud.Identity.Persistence.Contexts; @@ -85,6 +80,11 @@ public class DataContext(DbContextOptions options) : DbContext(opti /// public DbSet MailOtp { get; set; } + /// + /// Mail Otp. + /// + public DbSet IdentityPolicies { get; set; } + /// /// Crear el modelo en BD. /// @@ -144,6 +144,22 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(t => t.GroupId); }); + // Group Member Model + modelBuilder.Entity(entity => + { + entity.ToTable("identity_policy"); + entity.HasKey(t => new { t.IdentityId, t.PolicyId }); + + entity.HasOne(t => t.Identity) + .WithMany() + .HasForeignKey(t => t.IdentityId) + .OnDelete(DeleteBehavior.NoAction); + + entity.HasOne(t => t.Policy) + .WithMany() + .HasForeignKey(t => t.PolicyId); + }); + // Identity Roles Model modelBuilder.Entity(entity => { diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 24b999d..16e75ab 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -1,9 +1,7 @@ -using LIN.Cloud.Identity.Persistence.Contexts; -using LIN.Cloud.Identity.Persistence.Queries; +using LIN.Cloud.Identity.Persistence.Queries; using LIN.Cloud.Identity.Persistence.Repositories; using LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; using Microsoft.AspNetCore.Builder; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index c24a02a..20db7b7 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs b/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs index 3e7431c..9cd966d 100644 --- a/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs +++ b/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs @@ -1,6 +1,4 @@ -using LIN.Types.Cloud.Identity.Models; - -namespace LIN.Cloud.Identity.Persistence.Models; +namespace LIN.Cloud.Identity.Persistence.Models; public class MailOtpDatabaseModel { diff --git a/LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs b/LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs index 5ce0efc..7ad8798 100644 --- a/LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs +++ b/LIN.Cloud.Identity.Persistence/Models/QueryObjectFilter.cs @@ -7,6 +7,7 @@ public class QueryObjectFilter public List OrganizationsDirectories { get; set; } = []; public bool IsAdmin { get; set; } public bool IncludePhoto { get; set; } = true; + public bool IncludeIdentity { get; set; } public FindOn FindOn { get; set; } } diff --git a/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs b/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs index 5a0ad09..a9ba2de 100644 --- a/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs +++ b/LIN.Cloud.Identity.Persistence/Queries/IdentityFindable.cs @@ -1,5 +1,4 @@ using LIN.Cloud.Identity.Persistence.Queries.Interfaces; -using LIN.Types.Cloud.Identity.Models.Identities; namespace LIN.Cloud.Identity.Persistence.Queries; diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs index 1db8540..65195d2 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs @@ -48,6 +48,13 @@ public async Task> Create(AccountModel modelo, int Type = GroupMemberTypes.User }; + modelo.Identity.Owner = new() + { + Id = organization + }; + + modelo.Identity.Owner = context.AttachOrUpdate(modelo.Identity.Owner)!; + // El grupo existe. groupMember.Group = context.AttachOrUpdate(groupMember.Group)!; context.GroupMembers.Add(groupMember); @@ -108,7 +115,7 @@ public async Task> Read(string unique, QueryObject try { // Consulta de las cuentas. - var account = await accountFindable.GetAccounts(unique, filters).FirstOrDefaultAsync(); + var account = await accountFindable.GetAccounts(unique, filters).IncludeIf(filters.IncludeIdentity, t => t.Include(a => a.Identity)).FirstOrDefaultAsync(); // Si la cuenta no existe. if (account is null) diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs index 26dce88..15be811 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs @@ -249,7 +249,7 @@ private static IQueryable BuildModel(IQueryable quer || account.Visibility == Visibility.Visible || filters.AccountContext == account.Id || context.GroupMembers.FirstOrDefault(t => t.Group.Members.Any(t => t.IdentityId == filters.IdentityContext)) != null - + ? account.Name : "Usuario privado", Identity = new() diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupRepository.cs index 78fc45c..089d41b 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/GroupRepository.cs @@ -36,8 +36,8 @@ public async Task> Create(GroupModel modelo) // Obtener el directorio general. var generalGroupInformation = (from org in context.Organizations - where org.Id == modelo.Identity.OwnerId - select new { org.DirectoryId, org.Directory.Identity.Unique }).FirstOrDefault(); + where org.Id == modelo.Identity.OwnerId + select new { org.DirectoryId, org.Directory.Identity.Unique }).FirstOrDefault(); // Si no se encontró el directorio. if (generalGroupInformation is null) diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs index b390ede..9d7eff4 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs @@ -19,12 +19,15 @@ public async Task Create(OrganizationModel modelo) // Metadata. modelo.Directory.Name = "Directorio General"; modelo.Directory.Description = "Directorio General de la organización"; - modelo.Directory.Identity.Owner = modelo; - modelo.Directory.Identity.Type = IdentityType.Group; + modelo.Directory.Identity.Type = IdentityType.Group; + modelo.Directory.Identity.Owner = null; + modelo.Directory.Identity.OwnerId = null; // Agregar la organización. await context.Organizations.AddAsync(modelo); context.SaveChanges(); + modelo.Directory.Identity.Owner = modelo; + context.SaveChanges(); // Crear la cuenta administrativa. var account = new AccountModel() @@ -64,6 +67,7 @@ public async Task Create(OrganizationModel modelo) context.SaveChanges(); modelo.Directory.Identity.Owner = modelo; + account.Identity.Owner = modelo; modelo.Directory.Members.Add(new() { Group = modelo.Directory, @@ -149,8 +153,8 @@ public async Task> ReadDirectory(int id) try { var groupId = await (from g in context.Organizations - where g.Id == id - select g.DirectoryId).FirstOrDefaultAsync(); + where g.Id == id + select g.DirectoryId).FirstOrDefaultAsync(); // Si la cuenta no existe. if (groupId <= 0) @@ -176,8 +180,8 @@ public async Task> ReadDirectoryIdentity(int id) try { var identityId = await (from g in context.Organizations - where g.Id == id - select g.Directory.IdentityId).FirstOrDefaultAsync(); + where g.Id == id + select g.Directory.IdentityId).FirstOrDefaultAsync(); // Si la cuenta no existe. if (identityId <= 0) diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs index 7fef39f..21625c5 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs @@ -13,6 +13,16 @@ public async Task Create(PolicyModel model) model.CreatedBy = context.AttachOrUpdate(model.CreatedBy)!; model.Owner = context.AttachOrUpdate(model.Owner); + foreach (var e in model.TimeAccessPolicies) + { + e.Policy = model; + } + + foreach (var e in model.IdentityTypePolicies) + { + e.Policy = model; + } + // Guardar la cuenta. await context.Policies.AddAsync(model); context.SaveChanges(); @@ -115,6 +125,53 @@ public async Task Delete(int id) return new(); } + /// + /// Obtener una política de acceso por organization. + /// + /// Id de la política. + public async Task> ReadAll(int organization, bool includeDetails) + { + try + { + var model = await (from p in context.Policies + where p.OwnerId == organization + select p) + .IncludeIf(includeDetails, t => t.Include(t => t.TimeAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.IpAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.IdentityTypePolicies)).ToListAsync(); + + return new(Responses.Success, model); + } + catch (Exception) + { + } + return new(); + } + + + /// + /// Obtener una política de acceso por organization. + /// + /// Id de la política. + public async Task> ReadAll(IEnumerable identities, int organization, bool includeDetails) + { + try + { + var model = await context.IdentityPolicies + .Where(p => p.Identity.OwnerId == organization && identities.Contains(p.IdentityId)) + .IncludeIf(includeDetails, t => t.Include(t => t.Policy).ThenInclude(p => p.TimeAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.Policy).ThenInclude(p => p.IpAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.Policy).ThenInclude(p => p.IdentityTypePolicies)) + .Select(p => p.Policy) + .ToListAsync(); + + return new(Responses.Success, model); + } + catch (Exception) + { + } + return new(); + } /// /// Obtener una política de acceso por id. diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs index 651be5f..54580cf 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs @@ -7,5 +7,7 @@ public interface IPolicyRepository Task Add(IpAccessPolicy policyModel); Task Add(IdentityTypePolicy policyModel); Task> Read(int id, bool includeDetails); + Task> ReadAll(int organization, bool includeDetails); + Task> ReadAll(IEnumerable identity, int organization, bool includeDetails); Task Delete(int id); } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Usings.cs b/LIN.Cloud.Identity.Persistence/Usings.cs index 7b0d521..e9e4957 100644 --- a/LIN.Cloud.Identity.Persistence/Usings.cs +++ b/LIN.Cloud.Identity.Persistence/Usings.cs @@ -1,9 +1,9 @@ -global using LIN.Types.Cloud.Identity.Models; -global using LIN.Types.Responses; -global using LIN.Types.Cloud.Identity.Models.Policies; -global using LIN.Types.Cloud.Identity.Models.Identities; -global using LIN.Cloud.Identity.Persistence.Contexts; -global using Microsoft.EntityFrameworkCore; +global using LIN.Cloud.Identity.Persistence.Contexts; +global using LIN.Cloud.Identity.Persistence.Extensions; global using LIN.Cloud.Identity.Persistence.Models; global using LIN.Types.Cloud.Identity.Enumerations; -global using LIN.Cloud.Identity.Persistence.Extensions; \ No newline at end of file +global using LIN.Types.Cloud.Identity.Models; +global using LIN.Types.Cloud.Identity.Models.Identities; +global using LIN.Types.Cloud.Identity.Models.Policies; +global using LIN.Types.Responses; +global using Microsoft.EntityFrameworkCore; diff --git a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000..9088b18 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs @@ -0,0 +1,31 @@ +using LIN.Cloud.Identity.Services.Services; +using LIN.Cloud.Identity.Services.Services.Authentication; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace LIN.Cloud.Identity.Services.Extensions; + +public static class ServiceExtensions +{ + + /// + /// Agregar servicios de persistence. + /// + /// Services. + public static IServiceCollection AddAuthenticationServices(this IServiceCollection services, IConfiguration configuration) + { + // Servicios de datos. + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + + JwtService.Open(configuration); + + return services; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Interfaces/AuthenticationPipelineComponent.cs b/LIN.Cloud.Identity.Services/Interfaces/AuthenticationPipelineComponent.cs new file mode 100644 index 0000000..0f181b8 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Interfaces/AuthenticationPipelineComponent.cs @@ -0,0 +1,5 @@ +namespace LIN.Cloud.Identity.Services.Interfaces; + +public interface IApplicationValidationService : IAuthenticationService; +public interface IIdentityValidationService : IAuthenticationService; +public interface IOrganizationValidationService : IAuthenticationService; \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationAccountService.cs b/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationAccountService.cs new file mode 100644 index 0000000..350ae5f --- /dev/null +++ b/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationAccountService.cs @@ -0,0 +1,9 @@ +using LIN.Types.Cloud.Identity.Models.Identities; + +namespace LIN.Cloud.Identity.Services.Interfaces; + +public interface IAuthenticationAccountService : IAuthenticationService +{ + public AccountModel? Account { get; } + public string GenerateToken(); +} diff --git a/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs b/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs index 78e5753..cd9bb8d 100644 --- a/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs +++ b/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs @@ -1,6 +1,8 @@ -namespace LIN.Cloud.Identity.Services.Interfaces; +using LIN.Cloud.Identity.Services.Models; + +namespace LIN.Cloud.Identity.Services.Interfaces; public interface IAuthenticationService { - public Task Authenticate(); + public Task Authenticate(AuthenticationRequest request); } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Interfaces/IIdentityService.cs b/LIN.Cloud.Identity.Services/Interfaces/IIdentityService.cs new file mode 100644 index 0000000..528e3b9 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Interfaces/IIdentityService.cs @@ -0,0 +1,9 @@ +using LIN.Cloud.Identity.Services.Services; + +namespace LIN.Cloud.Identity.Services.Interfaces; + +internal interface IIdentityService +{ + Task> GetIdentities(int identity); + Task>> GetLevel(int identity, int organization); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs b/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs new file mode 100644 index 0000000..95acdf8 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs @@ -0,0 +1,8 @@ +using LIN.Cloud.Identity.Services.Models; + +namespace LIN.Cloud.Identity.Services.Interfaces; + +internal interface IPolicyOrchestrator +{ + Task> ValidatePoliciesForOrganization(AuthenticationRequest request); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs b/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs new file mode 100644 index 0000000..0a59f08 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs @@ -0,0 +1,12 @@ +namespace LIN.Cloud.Identity.Services.Models; + +public class AuthenticationRequest +{ + public string User { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public string Application { get; set; } = string.Empty; + + public LIN.Types.Cloud.Identity.Models.Identities.AccountModel? Account { get; internal set; } + public LIN.Types.Cloud.Identity.Models.Identities.ApplicationModel? ApplicationModel { get; internal set; } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Models/JwtModel.cs b/LIN.Cloud.Identity.Services/Models/JwtModel.cs similarity index 100% rename from LIN.Cloud.Identity/Services/Models/JwtModel.cs rename to LIN.Cloud.Identity.Services/Models/JwtModel.cs diff --git a/LIN.Cloud.Identity.Services/Models/PolicyValidationResult.cs b/LIN.Cloud.Identity.Services/Models/PolicyValidationResult.cs new file mode 100644 index 0000000..841b416 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Models/PolicyValidationResult.cs @@ -0,0 +1,7 @@ +namespace LIN.Cloud.Identity.Services.Models; + +internal class PolicyValidationResult +{ + public bool IsValid { get; set; } = true; + public string Reason { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Models/PolicyValidatorContext.cs b/LIN.Cloud.Identity.Services/Models/PolicyValidatorContext.cs new file mode 100644 index 0000000..954cca4 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Models/PolicyValidatorContext.cs @@ -0,0 +1,9 @@ +namespace LIN.Cloud.Identity.Services.Models; + +internal class PolicyValidatorContext +{ + public AuthenticationRequest AuthenticationRequest { get; set; } = null!; + public List Reasons { get; set; } = []; + + public Dictionary Evaluated = []; +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs b/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs new file mode 100644 index 0000000..8cd50a1 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs @@ -0,0 +1,52 @@ +using LIN.Cloud.Identity.Services.Models; +using LIN.Types.Cloud.Identity.Models.Identities; +using Microsoft.Extensions.DependencyInjection; + +namespace LIN.Cloud.Identity.Services.Services; + +internal class AccountAuthenticationService(IServiceProvider provider) : IAuthenticationAccountService +{ + + AuthenticationRequest Request; + + public AccountModel? Account => Request?.Account; + + /// + /// Autenticar una cuenta de usuario. + /// + public async Task Authenticate(AuthenticationRequest request) + { + Request = request; + + using var scope = provider.CreateScope(); + var serviceProvider = scope.ServiceProvider; + + var pipelineSteps = new List + { + typeof(IIdentityValidationService), + typeof(IOrganizationValidationService), + typeof(IApplicationValidationService) + }; + + foreach (var stepType in pipelineSteps) + { + var service = (IAuthenticationService)serviceProvider.GetRequiredService(stepType); + var result = await service.Authenticate(request); + + if (result.Response != Responses.Success) + return result; + } + + return new ResponseBase(Responses.Success); + } + + public string GenerateToken() + { + + if (Request.Account is null || Request.ApplicationModel is null) + return string.Empty; + + return JwtService.Generate(Request.Account!, Request.ApplicationModel.Id); + + } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs new file mode 100644 index 0000000..4418e57 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs @@ -0,0 +1,34 @@ +using LIN.Cloud.Identity.Services.Models; + +namespace LIN.Cloud.Identity.Services.Services.Authentication; + +internal class ApplicationValidationService(IApplicationRepository applicationRepository) : IApplicationValidationService +{ + + /// + /// Valida la cuenta de usuario y la contraseña. + /// + public async Task Authenticate(AuthenticationRequest request) + { + // Obtener la aplicación. + var applicationResponse = await applicationRepository.Read(request.Application); + + if (applicationResponse.Response != Responses.Success) + return new ResponseBase(Responses.UnauthorizedByApp) + { + Message = "Application not found.", + }; + + // Validar politicas de IP. + + // Validar politicas de tiempo. + + // Validar politicas de identidad. + + // Correcto. + request.ApplicationModel = applicationResponse.Model; + return new ResponseBase(Responses.Success); + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs new file mode 100644 index 0000000..8e8e805 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs @@ -0,0 +1,53 @@ +using LIN.Cloud.Identity.Services.Models; + +namespace LIN.Cloud.Identity.Services.Services.Authentication; + +internal class IdentityValidationService(IAccountRepository accountRepository) : IIdentityValidationService +{ + + /// + /// Valida la cuenta de usuario y la contraseña. + /// + public async Task Authenticate(AuthenticationRequest request) + { + + // Obtener la cuenta. + var accountResponse = await accountRepository.Read(request.User, new() + { + IncludeIdentity = true, + FindOn = Persistence.Models.FindOn.AllAccounts + }); + + // Validar respuesta. + if (accountResponse.Response != Responses.Success) + return new ResponseBase + { + Response = Responses.NotExistAccount, + Message = "Account not found" + }; + + var account = accountResponse.Model; + + // Validar estado identidad. + if (account.Identity.Status != Types.Cloud.Identity.Enumerations.IdentityStatus.Enable) + return new ResponseBase + { + Response = Responses.NotExistAccount, + Message = "La identidad de la cuenta de usuario no se encuentra activa." + }; + + // Validar contraseña. + if (Global.Utilities.Cryptography.Encrypt(request.Password) != account.Password) + return new ResponseBase + { + Response = Responses.InvalidPassword, + Message = "La contraseña es incorrecta." + }; + + // Establecer datos en la solicitud. + request.Account = account; + + return new(Responses.Success); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/OrganizationValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/OrganizationValidationService.cs new file mode 100644 index 0000000..c7b29b5 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/Authentication/OrganizationValidationService.cs @@ -0,0 +1,26 @@ +using LIN.Cloud.Identity.Services.Models; + +namespace LIN.Cloud.Identity.Services.Services.Authentication; + +internal class OrganizationValidationService(IPolicyOrchestrator policyOrchestrator) : IOrganizationValidationService +{ + + /// + /// Valida políticas de organización. + /// + public async Task Authenticate(AuthenticationRequest request) + { + // Validar si existe una organización dueña de la cuenta. + if (request.Account!.Identity.OwnerId is null || request.Account!.Identity.OwnerId <= 0) + return new ResponseBase(Responses.Success); + + var response = await policyOrchestrator.ValidatePoliciesForOrganization(request); + + if (response.Response != Responses.Success) + return response; + + // Correcto. + return new ResponseBase(Responses.Success); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/IdentityService.cs b/LIN.Cloud.Identity.Services/Services/IdentityService.cs new file mode 100644 index 0000000..0e6fd69 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/IdentityService.cs @@ -0,0 +1,142 @@ +using LIN.Cloud.Identity.Persistence.Contexts; + +namespace LIN.Cloud.Identity.Services.Services; + +internal class IdentityService(DataContext context) : IIdentityService +{ + + /// + /// Obtener la identidades asociadas a una identidad base. + /// + /// Identidad base. + public async Task> GetIdentities(int identity) + { + List result = [identity]; + await GetIdentities(identity, result); + return result; + } + + + /// + /// Obtener la identidades asociadas a una identidad base. + /// + /// Identidad base. + public async Task>> GetLevel(int identity, int organization) + { + Dictionary> result = new() + { + { + 0, + [new() { + Identity = identity, + Level = 0 + }] + } + }; + + await GetIdentities(identity, 1, organization, [], result); + return result; + } + + + /// + /// Obtener la identidades asociadas a una identidad base. + /// + /// Identidad base. + /// Identidades encontradas. + private async Task GetIdentities(int identity, List ids) + { + // Consulta. + var query = from id in context.Identities + where id.Id == identity + select new + { + // Encontrar grupos donde la identidad pertenece. + In = (from member in context.GroupMembers + where !ids.Contains(member.Group.IdentityId) + && member.IdentityId == identity + select member.Group.IdentityId).ToList(), + }; + + // Si no hay elementos. + if (!query.Any()) + return; + + // Ejecuta la consulta. + var local = query.ToList(); + + // Obtiene las bases. + var bases = local.SelectMany(t => t.In); + + // Agregar a los objetos. + ids.AddRange(bases); + + // Recorrer. + foreach (var @base in bases) + await GetIdentities(@base, ids); + + } + + + /// + /// Obtener la identidades asociadas a una identidad base. + /// + /// Identidad base. + /// Identidades encontradas. + private async Task GetIdentities(int identity, int level, int organization, List ids, Dictionary> keys) + { + // Consulta. + var query = from id in context.Identities + where id.Id == identity + && id.OwnerId == organization + select new + { + // Encontrar grupos donde la identidad pertenece. + In = (from member in context.GroupMembers + where !ids.Contains(member.Group.IdentityId) + && member.IdentityId == identity + select member.Group.IdentityId).ToList(), + }; + + // Si no hay elementos. + if (!query.Any()) + return; + + // Ejecuta la consulta. + var local = query.ToList(); + + // Obtiene las bases. + var bases = local.SelectMany(t => t.In); + + // Agregar a los objetos. + ids.AddRange(bases.Select(t => t)); + + keys.TryGetValue(level, out var list); + + if (list == null) + keys.Add(level, bases.Select(t => new IdentityLevelModel + { + Identity = t, + Level = level + }).ToList()); + else + list.AddRange(bases.Select(t => new IdentityLevelModel + { + Identity = t, + Level = level + })); + + // Recorrer. + foreach (var @base in bases) + await GetIdentities(@base, level + 1, organization, ids, keys); + + } + +} + + +class IdentityLevelModel +{ + public int Level { get; set; } + public int Identity { get; set; } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/JwtService.cs b/LIN.Cloud.Identity.Services/Services/JwtService.cs similarity index 89% rename from LIN.Cloud.Identity/Services/Auth/JwtService.cs rename to LIN.Cloud.Identity.Services/Services/JwtService.cs index 06a43a5..277f701 100644 --- a/LIN.Cloud.Identity/Services/Auth/JwtService.cs +++ b/LIN.Cloud.Identity.Services/Services/JwtService.cs @@ -1,4 +1,12 @@ -namespace LIN.Cloud.Identity.Services.Auth; +using LIN.Cloud.Identity.Services.Models; +using LIN.Types.Cloud.Identity.Models.Identities; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace LIN.Cloud.Identity.Services.Services; public class JwtService { @@ -14,7 +22,7 @@ public class JwtService /// public static void Open(IConfiguration configuration) { - JwtKey =configuration["jwt:key"]; + JwtKey = configuration["jwt:key"]; } @@ -36,7 +44,7 @@ public static string Generate(AccountModel user, int appID) { new Claim(ClaimTypes.PrimarySid, user.Id.ToString()), new Claim(ClaimTypes.NameIdentifier, user.Identity.Unique), - new Claim(ClaimTypes.GroupSid, (user.Identity.Id).ToString() ?? ""), + new Claim(ClaimTypes.GroupSid, user.Identity.Id.ToString() ?? ""), new Claim(ClaimTypes.Authentication, appID.ToString()) }; diff --git a/LIN.Cloud.Identity.Services/Services/Policies/IdentityTypePolicyValidator.cs b/LIN.Cloud.Identity.Services/Services/Policies/IdentityTypePolicyValidator.cs new file mode 100644 index 0000000..c0bc58c --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/Policies/IdentityTypePolicyValidator.cs @@ -0,0 +1,40 @@ +using LIN.Cloud.Identity.Services.Models; +using LIN.Types.Cloud.Identity.Enumerations; +using LIN.Types.Cloud.Identity.Models; +using LIN.Types.Cloud.Identity.Models.Policies; + +namespace LIN.Cloud.Identity.Services.Services.Policies; + +internal class IdentityTypePolicyValidator +{ + + /// + /// Valida las políticas de tipo de identidad. + /// + public static bool Validate(PolicyModel policyBase, PolicyValidatorContext context, IEnumerable identityTypePolicies) + { + + // Si no hay políticas de tipo de identidad, se permite el acceso. + if (identityTypePolicies == null || !identityTypePolicies.Any()) + return true; + + // Tipo de identidad actual. + IdentityType? identityType = context.AuthenticationRequest.Account?.Identity.Type; + + bool result = false; + foreach (var e in identityTypePolicies) + if (e.Type == identityType) + { + result = true; // Se encontró una política válida + break; + } + + bool isValid = (result && policyBase.Effect == PolicyEffect.Allow) + || (!result && policyBase.Effect == PolicyEffect.Deny); + + + context.Evaluated["TIPE"] = isValid; + + return isValid; + } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs b/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs new file mode 100644 index 0000000..ec7e993 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs @@ -0,0 +1,42 @@ +using LIN.Cloud.Identity.Services.Models; +using LIN.Types.Cloud.Identity.Enumerations; +using LIN.Types.Cloud.Identity.Models; +using LIN.Types.Cloud.Identity.Models.Policies; + +namespace LIN.Cloud.Identity.Services.Services.Policies; + +internal class TimeAccessPolicyValidator +{ + + /// + /// Valida las políticas de acceso por tiempo. + /// + /// Contexto actual. + /// Lista de políticas. + public static bool Validate(PolicyModel policyBase, PolicyValidatorContext context, IEnumerable policies) + { + if (!policies.Any()) + return true; + + // Hora actual. + var now = TimeOnly.FromDateTime(DateTime.UtcNow); + + bool result = false; + foreach (var policy in policies) + { + if (now >= policy.StartHour && now <= policy.EndHour) + { + result = true; // Se encontró una política válida + break; + } + } + + bool isValid = (result && policyBase.Effect == PolicyEffect.Allow) + || (!result && policyBase.Effect == PolicyEffect.Deny); + + context.Evaluated["TIME"] = isValid; + + + return true; + } +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs b/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs new file mode 100644 index 0000000..e5dfcfe --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs @@ -0,0 +1,70 @@ +using LIN.Cloud.Identity.Services.Models; +using LIN.Cloud.Identity.Services.Services.Policies; +using LIN.Types.Cloud.Identity.Models; + +namespace LIN.Cloud.Identity.Services.Services; + +internal class PolicyOrchestrator(IPolicyRepository policyRepository, IIdentityService identityService) : IPolicyOrchestrator +{ + + /// + /// Valida las políticas de acceso para una organización. + /// + public async Task> ValidatePoliciesForOrganization(AuthenticationRequest request) + { + var context = new PolicyValidatorContext + { + AuthenticationRequest = request + }; + + int organization = request.Account!.Identity.OwnerId!.Value; + + // Obtener las identidades. + var levels = await identityService.GetLevel(request.Account!.Identity.Id, organization); + + foreach (var level in levels.OrderBy(t => t.Key)) + { + // Obtener las políticas asociadas a la identidad. + var policies = await policyRepository.ReadAll(level.Value.Select(t => t.Identity), organization, true); + + foreach (var policy in policies.Models) + { + // Si se valido correctamente la política, continuar con la siguiente. + ValidateSinglePolicyAsync(policy, context); + } + } + + if (!context.Evaluated.Select(t => t.Value).All(t => t == true)) + { + // Si no se valida correctamente, agregar el error a la lista de razones. + return new(Responses.UnauthorizedByOrg) + { + Errors = [.. context.Reasons.Select(reason => new Types.Models.ErrorModel + { + Tittle = "Acceso denegado", + Description = reason + })] + }; + } + + return new(Responses.Success); + } + + /// + /// Validar una sola política. + /// + private static bool ValidateSinglePolicyAsync(PolicyModel policy, PolicyValidatorContext context) + { + + // Validar acceso por hora. + if (context.Evaluated.ContainsKey("TIME") || !TimeAccessPolicyValidator.Validate(policy, context, policy.TimeAccessPolicies)) + return false; + + // Validar tipo de identidad + if (context.Evaluated.ContainsKey("TYPE") || !IdentityTypePolicyValidator.Validate(policy, context, policy.IdentityTypePolicies)) + return false; + + return true; + } + +} diff --git a/LIN.Cloud.Identity.Services/Services/ServiceAuthenticationService.cs b/LIN.Cloud.Identity.Services/Services/ServiceAuthenticationService.cs deleted file mode 100644 index e892523..0000000 --- a/LIN.Cloud.Identity.Services/Services/ServiceAuthenticationService.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Services; - -internal class ServiceAuthenticationService : IAuthenticationService -{ - public Task Authenticate() - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/UserAuthenticationService.cs b/LIN.Cloud.Identity.Services/Services/UserAuthenticationService.cs deleted file mode 100644 index 031a701..0000000 --- a/LIN.Cloud.Identity.Services/Services/UserAuthenticationService.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Services; - -internal class UserAuthenticationService : IAuthenticationService -{ - public Task Authenticate() - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/usings.cs b/LIN.Cloud.Identity.Services/usings.cs index ed6d181..7bb7666 100644 --- a/LIN.Cloud.Identity.Services/usings.cs +++ b/LIN.Cloud.Identity.Services/usings.cs @@ -1,2 +1,3 @@ -global using LIN.Types.Responses; -global using LIN.Cloud.Identity.Services.Interfaces; \ No newline at end of file +global using LIN.Cloud.Identity.Persistence.Repositories; +global using LIN.Cloud.Identity.Services.Interfaces; +global using LIN.Types.Responses; diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 2f320ea..426099f 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -1,4 +1,5 @@ using LIN.Cloud.Identity.Persistence.Repositories; +using LIN.Cloud.Identity.Services.Services; namespace LIN.Cloud.Identity.Areas.Accounts; @@ -65,7 +66,6 @@ public async Task Create([FromBody] AccountModel? modelo, [F Token = token, Message = "Cuenta creada satisfactoriamente." }; - } diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs index ee485ec..2c97c6d 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs @@ -5,5 +5,5 @@ public class ApplicationRestrictionsController() : AuthenticationBaseController { - + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index f6e82fa..96d29db 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,10 +1,10 @@ using LIN.Cloud.Identity.Persistence.Repositories; -using LIN.Cloud.Identity.Services.Auth.Interfaces; +using LIN.Cloud.Identity.Services.Interfaces; namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class AuthenticationController(IAuthentication authentication, IAccountRepository accountData, IPolicyRepository policyData) : AuthenticationBaseController +public class AuthenticationController(IAuthenticationAccountService serviceAuth, IAccountRepository accountData, IPolicyRepository policyData) : AuthenticationBaseController { /// @@ -26,17 +26,15 @@ public async Task> Login([FromQuery] string us }; // Establecer credenciales. - authentication.SetCredentials(user, password, application); - - // Respuesta. - var response = await authentication.Start(new() + var response = await serviceAuth.Authenticate(new() { - ValidateApp = true, - Log = true + User = user, + Password = password, + Application = application }); // Validación al obtener el usuario - switch (response) + switch (response.Response) { // Correcto case Responses.Success: @@ -63,7 +61,17 @@ public async Task> Login([FromQuery] string us return new() { Response = Responses.UnauthorizedByApp, - Message = "La aplicación no existe o no permite que inicies sesión en este momento." + Message = "La aplicación no existe o no permite que inicies sesión en este momento.", + Errors = response.Errors + }; + + // Contraseña invalida. + case Responses.UnauthorizedByOrg: + return new() + { + Response = Responses.UnauthorizedByOrg, + Message = "Tu organización no permite que inicies sesión en este momento.", + Errors = response.Errors }; // Incorrecto @@ -76,12 +84,12 @@ public async Task> Login([FromQuery] string us } // Genera el token - var token = authentication.GenerateToken(); + var token = serviceAuth.GenerateToken(); // Respuesta. var http = new ReadOneResponse { - Model = authentication.GetData(), + Model = serviceAuth.Account!, Response = Responses.Success, Token = token }; @@ -113,79 +121,4 @@ public async Task> LoginWithToken() } - - /// - /// Iniciar sesión y validar una política. - /// - /// Usuario. - /// Contraseña. - /// Id de la política. - [HttpGet("validate/policy")] - public async Task ValidatePolicy([FromQuery] string user, [FromQuery] string password, [FromHeader] int policy) - { - - // Validación de parámetros. - if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(password)) - return new(Responses.InvalidParam) - { - Message = "Uno o varios parámetros son invalido." - }; - - // Establecer credenciales. - authentication.SetCredentials(user, password, string.Empty); - - // Respuesta. - var response = await authentication.Start(new() - { - Log = false - }); - - // Validación al obtener el usuario - switch (response) - { - // Correcto - case Responses.Success: - break; - - // No existe esta cuenta. - case Responses.NotExistAccount: - return new() - { - Response = Responses.Unauthorized, - Message = "No existe esta cuenta." - }; - - // Contraseña invalida. - case Responses.InvalidPassword: - return new() - { - Response = Responses.Unauthorized, - Message = "Contraseña incorrecta." - }; - - // Incorrecto - default: - return new() - { - Response = Responses.Unauthorized, - Message = "Hubo un error grave." - }; - } - - //// Validar política. - //var isAllow = await policyData.HasFor(authentication.GetData().IdentityId, policy); - - //// Respuesta. - //var http = new ResponseBase - //{ - // Response = isAllow.Response - //}; - - //return http; - return new() - { - Response = Responses.Success - }; - } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index 7ebb395..babf904 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -3,7 +3,7 @@ namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class SecurityController(IAccountRepository accountsData,IOtpRepository otpService, EmailSender emailSender) : AuthenticationBaseController +public class SecurityController(IAccountRepository accountsData, IOtpRepository otpService, EmailSender emailSender) : AuthenticationBaseController { ///// diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index dd66dd5..fed4c24 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Policies; [IdentityToken] [Route("[controller]")] -public class PoliciesController(IPolicyRepository policiesData, IGroupRepository groups, IamRoles iam,IOrganizationRepository organizations, IamPolicy iamPolicy) : AuthenticationBaseController +public class PoliciesController(IPolicyRepository policiesData, IGroupRepository groups, IamRoles iam, IOrganizationRepository organizations, IamPolicy iamPolicy) : AuthenticationBaseController { /// diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index a8b720e..b172d77 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -17,6 +17,7 @@ + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index e472509..c0e6256 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -1,6 +1,5 @@ using Http.Extensions; using Http.Extensions.OpenApi; -using LIN.Cloud.Identity.Services.Auth.Interfaces; using LIN.Cloud.Identity.Services.Extensions; using LIN.Cloud.Identity.Services.Realtime; @@ -14,9 +13,9 @@ builder.Services.AddSignalR(); builder.Services.AddLocalServices(); +builder.Services.AddAuthenticationServices(builder.Configuration); // Servicio de autenticación. -builder.Services.AddScoped(); builder.Services.AddPersistence(builder.Configuration); //builder.Host.UseLoggingService(builder.Configuration); diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs deleted file mode 100644 index 4764e8f..0000000 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.Application.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Auth; - -public partial class Authentication -{ - - /// - /// Modelo de la aplicación. - /// - public ApplicationModel? Application { get; set; } = null; - - - /// - /// Validar aplicación. - /// - private async Task ValidateApp() - { - - // Obtener la restrictions. - var appResponse = await applications.Read(AppCode); - Application = appResponse.Model; - - // Si no se requiere validar la restrictions. - if (!Settings.ValidateApp) - return true; - - // Validar si la restrictions existe. - if (appResponse.Response != Responses.Success) - return false; - - // Respuesta. - return true; - } - - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Authentication.cs b/LIN.Cloud.Identity/Services/Auth/Authentication.cs deleted file mode 100644 index 0d46c7b..0000000 --- a/LIN.Cloud.Identity/Services/Auth/Authentication.cs +++ /dev/null @@ -1,156 +0,0 @@ -using LIN.Cloud.Identity.Persistence.Repositories; -using LIN.Cloud.Identity.Services.Auth.Interfaces; -using LIN.Cloud.Identity.Services.Auth.Models; - -namespace LIN.Cloud.Identity.Services.Auth; - -public partial class Authentication(IAccountRepository accountData, IAccountLogRepository accountLogs, IApplicationRepository applications, IIdentityService identityService, IAllowService allowService) : Interfaces.IAuthentication -{ - - /// - /// Usuario. - /// - private string User { get; set; } = string.Empty; - - - /// - /// Usuario. - /// - private string Password { get; set; } = string.Empty; - - - /// - /// Código de la aplicación. - /// - private string AppCode { get; set; } = string.Empty; - - - /// - /// Modelo obtenido. - /// - public AccountModel? Account { get; set; } = null; - - - /// - /// Ajustes. - /// - private AuthenticationSettings Settings { get; set; } = new(); - - - /// - /// Establecer credenciales. - /// - /// Usuario. - /// Contraseña. - /// Código de restrictions. - public void SetCredentials(string username, string password, string appCode) - { - this.User = username; - this.Password = password; - this.AppCode = appCode; - } - - - /// - /// Iniciar el proceso. - /// - public async Task Start(AuthenticationSettings? settings = null) - { - - // Validar. - Settings = settings ?? new(); - - // Obtener la cuenta. - var account = await GetAccount(); - - // Error. - if (!account) - return Responses.NotExistAccount; - - // Validar contraseña. - bool password = ValidatePassword(); - - if (!password) - return Responses.InvalidPassword; - - // Validar aplicación. - var valApp = await ValidateApp(); - - // Bloqueado por la aplicación. - if (!valApp) - return Responses.UnauthorizedByApp; - - if (Settings.Log) - await SaveLog(); - - return Responses.Success; - } - - - /// - /// Iniciar el proceso. - /// - private async Task GetAccount() - { - // Obtener la cuenta. - var account = await accountData.Read(User, new() - { - FindOn = FindOn.StableAccounts, - IsAdmin = true - }); - - // Establecer. - Account = account.Model; - - // Respuesta. - return account.Response == Responses.Success; - } - - - /// - /// Validar la contraseña. - /// - private bool ValidatePassword() - { - - // Validar la cuenta. - if (Account == null) - return false; - - // Validar la contraseña. - if (Global.Utilities.Cryptography.Encrypt(Password) != Account.Password) - return false; - - // Correcto. - return true; - - } - - - /// - /// Guardar log. - /// - private async Task SaveLog() - { - await accountLogs.Create(new() - { - AccountId = Account!.Id, - AuthenticationMethod = AuthenticationMethods.Password, - Time = DateTime.Now, - Application = Application - }); - } - - - /// - /// Obtener el token. - /// - public string GenerateToken() => JwtService.Generate(Account!, Application!.Id); - - - /// - /// Obtener el token. - /// - public AccountModel GetData() => Account!; - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs b/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs deleted file mode 100644 index abb168c..0000000 --- a/LIN.Cloud.Identity/Services/Auth/Interfaces/IAuthentication.cs +++ /dev/null @@ -1,34 +0,0 @@ -using LIN.Cloud.Identity.Services.Auth.Models; - -namespace LIN.Cloud.Identity.Services.Auth.Interfaces; - -public interface IAuthentication -{ - - /// - /// Iniciar el proceso. - /// - public Task Start(AuthenticationSettings? settings = null); - - - /// - /// Establecer credenciales. - /// - /// Usuario único. - /// Contraseña. - /// Código de app. - public void SetCredentials(string username, string password, string appCode); - - - /// - /// Obtener el token. - /// - public string GenerateToken(); - - - /// - /// Obtener la data. - /// - public AccountModel GetData(); - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs index 4619476..956dfc5 100644 --- a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs +++ b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs @@ -1,4 +1,5 @@ -using LIN.Types.Models; +using LIN.Cloud.Identity.Services.Services; +using LIN.Types.Models; namespace LIN.Cloud.Identity.Services.Filters; diff --git a/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs b/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs deleted file mode 100644 index 8839cac..0000000 --- a/LIN.Cloud.Identity/Services/Models/QueryIdentityFilter.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Models; - -public class QueryIdentityFilter -{ - public FindOn FindOn { get; set; } - public bool IncludeDates { get; set; } - -} - -public enum FindOnIdentities -{ - Stable, - All -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 5eedbbf..876e422 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -1,4 +1,5 @@ using LIN.Cloud.Identity.Persistence.Repositories; +using LIN.Cloud.Identity.Services.Services; namespace LIN.Cloud.Identity.Services.Realtime; diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index 9c783e8..78bac99 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -1,4 +1,6 @@ -namespace LIN.Cloud.Identity.Services.Realtime; +using LIN.Cloud.Identity.Services.Services; + +namespace LIN.Cloud.Identity.Services.Realtime; public partial class PassKeyHub { diff --git a/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs b/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs deleted file mode 100644 index c7c5763..0000000 --- a/LIN.Cloud.Identity/Services/Utils/IIdentityService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Utils; - -public interface IIdentityService -{ - Task> GetIdentities(int identity); -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs b/LIN.Cloud.Identity/Services/Utils/IdentityService.cs deleted file mode 100644 index 487ed35..0000000 --- a/LIN.Cloud.Identity/Services/Utils/IdentityService.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Utils; - -public class IdentityService(DataContext context) : IIdentityService -{ - - /// - /// Obtener la identidades asociadas a una identidad base. - /// - /// Identidad base. - public async Task> GetIdentities(int identity) - { - List result = [identity]; - await GetIdentities(identity, result); - return result; - } - - - /// - /// Obtener la identidades asociadas a una identidad base. - /// - /// Identidad base. - /// Identidades encontradas. - private async Task GetIdentities(int identity, List ids) - { - // Consulta. - var query = from id in context.Identities - where id.Id == identity - select new - { - // Encontrar grupos donde la identidad pertenece. - In = (from member in context.GroupMembers - where !ids.Contains(member.Group.IdentityId) - && member.IdentityId == identity - select member.Group.IdentityId).ToList(), - }; - - // Si no hay elementos. - if (!query.Any()) - return; - - // Ejecuta la consulta. - var local = query.ToList(); - - // Obtiene las bases. - var bases = local.SelectMany(t => t.In); - - // Agregar a los objetos. - ids.AddRange(bases); - - // Recorrer. - foreach (var @base in bases) - await GetIdentities(@base, ids); - - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs b/LIN.Cloud.Identity/Services/Utils/PolicyService.cs deleted file mode 100644 index 7c9ec84..0000000 --- a/LIN.Cloud.Identity/Services/Utils/PolicyService.cs +++ /dev/null @@ -1,11 +0,0 @@ -using LIN.Types.Models; -using Newtonsoft.Json.Linq; - -namespace LIN.Cloud.Identity.Services.Utils; - -public class PolicyService(IamPolicy iamPolicy) -{ - - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 46ef86c..b3c9a69 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -12,7 +12,6 @@ global using LIN.Types.Cloud.Identity.Enumerations; global using LIN.Types.Cloud.Identity.Models; global using LIN.Types.Cloud.Identity.Models.Identities; -global using LIN.Types.Cloud.Identity.Models.Policies; global using LIN.Types.Enumerations; // Tipos Generales global using LIN.Types.Responses; @@ -22,9 +21,4 @@ // SQL. global using Microsoft.EntityFrameworkCore; // Tipos Extras. -global using Microsoft.IdentityModel.Tokens; -global using System.IdentityModel.Tokens.Jwt; -global using System.IO; -global using System.Security.Claims; // Framework. -global using System.Text; From 0913e39cc6c05827a4d419b4beabcf0cd570c716 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sat, 17 May 2025 16:58:21 -0500 Subject: [PATCH 165/178] limpieza de codigo, y mejoras generales --- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../OrganizationMemberRepository.cs | 33 ++++ .../IOrganizationMemberRepository.cs | 8 + .../Extensions/IamExtensions.cs | 105 ++++++++++ .../Extensions/ServiceExtensions.cs | 2 + .../Interfaces/IIamService.cs | 10 + .../Models/JwtModel.cs | 6 - .../Models/PolicyValidationResult.cs | 7 - .../Models/PolicyValidatorContext.cs | 3 +- .../Services/AccountAuthenticationService.cs | 16 +- .../Services/Iam/Iam.cs | 68 +++++++ .../Services/IdentityService.cs | 7 +- .../Policies/TimeAccessPolicyValidator.cs | 2 - .../Services/PolicyOrchestrator.cs | 5 +- .../Applications/ApplicationsController.cs | 2 +- .../AuthenticationController.cs | 1 - .../Areas/Directories/DirectoryController.cs | 2 +- .../Areas/Groups/GroupsController.cs | 2 +- .../Areas/Groups/GroupsMembersController.cs | 2 +- .../Areas/Organizations/IdentityController.cs | 2 +- .../OrganizationMembersController.cs | 86 ++++---- .../Areas/Policies/PoliciesController.cs | 71 ++----- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 2 +- LIN.Cloud.Identity/Program.cs | 1 - .../Services/Auth/AllowService.cs | 26 --- .../Services/Auth/Interfaces/IAllowService.cs | 6 - .../Auth/Models/AuthenticationSettings.cs | 7 - .../Services/Extensions/LocalServices.cs | 10 +- .../Services/Formats/Identities.cs | 6 +- LIN.Cloud.Identity/Services/Iam/IamPolicy.cs | 33 +--- LIN.Cloud.Identity/Services/Iam/IamRoles.cs | 184 ------------------ LIN.Cloud.Identity/Usings.cs | 3 +- 32 files changed, 314 insertions(+), 406 deletions(-) create mode 100644 LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs create mode 100644 LIN.Cloud.Identity.Services/Interfaces/IIamService.cs delete mode 100644 LIN.Cloud.Identity.Services/Models/PolicyValidationResult.cs create mode 100644 LIN.Cloud.Identity.Services/Services/Iam/Iam.cs delete mode 100644 LIN.Cloud.Identity/Services/Auth/AllowService.cs delete mode 100644 LIN.Cloud.Identity/Services/Auth/Interfaces/IAllowService.cs delete mode 100644 LIN.Cloud.Identity/Services/Auth/Models/AuthenticationSettings.cs delete mode 100644 LIN.Cloud.Identity/Services/Iam/IamRoles.cs diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 20db7b7..1860a25 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -12,7 +12,7 @@ - + diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs index d73a913..69edabf 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs @@ -48,6 +48,39 @@ on org.DirectoryId equals gm.GroupId } + /// /// + /// Valida si una lista de identidades son miembro de una organización. + /// + /// Identidades + /// Id de la organización + /// Contexto + public async Task<(IEnumerable success, List failure)> IamIn(IEnumerable ids, int organization) + { + + try + { + + // Consulta. + var query = await (from org in context.Organizations + where org.Id == organization + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + where ids.Contains(gm.IdentityId) + select gm.IdentityId).ToListAsync(); + + // Lista. + List success = [.. query]; + List failure = [.. ids.Except(success)]; + + return (success, failure); + } + catch (Exception) + { + } + return ([], []); + } + + /// /// Expulsar identidades de la organización. /// diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs index 2421360..3b5ba0b 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs @@ -18,6 +18,14 @@ public interface IOrganizationMemberRepository Task> IamIn(int id, int organization); + /// + /// Validar si una identidad es integrante de una organización. + /// + /// Id de la identidad. + /// Id de la organización. + Task<(IEnumerable success, List failure)> IamIn(IEnumerable id, int organization); + + /// /// Expulsar a una identidad de una organización. /// diff --git a/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs new file mode 100644 index 0000000..06f8c81 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs @@ -0,0 +1,105 @@ +using LIN.Types.Cloud.Identity.Enumerations; + +namespace LIN.Cloud.Identity.Services.Extensions; + +public static class ValidateRoles +{ + + public static bool ValidateRead(this IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager, + Roles.AccountOperator, + Roles.Regular, + Roles.Viewer, + Roles.SecurityViewer + ]; + + + var sets = availed.Intersect(roles); + return sets.Any(); + } + + public static bool ValidateReadSecure(this IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager, + Roles.AccountOperator, + Roles.Regular, + Roles.SecurityViewer + ]; + + var sets = availed.Intersect(roles); + return sets.Any(); + } + + public static bool ValidateAlterPolicies(this IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager + ]; + + var sets = availed.Intersect(roles); + + return sets.Any(); + + } + + public static bool ValidateAlterMembers(this IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager, + Roles.AccountOperator + ]; + + var sets = availed.Intersect(roles); + return sets.Any(); + } + + public static bool ValidateInviteMembers(this IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager + ]; + + var sets = availed.Intersect(roles); + return sets.Any(); + } + + public static bool ValidateDelete(this IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager + ]; + + var sets = availed.Intersect(roles); + return sets.Any(); + } + + public static bool ValidateReadPolicies(this IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager, + Roles.AccountOperator, + Roles.SecurityViewer + ]; + + var sets = availed.Intersect(roles); + return sets.Any(); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs index 9088b18..0f73f6d 100644 --- a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs +++ b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs @@ -1,5 +1,6 @@ using LIN.Cloud.Identity.Services.Services; using LIN.Cloud.Identity.Services.Services.Authentication; +using LIN.Cloud.Identity.Services.Services.Iam; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -22,6 +23,7 @@ public static IServiceCollection AddAuthenticationServices(this IServiceCollecti services.AddScoped(); services.AddScoped(); + services.AddScoped(); JwtService.Open(configuration); diff --git a/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs b/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs new file mode 100644 index 0000000..880b55f --- /dev/null +++ b/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs @@ -0,0 +1,10 @@ +using LIN.Types.Cloud.Identity.Enumerations; +using LIN.Types.Enumerations; + +namespace LIN.Cloud.Identity.Services.Interfaces; + +public interface IIamService +{ + Task IamIdentity(int identity1, int identity2); + Task> Validate(int identity, int organization); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Models/JwtModel.cs b/LIN.Cloud.Identity.Services/Models/JwtModel.cs index 34a32dc..58e7ddd 100644 --- a/LIN.Cloud.Identity.Services/Models/JwtModel.cs +++ b/LIN.Cloud.Identity.Services/Models/JwtModel.cs @@ -2,34 +2,28 @@ public class JwtModel { - /// /// El token esta autenticado. /// public bool IsAuthenticated { get; set; } - /// /// Usuario. /// public string Unique { get; set; } = string.Empty; - /// /// Id de la cuenta. /// public int AccountId { get; set; } - /// /// Id de la identidad. /// public int IdentityId { get; set; } - /// /// Id de la aplicación. /// public int ApplicationId { get; set; } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Models/PolicyValidationResult.cs b/LIN.Cloud.Identity.Services/Models/PolicyValidationResult.cs deleted file mode 100644 index 841b416..0000000 --- a/LIN.Cloud.Identity.Services/Models/PolicyValidationResult.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Models; - -internal class PolicyValidationResult -{ - public bool IsValid { get; set; } = true; - public string Reason { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Models/PolicyValidatorContext.cs b/LIN.Cloud.Identity.Services/Models/PolicyValidatorContext.cs index 954cca4..622eab3 100644 --- a/LIN.Cloud.Identity.Services/Models/PolicyValidatorContext.cs +++ b/LIN.Cloud.Identity.Services/Models/PolicyValidatorContext.cs @@ -4,6 +4,5 @@ internal class PolicyValidatorContext { public AuthenticationRequest AuthenticationRequest { get; set; } = null!; public List Reasons { get; set; } = []; - - public Dictionary Evaluated = []; + public Dictionary Evaluated { get; set; } = []; } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs b/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs index 8cd50a1..a9157bb 100644 --- a/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs +++ b/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs @@ -7,10 +7,18 @@ namespace LIN.Cloud.Identity.Services.Services; internal class AccountAuthenticationService(IServiceProvider provider) : IAuthenticationAccountService { - AuthenticationRequest Request; + /// + /// Solicitud de autenticación. + /// + private AuthenticationRequest Request { get; set; } = null!; + + /// + /// Obtener la cuenta de usuario. + /// public AccountModel? Account => Request?.Account; + /// /// Autenticar una cuenta de usuario. /// @@ -40,13 +48,15 @@ public async Task Authenticate(AuthenticationRequest request) return new ResponseBase(Responses.Success); } + + /// + /// Generar token de autenticación. + /// public string GenerateToken() { - if (Request.Account is null || Request.ApplicationModel is null) return string.Empty; return JwtService.Generate(Request.Account!, Request.ApplicationModel.Id); - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs b/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs new file mode 100644 index 0000000..1b48942 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs @@ -0,0 +1,68 @@ +using LIN.Cloud.Identity.Persistence.Contexts; +using LIN.Cloud.Identity.Services.Extensions; +using LIN.Types.Cloud.Identity.Enumerations; +using LIN.Types.Enumerations; +using Microsoft.EntityFrameworkCore; + +namespace LIN.Cloud.Identity.Services.Services.Iam; + +internal class IamService(DataContext context, IGroupRepository groups, IIdentityService identityService) : IIamService +{ + + /// + /// Obtener los roles de una identidad en una organización. + /// + /// Id de la identidad. + /// Id de la organización. + public async Task> Validate(int identity, int organization) + { + + // Identidades. + var identities = await identityService.GetIdentities(identity); + + // Obtener roles. + var roles = await (from rol in context.IdentityRoles + where identities.Contains(rol.IdentityId) + && rol.OrganizationId == organization + select rol.Rol).ToListAsync(); + + return roles; + + } + + + public async Task IamIdentity(int identity1, int identity2) + { + + var organizations = await (from z in context.Groups + where z.Members.Any(x => x.IdentityId == identity1) + && z.Members.Any(x => x.IdentityId == identity2) + select z.Identity.Owner!.Id).Distinct().ToListAsync(); + + // Si es un grupo. + var organization = await groups.GetOwnerByIdentity(identity2); + + if (organization.Response == Responses.Success) + { + organizations.Add(organization.Model); + organizations = organizations.Distinct().ToList(); + } + + bool have = false; + + foreach (var e in organizations) + { + var x = await Validate(identity1, e); + + if (x.ValidateReadSecure()) + { + have = true; + break; + } + } + + + return have ? IamLevels.Privileged : IamLevels.NotAccess; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/IdentityService.cs b/LIN.Cloud.Identity.Services/Services/IdentityService.cs index 0e6fd69..c4c4d01 100644 --- a/LIN.Cloud.Identity.Services/Services/IdentityService.cs +++ b/LIN.Cloud.Identity.Services/Services/IdentityService.cs @@ -27,9 +27,10 @@ public async Task>> GetLevel(int identi { { 0, - [new() { - Identity = identity, - Level = 0 + [ + new() { + Identity = identity, + Level = 0 }] } }; diff --git a/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs b/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs index ec7e993..41b0333 100644 --- a/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs +++ b/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs @@ -35,8 +35,6 @@ public static bool Validate(PolicyModel policyBase, PolicyValidatorContext conte || (!result && policyBase.Effect == PolicyEffect.Deny); context.Evaluated["TIME"] = isValid; - - return true; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs b/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs index e5dfcfe..2719503 100644 --- a/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs +++ b/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs @@ -29,7 +29,6 @@ public async Task> ValidatePoliciesForOr foreach (var policy in policies.Models) { - // Si se valido correctamente la política, continuar con la siguiente. ValidateSinglePolicyAsync(policy, context); } } @@ -50,12 +49,12 @@ public async Task> ValidatePoliciesForOr return new(Responses.Success); } + /// /// Validar una sola política. /// private static bool ValidateSinglePolicyAsync(PolicyModel policy, PolicyValidatorContext context) { - // Validar acceso por hora. if (context.Evaluated.ContainsKey("TIME") || !TimeAccessPolicyValidator.Validate(policy, context, policy.TimeAccessPolicies)) return false; @@ -67,4 +66,4 @@ private static bool ValidateSinglePolicyAsync(PolicyModel policy, PolicyValidato return true; } -} +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs index 355b373..e1300ea 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs @@ -44,7 +44,7 @@ public async Task Create([FromBody] ApplicationModel app) // Formatear app. app.Key = Guid.NewGuid(); - app.Policies = new(); + app.Policies = []; app.Identity.Type = IdentityType.Service; app.Identity.Roles = []; diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 96d29db..73c1b9e 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,5 +1,4 @@ using LIN.Cloud.Identity.Persistence.Repositories; -using LIN.Cloud.Identity.Services.Interfaces; namespace LIN.Cloud.Identity.Areas.Authentication; diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index edefff5..b784cac 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Directories; [IdentityToken] [Route("[controller]")] -public class DirectoryController(IOrganizationMemberRepository directoryMembersData, IGroupRepository groupsData, IamRoles rolesIam) : AuthenticationBaseController +public class DirectoryController(IOrganizationMemberRepository directoryMembersData, IGroupRepository groupsData, IIamService rolesIam) : AuthenticationBaseController { /// diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index cc76f4f..1de4963 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Groups; [IdentityToken] [Route("[controller]")] -public class GroupsController(IGroupRepository groupData, IamRoles rolesIam) : AuthenticationBaseController +public class GroupsController(IGroupRepository groupData, IIamService rolesIam) : AuthenticationBaseController { /// diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index f5ef4b4..00130ca 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Groups; [IdentityToken] [Route("Groups/members")] -public class GroupsMembersController(IGroupRepository groupsData, IOrganizationMemberRepository directoryMembersData, IGroupMemberRepository groupMembers, IamRoles rolesIam) : AuthenticationBaseController +public class GroupsMembersController(IGroupRepository groupsData, IOrganizationMemberRepository directoryMembersData, IGroupMemberRepository groupMembers, IIamService rolesIam) : AuthenticationBaseController { /// diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index c87537c..f0eb2f6 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [IdentityToken] [Route("[controller]")] -public class IdentityController(IOrganizationMemberRepository directoryMembersData, IIdentityRolesRepository identityRolesData, IamRoles rolesIam) : AuthenticationBaseController +public class IdentityController(IOrganizationMemberRepository directoryMembersData, IIdentityRolesRepository identityRolesData, IIamService rolesIam) : AuthenticationBaseController { /// diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index 1fc19d8..4db0728 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -5,7 +5,7 @@ namespace LIN.Cloud.Identity.Areas.Organizations; [IdentityToken] [Route("orgs/members")] -public class OrganizationMembersController(IOrganizationRepository organizationsData, IAccountRepository accountsData, IOrganizationMemberRepository directoryMembersData, IGroupMemberRepository groupMembers, IamRoles rolesIam) : AuthenticationBaseController +public class OrganizationMembersController(IOrganizationRepository organizationsData, IAccountRepository accountsData, IOrganizationMemberRepository directoryMembersData, IGroupMemberRepository groupMembers, IIamService rolesIam) : AuthenticationBaseController { /// @@ -15,7 +15,7 @@ public class OrganizationMembersController(IOrganizationRepository organizations /// Lista de ids a agregar. /// Retorna el resultado del proceso. [HttpPost("invite")] - public async Task AddExternalMembers([FromQuery] int organization, [FromBody] List ids) + public async Task AddExternalMembers([FromQuery] int organization, [FromBody] IEnumerable ids) { // Confirmar el rol. @@ -33,35 +33,31 @@ public async Task AddExternalMembers([FromQuery] int organiz }; // Solo elementos distintos. - ids = ids.Distinct().ToList(); + ids = ids.Distinct(); - //// Valida si el usuario pertenece a la organización. - //var (existentes, noUpdated) = await directoryMembersData.IamIn(ids, organization); + // Valida si el usuario pertenece a la organización. + var (existentes, noUpdated) = await directoryMembersData.IamIn(ids, organization); - //var directoryId = await organizationsData.ReadDirectory(organization); + var directoryId = await organizationsData.ReadDirectory(organization); - //// Crear el usuario. - //var response = await groupMembers.Create(noUpdated.Select(id => new GroupMember - //{ - // Group = new() - // { - // Id = directoryId.Model, - // }, - // Identity = new() - // { - // Id = id - // }, - // Type = GroupMemberTypes.Guest - //})); + // Crear el usuario. + var response = await groupMembers.Create(noUpdated.Select(id => new GroupMember + { + Group = new() + { + Id = directoryId.Model, + }, + Identity = new() + { + Id = id + }, + Type = GroupMemberTypes.Guest + })); - //response.Message = $"Se agregaron {noUpdated.Count} integrantes como invitados y se omitieron {existentes.Count()} debido a que ya pertenecen a esta organización."; + response.Message = $"Se agregaron {noUpdated.Count} integrantes como invitados y se omitieron {existentes.Count()} debido a que ya pertenecen a esta organización."; //// Retorna el resultado - //return response; - return new() - { - }; - + return response; } @@ -165,23 +161,20 @@ public async Task>> ReadAll([FromH Response = Responses.Unauthorized }; - //// Obtiene los miembros. - //var members = await directoryMembersData.ReadAll(organization); - - //// Error al obtener los integrantes. - //if (members.Response != Responses.Success) - // return new ReadAllResponse> - // { - // Message = "No se encontró la organización.", - // Response = Responses.Unauthorized - // }; + // Obtiene los miembros. + var members = await directoryMembersData.ReadAll(organization); - //// Retorna el resultado - //return members; - return new() - { - }; + // Error al obtener los integrantes. + if (members.Response != Responses.Success) + return new ReadAllResponse> + { + Message = "No se encontró la organización.", + Response = Responses.Unauthorized + }; + // Retorna el resultado + return new(); + // return members; } @@ -212,16 +205,13 @@ public async Task Expulse([FromQuery] int organization, [FromB // Solo elementos distintos. ids = ids.Distinct(); - //// Valida si el usuario pertenece a la organización. - //var (existentes, _) = await directoryMembersData.IamIn(ids, organization); + // Valida si el usuario pertenece a la organización. + var (existentes, _) = await directoryMembersData.IamIn(ids, organization); - //var response = await directoryMembersData.Expulse(existentes, organization); + var response = await directoryMembersData.Expulse(existentes, organization); - // Retorna el resultado - //return response; - return new() - { - }; + + return response; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index fed4c24..07540e8 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -4,7 +4,7 @@ namespace LIN.Cloud.Identity.Areas.Policies; [IdentityToken] [Route("[controller]")] -public class PoliciesController(IPolicyRepository policiesData, IGroupRepository groups, IamRoles iam, IOrganizationRepository organizations, IamPolicy iamPolicy) : AuthenticationBaseController +public class PoliciesController(IPolicyRepository policiesData, IIamService iam) : AuthenticationBaseController { /// @@ -12,67 +12,28 @@ public class PoliciesController(IPolicyRepository policiesData, IGroupRepository /// /// Modelo de la identidad. [HttpPost] - public async Task Create([FromBody] PolicyModel modelo, [FromHeader] int? organization, [FromHeader] bool assign) + public async Task Create([FromBody] PolicyModel modelo, [FromHeader] int organization) { - //// Si ya tiene una identidad. - //if (modelo.OwnerIdentityId > 0 && (organization is null || organization <= 0)) - //{ - // // Obtener detalles. - // var owner = await groups.GetOwnerByIdentity(modelo.OwnerIdentityId); + // Validar nivel de acceso y roles sobre la organización. + var validate = await iam.Validate(UserInformation.IdentityId, organization); - // if (owner.Response != Responses.Success) - // return new(Responses.NotRows) { Message = $"No se encontró la organización del grupo con identidad {modelo.OwnerIdentityId}" }; + if (!validate.ValidateAlterPolicies()) + return new(Responses.Unauthorized) + { + Message = $"No tienes permisos para crear políticas a titulo de la organización #{organization}." + }; - // // Validar roles. - // var roles = await iam.Validate(UserInformation.IdentityId, owner.Model); - - // bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); - - // if (!hasPermission) - // return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{owner.Model}." }; - - //} - //else if (organization is not null && organization > 0) - //{ - // // Validar roles. - // var roles = await iam.Validate(UserInformation.IdentityId, organization.Value); - - // bool hasPermission = ValidateRoles.ValidateAlterMembers(roles); - - // if (!hasPermission) - // return new(Responses.Unauthorized) { Message = $"No tienes permisos para crear políticas a titulo de la organización #{organization}." }; - - // // - // var directoryIdentity = await organizations.ReadDirectoryIdentity(organization.Value); - // modelo.OwnerIdentityId = directoryIdentity.Model; - //} - //else - //{ - // // Establecer propietario al usuario que realiza la solicitud. - // modelo.OwnerIdentityId = UserInformation.IdentityId; - //} - - //// Formatear. - //modelo.OwnerIdentity = new() - //{ - // Id = modelo.OwnerIdentityId - //}; - - //modelo.ApplyFor = []; - //if (assign) - // modelo.ApplyFor = [new() { - // Identity = new(){ - // Id = modelo.OwnerIdentityId - // } - // }]; + // Limpiar modelo. + modelo.Owner = new() { Id = organization }; + modelo.CreatedBy = new() { Id = UserInformation.IdentityId }; + modelo.CreatedAt = DateTime.UtcNow; + modelo.Id = 0; + modelo.Name = modelo.Name.Trim(); + // Crear la politica. var response = await policiesData.Create(modelo); return response; } - - - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index b172d77..9d201b9 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index c0e6256..591bd4f 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -1,6 +1,5 @@ using Http.Extensions; using Http.Extensions.OpenApi; -using LIN.Cloud.Identity.Services.Extensions; using LIN.Cloud.Identity.Services.Realtime; var builder = WebApplication.CreateBuilder(args); diff --git a/LIN.Cloud.Identity/Services/Auth/AllowService.cs b/LIN.Cloud.Identity/Services/Auth/AllowService.cs deleted file mode 100644 index d71b636..0000000 --- a/LIN.Cloud.Identity/Services/Auth/AllowService.cs +++ /dev/null @@ -1,26 +0,0 @@ -using LIN.Cloud.Identity.Services.Auth.Interfaces; - -namespace LIN.Cloud.Identity.Services.Auth; - -public class AllowService(DataContext context) : IAllowService -{ - - /// - /// Validar si una lista de identidades puede acceder a una aplicación. - /// - /// Ids de la identidad. - /// Id de la aplicación. - public async Task IsAllow(IEnumerable identities, int appId) - { - - //// Consulta. - //var isAllow = await (from allow in context.AllowApps - // where allow.Application.ApplicationId == appId - // && identities.Contains(allow.Identity.Id) - // select allow.IsAllow).ToListAsync(); - - //return isAllow.Contains(true); - return false; - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Interfaces/IAllowService.cs b/LIN.Cloud.Identity/Services/Auth/Interfaces/IAllowService.cs deleted file mode 100644 index c1d0f34..0000000 --- a/LIN.Cloud.Identity/Services/Auth/Interfaces/IAllowService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Auth.Interfaces; - -public interface IAllowService -{ - Task IsAllow(IEnumerable identities, int appId); -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Auth/Models/AuthenticationSettings.cs b/LIN.Cloud.Identity/Services/Auth/Models/AuthenticationSettings.cs deleted file mode 100644 index 6d51bc1..0000000 --- a/LIN.Cloud.Identity/Services/Auth/Models/AuthenticationSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Auth.Models; - -public class AuthenticationSettings -{ - public bool Log { get; set; } = true; - public bool ValidateApp { get; set; } = true; -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index dba8c71..eaebfe8 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Auth.Interfaces; - -namespace LIN.Cloud.Identity.Services.Extensions; +namespace LIN.Cloud.Identity.Services.Extensions; public static class LocalServices { @@ -17,14 +15,8 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic services.AddSingleton(); // Iam. - services.AddScoped(); services.AddScoped(); - // Allow. - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - return services; } diff --git a/LIN.Cloud.Identity/Services/Formats/Identities.cs b/LIN.Cloud.Identity/Services/Formats/Identities.cs index c9499b0..637c2d2 100644 --- a/LIN.Cloud.Identity/Services/Formats/Identities.cs +++ b/LIN.Cloud.Identity/Services/Formats/Identities.cs @@ -10,9 +10,9 @@ public class Identities public static void Process(IdentityModel id) { id.Id = 0; - id.ExpirationTime = DateTime.Now.AddYears(10); - id.EffectiveTime = DateTime.Now; - id.CreationTime = DateTime.Now; + id.ExpirationTime = DateTime.UtcNow.AddYears(10); + id.EffectiveTime = DateTime.UtcNow; + id.CreationTime = DateTime.UtcNow; id.Status = IdentityStatus.Enable; id.Unique = id.Unique.Trim(); } diff --git a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs index dc667b5..1f40c37 100644 --- a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs @@ -2,7 +2,7 @@ namespace LIN.Cloud.Identity.Services.Iam; -public class IamPolicy(DataContext context, IGroupRepository groups, IamRoles rolesIam) +public class IamPolicy(DataContext context, IGroupRepository groups, IIamService rolesIam) { /// @@ -41,35 +41,4 @@ public async Task Validate(int identity, int policy) return IamLevels.NotAccess; } - - - public bool PolicyRequirement(TimeSpan start, TimeSpan end) - { - // Definir los días válidos (lunes = 1, domingo = 7) - DayOfWeek[] diasValidos = { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, - DayOfWeek.Thursday, DayOfWeek.Friday }; - - // Obtener la hora y día actuales - DateTime ahora = DateTime.Now; - TimeSpan horaActual = ahora.TimeOfDay; - DayOfWeek diaActual = ahora.DayOfWeek; - - // Verificar si el día actual está en los días válidos - bool esDiaValido = Array.Exists(diasValidos, dia => dia == diaActual); - - // Verificar si la hora actual está en el rango - bool estaEnRango = horaActual >= start && horaActual <= end; - - // Resultado final - if (esDiaValido && estaEnRango) - { - return true; - } - else - { - return false; - } - } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Iam/IamRoles.cs b/LIN.Cloud.Identity/Services/Iam/IamRoles.cs deleted file mode 100644 index 00d38a9..0000000 --- a/LIN.Cloud.Identity/Services/Iam/IamRoles.cs +++ /dev/null @@ -1,184 +0,0 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - -namespace LIN.Cloud.Identity.Services.Iam; - -public class IamRoles(DataContext context, IGroupRepository groups, IIdentityService identityService) -{ - - /// - /// Obtener los roles de una identidad en una organización. - /// - /// Id de la identidad. - /// Id de la organización. - public async Task> Validate(int identity, int organization) - { - - // Identidades. - var identities = await identityService.GetIdentities(identity); - - // Obtener roles. - var roles = await (from rol in context.IdentityRoles - where identities.Contains(rol.IdentityId) - && rol.OrganizationId == organization - select rol.Rol).ToListAsync(); - - return roles; - - } - - - - - public async Task IamIdentity(int identity1, int identity2) - { - - var organizations = await (from z in context.Groups - where z.Members.Any(x => x.IdentityId == identity1) - && z.Members.Any(x => x.IdentityId == identity2) - select z.Identity.Owner!.Id).Distinct().ToListAsync(); - - // Si es un grupo. - var organization = await groups.GetOwnerByIdentity(identity2); - - if (organization.Response == Responses.Success) - { - organizations.Add(organization.Model); - organizations = organizations.Distinct().ToList(); - } - - bool have = false; - - foreach (var e in organizations) - { - var x = await Validate(identity1, e); - - if (ValidateRoles.ValidateReadSecure(x)) - { - have = true; - break; - } - } - - - return have ? IamLevels.Privileged : IamLevels.NotAccess; - } - -} - - -public static class ValidateRoles -{ - - public static bool ValidateRead(IEnumerable roles) - { - List availed = - [ - Roles.Administrator, - Roles.Manager, - Roles.AccountOperator, - Roles.Regular, - Roles.Viewer, - Roles.SecurityViewer - ]; - - - var sets = availed.Intersect(roles); - - return sets.Any(); - - } - - public static bool ValidateReadSecure(IEnumerable roles) - { - List availed = - [ - Roles.Administrator, - Roles.Manager, - Roles.AccountOperator, - Roles.Regular, - Roles.SecurityViewer - ]; - - - var sets = availed.Intersect(roles); - - return sets.Any(); - - } - - - public static bool ValidateAlterPolicies(IEnumerable roles) - { - List availed = - [ - Roles.Administrator, - Roles.Manager - ]; - - var sets = availed.Intersect(roles); - - return sets.Any(); - - } - - - public static bool ValidateAlterMembers(IEnumerable roles) - { - List availed = - [ - Roles.Administrator, - Roles.Manager, - Roles.AccountOperator - ]; - - var sets = availed.Intersect(roles); - - return sets.Any(); - - } - - public static bool ValidateInviteMembers(IEnumerable roles) - { - List availed = - [ - Roles.Administrator, - Roles.Manager - ]; - - var sets = availed.Intersect(roles); - - return sets.Any(); - - } - - public static bool ValidateDelete(IEnumerable roles) - { - List availed = - [ - Roles.Administrator, - Roles.Manager - ]; - - var sets = availed.Intersect(roles); - - return sets.Any(); - - } - - - public static bool ValidateReadPolicies(IEnumerable roles) - { - List availed = - [ - Roles.Administrator, - Roles.Manager, - Roles.AccountOperator, - Roles.SecurityViewer, - ]; - - var sets = availed.Intersect(roles); - - return sets.Any(); - - } -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index b3c9a69..e141eea 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -4,9 +4,10 @@ global using LIN.Cloud.Identity.Persistence.Contexts; global using LIN.Cloud.Identity.Persistence.Extensions; global using LIN.Cloud.Identity.Persistence.Models; -global using LIN.Cloud.Identity.Services.Auth; +global using LIN.Cloud.Identity.Services.Extensions; global using LIN.Cloud.Identity.Services.Filters; global using LIN.Cloud.Identity.Services.Iam; +global using LIN.Cloud.Identity.Services.Interfaces; global using LIN.Cloud.Identity.Services.Models; global using LIN.Cloud.Identity.Services.Utils; global using LIN.Types.Cloud.Identity.Enumerations; From 7bb69fc76c9b5d49c9cd858bf556c15001a0afd0 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sat, 17 May 2025 17:21:34 -0500 Subject: [PATCH 166/178] Limpieza --- .../Areas/Accounts/AccountController.cs | 2 -- .../Areas/Accounts/AccountLogs.cs | 3 --- .../ApplicationRestrictionsController.cs | 9 ------- .../Applications/ApplicationsController.cs | 4 +-- .../Authentication/AllowAppsController.cs | 6 ----- .../AuthenticationController.cs | 5 +--- .../Areas/Authentication/IntentsController.cs | 1 - .../Authentication/SecurityController.cs | 4 +-- .../Areas/Directories/DirectoryController.cs | 4 +-- .../Areas/Groups/GroupsController.cs | 4 +-- .../Areas/Groups/GroupsMembersController.cs | 4 +-- .../Areas/Organizations/IdentityController.cs | 7 ++--- .../Organizations/OrganizationController.cs | 27 +++++-------------- .../OrganizationMembersController.cs | 5 +--- .../Areas/Policies/PoliciesController.cs | 6 ++--- LIN.Cloud.Identity/Services/Iam/IamPolicy.cs | 4 +-- .../Services/Realtime/PassKeyHub.cs | 3 +-- LIN.Cloud.Identity/Usings.cs | 1 + 18 files changed, 20 insertions(+), 79 deletions(-) delete mode 100644 LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs delete mode 100644 LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 426099f..28e284e 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -1,4 +1,3 @@ -using LIN.Cloud.Identity.Persistence.Repositories; using LIN.Cloud.Identity.Services.Services; namespace LIN.Cloud.Identity.Areas.Accounts; @@ -256,7 +255,6 @@ public async Task> ReadAll([FromBody] List> ReadAll(DateTime? start, DateTime? end) { - // Fechas por defecto. start ??= DateTime.MinValue; end ??= DateTime.MaxValue; diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs deleted file mode 100644 index 2c97c6d..0000000 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationRestrictionsController.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace LIN.Cloud.Identity.Areas.Applications; - -[IdentityToken] -[Route("applications/restrictions")] -public class ApplicationRestrictionsController() : AuthenticationBaseController -{ - - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs index e1300ea..e309cc8 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - -namespace LIN.Cloud.Identity.Areas.Applications; +namespace LIN.Cloud.Identity.Areas.Applications; [IdentityToken] [Route("applications")] diff --git a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs b/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs deleted file mode 100644 index 9af3481..0000000 --- a/LIN.Cloud.Identity/Areas/Authentication/AllowAppsController.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LIN.Cloud.Identity.Areas.Authentication; - -[Route("applications/allow")] -public class AllowAppsController : ControllerBase -{ -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 73c1b9e..303d5aa 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -1,9 +1,7 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class AuthenticationController(IAuthenticationAccountService serviceAuth, IAccountRepository accountData, IPolicyRepository policyData) : AuthenticationBaseController +public class AuthenticationController(IAuthenticationAccountService serviceAuth, IAccountRepository accountData) : AuthenticationBaseController { /// @@ -117,7 +115,6 @@ public async Task> LoginWithToken() response.Token = Token; return response; - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 4f5ba9e..a271022 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -1,4 +1,3 @@ -using LIN.Cloud.Identity.Persistence.Repositories; using LIN.Cloud.Identity.Services.Realtime; namespace LIN.Cloud.Identity.Areas.Authentication; diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index babf904..2b5c736 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - -namespace LIN.Cloud.Identity.Areas.Authentication; +namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] public class SecurityController(IAccountRepository accountsData, IOtpRepository otpService, EmailSender emailSender) : AuthenticationBaseController diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs index b784cac..a9daa24 100644 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - -namespace LIN.Cloud.Identity.Areas.Directories; +namespace LIN.Cloud.Identity.Areas.Directories; [IdentityToken] [Route("[controller]")] diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs index 1de4963..7f5d7c0 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsController.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - -namespace LIN.Cloud.Identity.Areas.Groups; +namespace LIN.Cloud.Identity.Areas.Groups; [IdentityToken] [Route("[controller]")] diff --git a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs index 00130ca..5583b85 100644 --- a/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Groups/GroupsMembersController.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - -namespace LIN.Cloud.Identity.Areas.Groups; +namespace LIN.Cloud.Identity.Areas.Groups; [IdentityToken] [Route("Groups/members")] diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index f0eb2f6..ffbefef 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - -namespace LIN.Cloud.Identity.Areas.Organizations; +namespace LIN.Cloud.Identity.Areas.Organizations; [IdentityToken] [Route("[controller]")] @@ -8,9 +6,8 @@ public class IdentityController(IOrganizationMemberRepository directoryMembersDa { /// - /// Crear nuevo grupo. + /// Crear nuevo rol en una identidad. /// - /// Modelo del grupo. [HttpPost] public async Task Create([FromBody] IdentityRolesModel rolModel) { diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index 60cc644..e510ded 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -1,5 +1,3 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - namespace LIN.Cloud.Identity.Areas.Organizations; [Route("[controller]")] @@ -26,12 +24,12 @@ public async Task Create([FromBody] OrganizationModel modelo { modelo.Id = 0; modelo.Name = modelo.Name.Trim(); - modelo.Creation = DateTime.Now; + modelo.Creation = DateTime.UtcNow; modelo.Directory.Members = []; modelo.Directory.Name = modelo.Directory.Name.Trim(); - modelo.Directory.Identity.EffectiveTime = DateTime.Now; - modelo.Directory.Identity.CreationTime = DateTime.Now; - modelo.Directory.Identity.EffectiveTime = DateTime.Now.AddYears(10); + modelo.Directory.Identity.EffectiveTime = DateTime.UtcNow; + modelo.Directory.Identity.CreationTime = DateTime.UtcNow; + modelo.Directory.Identity.EffectiveTime = DateTime.UtcNow.AddYears(10); modelo.Directory.Identity.Status = IdentityStatus.Enable; } @@ -43,13 +41,7 @@ public async Task Create([FromBody] OrganizationModel modelo return new(response.Response); // Retorna el resultado. - return new CreateResponse() - { - LastId = response.LastId, - Response = Responses.Success, - Message = "Success" - }; - + return new CreateResponse(Responses.Success, response.LastId); } @@ -97,12 +89,7 @@ public async Task> ReadOneByID([FromQuery }; } - return new ReadOneResponse() - { - Response = Responses.Success, - Model = response.Model - }; - + return new ReadOneResponse(Responses.Success, response.Model); } @@ -113,12 +100,10 @@ public async Task> ReadOneByID([FromQuery [IdentityToken] public async Task> ReadAll() { - // Obtiene la organización var response = await directoryMembersData.ReadAll(UserInformation.IdentityId); return response; - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index 4db0728..fef39ba 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -1,4 +1,3 @@ -using LIN.Cloud.Identity.Persistence.Repositories; using LIN.Types.Cloud.Identity.Abstracts; namespace LIN.Cloud.Identity.Areas.Organizations; @@ -17,7 +16,6 @@ public class OrganizationMembersController(IOrganizationRepository organizations [HttpPost("invite")] public async Task AddExternalMembers([FromQuery] int organization, [FromBody] IEnumerable ids) { - // Confirmar el rol. var roles = await rolesIam.Validate(UserInformation.IdentityId, organization); @@ -208,9 +206,8 @@ public async Task Expulse([FromQuery] int organization, [FromB // Valida si el usuario pertenece a la organización. var (existentes, _) = await directoryMembersData.IamIn(ids, organization); + // Expulsar a los miembros. var response = await directoryMembersData.Expulse(existentes, organization); - - return response; } diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index 07540e8..6397cca 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - -namespace LIN.Cloud.Identity.Areas.Policies; +namespace LIN.Cloud.Identity.Areas.Policies; [IdentityToken] [Route("[controller]")] @@ -31,7 +29,7 @@ public async Task Create([FromBody] PolicyModel modelo, [Fro modelo.Id = 0; modelo.Name = modelo.Name.Trim(); - // Crear la politica. + // Crear la política. var response = await policiesData.Create(modelo); return response; } diff --git a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs index 1f40c37..e6c0eae 100644 --- a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Repositories; - -namespace LIN.Cloud.Identity.Services.Iam; +namespace LIN.Cloud.Identity.Services.Iam; public class IamPolicy(DataContext context, IGroupRepository groups, IIamService rolesIam) { diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 876e422..6300b97 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -1,5 +1,4 @@ -using LIN.Cloud.Identity.Persistence.Repositories; -using LIN.Cloud.Identity.Services.Services; +using LIN.Cloud.Identity.Services.Services; namespace LIN.Cloud.Identity.Services.Realtime; diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index e141eea..25acd59 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -4,6 +4,7 @@ global using LIN.Cloud.Identity.Persistence.Contexts; global using LIN.Cloud.Identity.Persistence.Extensions; global using LIN.Cloud.Identity.Persistence.Models; +global using LIN.Cloud.Identity.Persistence.Repositories; global using LIN.Cloud.Identity.Services.Extensions; global using LIN.Cloud.Identity.Services.Filters; global using LIN.Cloud.Identity.Services.Iam; From 293b1c18c8c1e5642a364f41051130863ba6d53d Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sat, 17 May 2025 17:45:57 -0500 Subject: [PATCH 167/178] fix --- .../Formatters/Identities.cs | 6 +-- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../EntityFramework/PolicyRepository.cs | 25 +++++++++++++ .../Repositories/IPolicyRepository.cs | 1 + .../Extensions/IamExtensions.cs | 4 +- .../Interfaces/IAuthenticationService.cs | 4 +- .../Interfaces/IIamService.cs | 5 +-- .../Interfaces/IIdentityService.cs | 4 +- .../Interfaces/IPolicyOrchestrator.cs | 4 +- .../Services/AccountAuthenticationService.cs | 3 +- .../ApplicationValidationService.cs | 4 +- .../IdentityValidationService.cs | 6 +-- .../OrganizationValidationService.cs | 5 +-- .../Services/Iam/Iam.cs | 2 - .../Services/JwtService.cs | 3 +- .../Policies/IdentityTypePolicyValidator.cs | 4 +- .../Policies/TimeAccessPolicyValidator.cs | 4 +- .../Services/PolicyOrchestrator.cs | 37 ++++++++++++++++++- LIN.Cloud.Identity.Services/usings.cs | 4 ++ 19 files changed, 83 insertions(+), 44 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs b/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs index 719804f..07df5a6 100644 --- a/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs +++ b/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs @@ -10,9 +10,9 @@ public class Identities public static void Process(IdentityModel id) { id.Id = 0; - id.ExpirationTime = DateTime.Now.AddYears(10); - id.EffectiveTime = DateTime.Now; - id.CreationTime = DateTime.Now; + id.ExpirationTime = DateTime.UtcNow.AddYears(10); + id.EffectiveTime = DateTime.UtcNow; + id.CreationTime = DateTime.UtcNow; id.Status = IdentityStatus.Enable; id.Unique = id.Unique.Trim(); } diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 1860a25..5454434 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs index 21625c5..5c06397 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs @@ -149,6 +149,31 @@ public async Task> ReadAll(int organization, bool i } + /// + /// Obtener una política de acceso por organization. + /// + /// Id de la política. + public async Task> ReadAll(int organization, bool includeDetails) + { + try + { + var model = await (from p in context.Policies + where p. == organization + select p) + .IncludeIf(includeDetails, t => t.Include(t => t.TimeAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.IpAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.IdentityTypePolicies)).ToListAsync(); + + return new(Responses.Success, model); + } + catch (Exception) + { + } + return new(); + } + + + /// /// Obtener una política de acceso por organization. /// diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs index 54580cf..5660c4e 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs @@ -8,6 +8,7 @@ public interface IPolicyRepository Task Add(IdentityTypePolicy policyModel); Task> Read(int id, bool includeDetails); Task> ReadAll(int organization, bool includeDetails); + Task> ReadAllByApp(int application, bool includeDetails); Task> ReadAll(IEnumerable identity, int organization, bool includeDetails); Task Delete(int id); } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs index 06f8c81..d91ba32 100644 --- a/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs +++ b/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs @@ -1,6 +1,4 @@ -using LIN.Types.Cloud.Identity.Enumerations; - -namespace LIN.Cloud.Identity.Services.Extensions; +namespace LIN.Cloud.Identity.Services.Extensions; public static class ValidateRoles { diff --git a/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs b/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs index cd9bb8d..e0cbb98 100644 --- a/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs +++ b/LIN.Cloud.Identity.Services/Interfaces/IAuthenticationService.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; - -namespace LIN.Cloud.Identity.Services.Interfaces; +namespace LIN.Cloud.Identity.Services.Interfaces; public interface IAuthenticationService { diff --git a/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs b/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs index 880b55f..5bfe573 100644 --- a/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs +++ b/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs @@ -1,7 +1,4 @@ -using LIN.Types.Cloud.Identity.Enumerations; -using LIN.Types.Enumerations; - -namespace LIN.Cloud.Identity.Services.Interfaces; +namespace LIN.Cloud.Identity.Services.Interfaces; public interface IIamService { diff --git a/LIN.Cloud.Identity.Services/Interfaces/IIdentityService.cs b/LIN.Cloud.Identity.Services/Interfaces/IIdentityService.cs index 528e3b9..17aa3f0 100644 --- a/LIN.Cloud.Identity.Services/Interfaces/IIdentityService.cs +++ b/LIN.Cloud.Identity.Services/Interfaces/IIdentityService.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Services; - -namespace LIN.Cloud.Identity.Services.Interfaces; +namespace LIN.Cloud.Identity.Services.Interfaces; internal interface IIdentityService { diff --git a/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs b/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs index 95acdf8..f38edf8 100644 --- a/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs +++ b/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; - -namespace LIN.Cloud.Identity.Services.Interfaces; +namespace LIN.Cloud.Identity.Services.Interfaces; internal interface IPolicyOrchestrator { diff --git a/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs b/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs index a9157bb..be88a5c 100644 --- a/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs +++ b/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs @@ -1,5 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; -using LIN.Types.Cloud.Identity.Models.Identities; +using LIN.Types.Cloud.Identity.Models.Identities; using Microsoft.Extensions.DependencyInjection; namespace LIN.Cloud.Identity.Services.Services; diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs index 4418e57..7fa0b3a 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; - -namespace LIN.Cloud.Identity.Services.Services.Authentication; +namespace LIN.Cloud.Identity.Services.Services.Authentication; internal class ApplicationValidationService(IApplicationRepository applicationRepository) : IApplicationValidationService { diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs index 8e8e805..efd840b 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; - -namespace LIN.Cloud.Identity.Services.Services.Authentication; +namespace LIN.Cloud.Identity.Services.Services.Authentication; internal class IdentityValidationService(IAccountRepository accountRepository) : IIdentityValidationService { @@ -29,7 +27,7 @@ public async Task Authenticate(AuthenticationRequest request) var account = accountResponse.Model; // Validar estado identidad. - if (account.Identity.Status != Types.Cloud.Identity.Enumerations.IdentityStatus.Enable) + if (account.Identity.Status != IdentityStatus.Enable) return new ResponseBase { Response = Responses.NotExistAccount, diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/OrganizationValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/OrganizationValidationService.cs index c7b29b5..9615669 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/OrganizationValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/OrganizationValidationService.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; - -namespace LIN.Cloud.Identity.Services.Services.Authentication; +namespace LIN.Cloud.Identity.Services.Services.Authentication; internal class OrganizationValidationService(IPolicyOrchestrator policyOrchestrator) : IOrganizationValidationService { @@ -14,6 +12,7 @@ public async Task Authenticate(AuthenticationRequest request) if (request.Account!.Identity.OwnerId is null || request.Account!.Identity.OwnerId <= 0) return new ResponseBase(Responses.Success); + // Validar políticas. var response = await policyOrchestrator.ValidatePoliciesForOrganization(request); if (response.Response != Responses.Success) diff --git a/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs b/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs index 1b48942..32e4701 100644 --- a/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs +++ b/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs @@ -1,7 +1,5 @@ using LIN.Cloud.Identity.Persistence.Contexts; using LIN.Cloud.Identity.Services.Extensions; -using LIN.Types.Cloud.Identity.Enumerations; -using LIN.Types.Enumerations; using Microsoft.EntityFrameworkCore; namespace LIN.Cloud.Identity.Services.Services.Iam; diff --git a/LIN.Cloud.Identity.Services/Services/JwtService.cs b/LIN.Cloud.Identity.Services/Services/JwtService.cs index 277f701..a01840c 100644 --- a/LIN.Cloud.Identity.Services/Services/JwtService.cs +++ b/LIN.Cloud.Identity.Services/Services/JwtService.cs @@ -1,5 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; -using LIN.Types.Cloud.Identity.Models.Identities; +using LIN.Types.Cloud.Identity.Models.Identities; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; diff --git a/LIN.Cloud.Identity.Services/Services/Policies/IdentityTypePolicyValidator.cs b/LIN.Cloud.Identity.Services/Services/Policies/IdentityTypePolicyValidator.cs index c0bc58c..d587263 100644 --- a/LIN.Cloud.Identity.Services/Services/Policies/IdentityTypePolicyValidator.cs +++ b/LIN.Cloud.Identity.Services/Services/Policies/IdentityTypePolicyValidator.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; -using LIN.Types.Cloud.Identity.Enumerations; -using LIN.Types.Cloud.Identity.Models; +using LIN.Types.Cloud.Identity.Models; using LIN.Types.Cloud.Identity.Models.Policies; namespace LIN.Cloud.Identity.Services.Services.Policies; diff --git a/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs b/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs index 41b0333..fcf4c57 100644 --- a/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs +++ b/LIN.Cloud.Identity.Services/Services/Policies/TimeAccessPolicyValidator.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; -using LIN.Types.Cloud.Identity.Enumerations; -using LIN.Types.Cloud.Identity.Models; +using LIN.Types.Cloud.Identity.Models; using LIN.Types.Cloud.Identity.Models.Policies; namespace LIN.Cloud.Identity.Services.Services.Policies; diff --git a/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs b/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs index 2719503..a6cf962 100644 --- a/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs +++ b/LIN.Cloud.Identity.Services/Services/PolicyOrchestrator.cs @@ -1,5 +1,4 @@ -using LIN.Cloud.Identity.Services.Models; -using LIN.Cloud.Identity.Services.Services.Policies; +using LIN.Cloud.Identity.Services.Services.Policies; using LIN.Types.Cloud.Identity.Models; namespace LIN.Cloud.Identity.Services.Services; @@ -50,6 +49,40 @@ public async Task> ValidatePoliciesForOr } + /// + /// Valida las políticas de acceso para una organización. + /// + public async Task> ValidatePoliciesForApplication(AuthenticationRequest request, int appId) + { + var context = new PolicyValidatorContext + { + AuthenticationRequest = request + }; + + // Obtener políticas de la aplicación. + var policies = await policyRepository.ReadAllByApp(appId, true); + + foreach (var policy in policies.Models) + { + ValidateSinglePolicyAsync(policy, context); + } + + if (!context.Evaluated.Select(t => t.Value).All(t => t == true)) + { + return new(Responses.UnauthorizedByApp) + { + Errors = [.. context.Reasons.Select(reason => new Types.Models.ErrorModel + { + Tittle = "Acceso denegado", + Description = reason + })] + }; + } + + return new(Responses.Success); + } + + /// /// Validar una sola política. /// diff --git a/LIN.Cloud.Identity.Services/usings.cs b/LIN.Cloud.Identity.Services/usings.cs index 7bb7666..1fef1ba 100644 --- a/LIN.Cloud.Identity.Services/usings.cs +++ b/LIN.Cloud.Identity.Services/usings.cs @@ -1,3 +1,7 @@ global using LIN.Cloud.Identity.Persistence.Repositories; global using LIN.Cloud.Identity.Services.Interfaces; +global using LIN.Cloud.Identity.Services.Models; +global using LIN.Types.Cloud.Identity.Enumerations; +global using LIN.Types.Enumerations; global using LIN.Types.Responses; +global using LIN.Cloud.Identity.Services.Services; \ No newline at end of file From 4239e83537b9ca5dc96372b593005ce09209fa0a Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sat, 17 May 2025 20:27:54 -0500 Subject: [PATCH 168/178] Mejoras de V4 y nuevo contrador --- .../Extensions/PersistenceExtensions.cs | 1 + .../EntityFramework/PolicyMemberRepository.cs | 31 +++++++++++++ .../EntityFramework/PolicyRepository.cs | 18 ++++---- .../Repositories/IPolicyMemberRepository.cs | 6 +++ .../Extensions/ServiceExtensions.cs | 2 +- .../Interfaces/IIamService.cs | 3 +- .../Interfaces/IPolicyOrchestrator.cs | 1 + .../ApplicationValidationService.cs | 10 ++-- .../Services/Iam/Iam.cs | 43 +++++++++++++---- .../Areas/Authentication/IntentsController.cs | 2 +- .../Areas/Policies/PoliciesController.cs | 46 +++++++++++++++++++ .../Policies/PoliciesIdentityController.cs | 34 ++++++++++++++ LIN.Cloud.Identity/Services/Iam/IamPolicy.cs | 10 ---- .../Services/Realtime/PassKeyHub.cs | 10 ++-- .../Services/Realtime/PassKeyHubActions.cs | 4 +- 15 files changed, 180 insertions(+), 41 deletions(-) create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IPolicyMemberRepository.cs create mode 100644 LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index 16e75ab..ece85e6 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -46,6 +46,7 @@ public static IServiceCollection AddPersistence(this IServiceCollection services services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs new file mode 100644 index 0000000..8de7a30 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs @@ -0,0 +1,31 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; + +internal class PolicyMemberRepository(DataContext context) : IPolicyMemberRepository +{ + + /// + /// Crear nueva política de acceso general. + /// + public async Task Create(IdentityPolicyModel model) + { + try + { + model.Identity = context.AttachOrUpdate(model.Identity)!; + model.Policy = context.AttachOrUpdate(model.Policy)!; + + // Guardar la cuenta. + await context.IdentityPolicies.AddAsync(model); + context.SaveChanges(); + + return new() + { + Response = Responses.Success + }; + } + catch (Exception) + { + } + return new(); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs index 5c06397..53cb6db 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs @@ -125,6 +125,7 @@ public async Task Delete(int id) return new(); } + /// /// Obtener una política de acceso por organization. /// @@ -153,16 +154,18 @@ public async Task> ReadAll(int organization, bool i /// Obtener una política de acceso por organization. /// /// Id de la política. - public async Task> ReadAll(int organization, bool includeDetails) + public async Task> ReadAllByApp(int application, bool includeDetails) { try { - var model = await (from p in context.Policies - where p. == organization - select p) - .IncludeIf(includeDetails, t => t.Include(t => t.TimeAccessPolicies)) - .IncludeIf(includeDetails, t => t.Include(t => t.IpAccessPolicies)) - .IncludeIf(includeDetails, t => t.Include(t => t.IdentityTypePolicies)).ToListAsync(); + + var model = await context.Applications + .Where(p => p.Id == application) + .SelectMany(p => p.Policies) + .IncludeIf(includeDetails, t => t.Include(t => t.TimeAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.IpAccessPolicies)) + .IncludeIf(includeDetails, t => t.Include(t => t.IdentityTypePolicies)) + .ToListAsync(); return new(Responses.Success, model); } @@ -173,7 +176,6 @@ public async Task> ReadAll(int organization, bool i } - /// /// Obtener una política de acceso por organization. /// diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyMemberRepository.cs new file mode 100644 index 0000000..6cbb269 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyMemberRepository.cs @@ -0,0 +1,6 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IPolicyMemberRepository +{ + Task Create(IdentityPolicyModel policyModel); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs index 0f73f6d..9c95e82 100644 --- a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs +++ b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs @@ -19,7 +19,7 @@ public static IServiceCollection AddAuthenticationServices(this IServiceCollecti services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs b/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs index 5bfe573..7d66d28 100644 --- a/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs +++ b/LIN.Cloud.Identity.Services/Interfaces/IIamService.cs @@ -2,6 +2,7 @@ public interface IIamService { - Task IamIdentity(int identity1, int identity2); + Task> IamIdentity(int identity1, int identity2); Task> Validate(int identity, int organization); + Task IamPolicy(int identity, int policy); } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs b/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs index f38edf8..5313590 100644 --- a/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs +++ b/LIN.Cloud.Identity.Services/Interfaces/IPolicyOrchestrator.cs @@ -3,4 +3,5 @@ internal interface IPolicyOrchestrator { Task> ValidatePoliciesForOrganization(AuthenticationRequest request); + Task> ValidatePoliciesForApplication(AuthenticationRequest request, int appId); } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs index 7fa0b3a..86d1d58 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs @@ -1,6 +1,6 @@ namespace LIN.Cloud.Identity.Services.Services.Authentication; -internal class ApplicationValidationService(IApplicationRepository applicationRepository) : IApplicationValidationService +internal class ApplicationValidationService(IApplicationRepository applicationRepository, IPolicyOrchestrator policyOrchestrator) : IApplicationValidationService { /// @@ -17,11 +17,11 @@ public async Task Authenticate(AuthenticationRequest request) Message = "Application not found.", }; - // Validar politicas de IP. + // Validar políticas. + var response = await policyOrchestrator.ValidatePoliciesForApplication(request, applicationResponse.Model.Id); - // Validar politicas de tiempo. - - // Validar politicas de identidad. + if (response.Response != Responses.Success) + return response; // Correcto. request.ApplicationModel = applicationResponse.Model; diff --git a/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs b/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs index 32e4701..34b7b8a 100644 --- a/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs +++ b/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs @@ -29,7 +29,10 @@ where identities.Contains(rol.IdentityId) } - public async Task IamIdentity(int identity1, int identity2) + /// + /// Validar el nivel de acceso de una identidad sobre otra identidad. + /// + public async Task> IamIdentity(int identity1, int identity2) { var organizations = await (from z in context.Groups @@ -48,19 +51,43 @@ where z.Members.Any(x => x.IdentityId == identity1) bool have = false; + List roles = new(); + foreach (var e in organizations) { var x = await Validate(identity1, e); - - if (x.ValidateReadSecure()) - { - have = true; - break; - } + roles.AddRange(x); } - return have ? IamLevels.Privileged : IamLevels.NotAccess; + return roles; + } + + + /// + /// Validar el nivel de acceso a una política. + /// + /// Id de la identidad. + /// Id de la política. + public async Task IamPolicy(int identity, int policy) + { + + // Obtener la identidad del dueño de la política. + var ownerPolicy = await (from pol in context.Policies + where pol.Id == policy + select pol.Owner!.Directory.IdentityId).FirstOrDefaultAsync(); + + // Obtener la organización. + var organizationId = await groups.GetOwnerByIdentity(ownerPolicy); + + // Obtener roles de la identidad sobre la organización. + var roles = await Validate(identity, organizationId.Model); + + // Tiene permisos para modificar la política. + if (ValidateRoles.ValidateAlterPolicies(roles)) + return IamLevels.Privileged; + + return IamLevels.NotAccess; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index a271022..0b836b9 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -26,7 +26,7 @@ where a.Key.Equals(UserInformation.Unique, StringComparison.CurrentCultureIgnore // Intentos. var intentos = (from I in account where I.Status == PassKeyStatus.Undefined - where I.Expiración > timeNow + where I.Expiration > timeNow select I).ToList(); // Retorna. diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index 6397cca..fccd665 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -34,4 +34,50 @@ public async Task Create([FromBody] PolicyModel modelo, [Fro return response; } + + /// + /// Obtener una política. + /// + /// Id. + [HttpGet] + public async Task> Read([FromHeader] int policyId) + { + + // Validar nivel de acceso y roles sobre la organización. + var validate = await iam.IamPolicy(UserInformation.IdentityId, policyId); + + if (validate != IamLevels.Privileged) + return new(Responses.Unauthorized) + { + Message = $"No tienes permisos para obtener esta política." + }; + + // Crear la política. + var response = await policiesData.Read(policyId, true); + return response; + } + + + /// + /// Obtener las políticas asociadas a una organización. + /// + /// Id de la organización. + [HttpGet("all")] + public async Task> ReadAll([FromHeader] int organization) + { + + // Validar nivel de acceso y roles sobre la organización. + var validate = await iam.Validate(UserInformation.IdentityId, organization); + + if (!validate.ValidateReadPolicies()) + return new(Responses.Unauthorized) + { + Message = $"No tienes permisos para obtener políticas a titulo de la organización #{organization}." + }; + + // Crear la política. + var response = await policiesData.ReadAll(organization, false); + return response; + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs new file mode 100644 index 0000000..d64db51 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs @@ -0,0 +1,34 @@ +namespace LIN.Cloud.Identity.Areas.Policies; + +[IdentityToken] +[Route("[controller]")] +public class PoliciesIdentityController(IPolicyMemberRepository policiesData, IIamService iam) : AuthenticationBaseController +{ + + /// + /// Crear nueva política. + /// + /// Modelo de la identidad. + [HttpPost] + public async Task Create([FromBody] IdentityPolicyModel modelo) + { + + // Validar nivel de acceso y roles sobre la organización. + var validate = await iam.IamIdentity(UserInformation.IdentityId, modelo.IdentityId); + + if (!validate.ValidateAlterPolicies()) + return new(Responses.Unauthorized) + { + Message = $"No tienes permisos modificar la identidad y agregarla a una política." + }; + + // Ajustar modelo. + modelo.Policy = new() { Id = modelo.PolicyId }; + modelo.Identity = new() { Id = modelo.IdentityId }; + + // Crear la política. + var response = await policiesData.Create(modelo); + return response; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs index e6c0eae..ec52dfd 100644 --- a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs +++ b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs @@ -11,16 +11,6 @@ public class IamPolicy(DataContext context, IGroupRepository groups, IIamService public async Task Validate(int identity, int policy) { - // Si la identidad es la administradora de la política. - var isOwner = await (from pol in context.Policies - where pol.Owner.Directory.Identity.Id == identity - && pol.Id == policy - select pol).AnyAsync(); - - // Es el creador. - if (isOwner) - return IamLevels.Privileged; - // Obtener la identidad del dueño de la política. var ownerPolicy = await (from pol in context.Policies where pol.Id == policy diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 6300b97..7873661 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -57,8 +57,8 @@ public async Task SendRequest(PassKeyModel modelo) var pass = new PassKeyModel() { - Expiración = modelo.Expiración, - Hora = modelo.Hora, + Expiration = modelo.Expiration, + Time = modelo.Time, Status = modelo.Status, User = modelo.User, HubKey = modelo.HubKey @@ -107,7 +107,7 @@ public async Task ReceiveRequest(PassKeyModel modelo) attempt.Status = modelo.Status; // Si el tiempo de expiración ya paso - if (DateTime.Now > modelo.Expiración) + if (DateTime.Now > modelo.Expiration) { attempt.Status = PassKeyStatus.Expired; attempt.Token = string.Empty; @@ -132,11 +132,11 @@ public async Task ReceiveRequest(PassKeyModel modelo) // Respuesta passkey. var responsePasskey = new PassKeyModel() { - Expiración = modelo.Expiración, + Expiration = modelo.Expiration, Status = attempt.Status, User = attempt.User, Token = attempt.Token, - Hora = DateTime.Now, + Time = DateTime.Now, HubKey = string.Empty, Key = string.Empty }; diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index 78bac99..4bb476c 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -51,8 +51,8 @@ public async Task JoinIntent(PassKeyModel attempt) // Caducidad el modelo attempt.HubKey = Context.ConnectionId; attempt.Status = PassKeyStatus.Undefined; - attempt.Hora = DateTime.Now; - attempt.Expiración = expiración; + attempt.Time = DateTime.Now; + attempt.Expiration = expiración; // Agrega el modelo if (!Attempts.ContainsKey(attempt.User.ToLower())) From 3e72c080244f7b98de8d00c8175e651bcede9c5e Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sun, 18 May 2025 10:48:51 -0500 Subject: [PATCH 169/178] mejoras generales --- .../EntityFramework/AccountRepository.cs | 16 +--- .../OrganizationMemberRepository.cs | 74 ++++++++++++++- .../EntityFramework/PolicyMemberRepository.cs | 22 +++++ .../EntityFramework/PolicyRepository.cs | 22 +++++ .../IOrganizationMemberRepository.cs | 19 +++- .../Repositories/IPolicyMemberRepository.cs | 1 + .../Repositories/IPolicyRepository.cs | 1 + .../Areas/Directories/DirectoryController.cs | 93 ------------------- .../Organizations/OrganizationController.cs | 2 +- .../OrganizationMembersController.cs | 7 +- .../Areas/Policies/PoliciesController.cs | 22 +++++ .../Policies/PoliciesIdentityController.cs | 22 +++++ 12 files changed, 187 insertions(+), 114 deletions(-) delete mode 100644 LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs index 65195d2..9333e18 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountRepository.cs @@ -1,6 +1,6 @@ namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; -internal class AccountRepository(DataContext context, Queries.AccountFindable accountFindable) : IAccountRepository +internal class AccountRepository(DataContext context, Queries.AccountFindable accountFindable, IGroupMemberRepository groupMemberRepository) : IAccountRepository { /// @@ -48,17 +48,11 @@ public async Task> Create(AccountModel modelo, int Type = GroupMemberTypes.User }; - modelo.Identity.Owner = new() - { - Id = organization - }; - - modelo.Identity.Owner = context.AttachOrUpdate(modelo.Identity.Owner)!; + // Actualizar la identidad. + await context.Identities.Where(t => t.Id == modelo.Identity.Id) + .ExecuteUpdateAsync(Identities => Identities.SetProperty(t => t.OwnerId, organization)); - // El grupo existe. - groupMember.Group = context.AttachOrUpdate(groupMember.Group)!; - context.GroupMembers.Add(groupMember); - context.SaveChanges(); + await groupMemberRepository.Create([groupMember]); } // Confirmar los cambios. diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs index 69edabf..1a6a40f 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs @@ -1,4 +1,6 @@ -namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; +using LIN.Types.Cloud.Identity.Abstracts; + +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; internal class OrganizationMemberRepository(DataContext context) : IOrganizationMemberRepository { @@ -126,7 +128,30 @@ where ids.Contains(rol.IdentityId) /// Obtener los integrantes de una organización. /// /// Id de la organización. - public async Task> ReadAll(int id) + public async Task> ReadAll(int id) + { + try + { + // Consulta. + var query = await (from gm in context.GroupMembers + where gm.Group.Identity.OwnerId == id + select gm).ToListAsync(); + + // Success. + return new(Responses.Success, query); + } + catch (Exception) + { + return new(); + } + } + + + /// + /// Obtener las organizaciones donde una identidad es integrante + /// + /// Id de la identidad. + public async Task> ReadAllMembers(int identity) { try { @@ -134,7 +159,7 @@ public async Task> ReadAll(int id) var query = await (from org in context.Organizations join gm in context.GroupMembers on org.DirectoryId equals gm.GroupId - where gm.IdentityId == id + where gm.IdentityId == identity select org).ToListAsync(); // Success. @@ -146,4 +171,47 @@ on org.DirectoryId equals gm.GroupId } } + + /// + /// Obtener las cuentas de usuarios de una organización. + /// + /// Id de la organización. + public async Task>> ReadUserAccounts(int id) + { + try + { + var members = await (from org in context.Organizations + where org.Id == id + join gm in context.GroupMembers + on org.DirectoryId equals gm.GroupId + join a in context.Accounts + on gm.IdentityId equals a.IdentityId + select new SessionModel + { + Account = new() + { + Id = a.Id, + Name = a.Name, + Visibility = a.Visibility, + IdentityService = a.IdentityService, + Identity = new() + { + Id = a.Identity.Id, + Unique = a.Identity.Unique + } + }, + Profile = gm + }).ToListAsync(); + + // Success. + return new(Responses.Success, members); + } + catch (Exception) + { + } + + return new(); + + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs index 8de7a30..39fbf50 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs @@ -28,4 +28,26 @@ public async Task Create(IdentityPolicyModel model) return new(); } + + public async Task> ReadAll(int id) + { + try + { + + var identities = await(from pl in context.IdentityPolicies + where pl.IdentityId == id + select pl.Policy).ToListAsync(); + + return new() + { + Response = Responses.Success, + Models = identities + }; + } + catch (Exception) + { + } + return new(); + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs index 53cb6db..5a81c20 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyRepository.cs @@ -150,6 +150,28 @@ public async Task> ReadAll(int organization, bool i } + /// + /// Buscar políticas por nombre. + /// + /// Id de la política. + public async Task> ReadAll(int organization, string query) + { + try + { + var model = await (from p in context.Policies + where p.OwnerId == organization + && (p.Name.Contains(query) || string.IsNullOrWhiteSpace(p.Name)) + select p).ToListAsync(); + + return new(Responses.Success, model); + } + catch (Exception) + { + } + return new(); + } + + /// /// Obtener una política de acceso por organization. /// diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs index 3b5ba0b..2c3f80e 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/IOrganizationMemberRepository.cs @@ -1,4 +1,6 @@ -namespace LIN.Cloud.Identity.Persistence.Repositories; +using LIN.Types.Cloud.Identity.Abstracts; + +namespace LIN.Cloud.Identity.Persistence.Repositories; public interface IOrganizationMemberRepository { @@ -7,7 +9,7 @@ public interface IOrganizationMemberRepository /// Obtener organizaciones donde una identidad es integrante. /// /// Id de la identidad. - Task> ReadAll(int id); + Task> ReadAll(int id); /// @@ -33,4 +35,17 @@ public interface IOrganizationMemberRepository /// Id de la organización. Task Expulse(IEnumerable ids, int organization); + + /// + /// Obtener las organizaciones donde una identidad es integrante. + /// + Task> ReadAllMembers(int identity); + + + /// + /// Obtener las cuentas de usuario de una organización. + /// + /// Id de la organización. + Task>> ReadUserAccounts(int id); + } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyMemberRepository.cs index 6cbb269..7b07777 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyMemberRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyMemberRepository.cs @@ -3,4 +3,5 @@ public interface IPolicyMemberRepository { Task Create(IdentityPolicyModel policyModel); + Task> ReadAll(int identity); } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs index 5660c4e..3483b7b 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/IPolicyRepository.cs @@ -8,6 +8,7 @@ public interface IPolicyRepository Task Add(IdentityTypePolicy policyModel); Task> Read(int id, bool includeDetails); Task> ReadAll(int organization, bool includeDetails); + Task> ReadAll(int organization, string query); Task> ReadAllByApp(int application, bool includeDetails); Task> ReadAll(IEnumerable identity, int organization, bool includeDetails); Task Delete(int id); diff --git a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs b/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs deleted file mode 100644 index a9daa24..0000000 --- a/LIN.Cloud.Identity/Areas/Directories/DirectoryController.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace LIN.Cloud.Identity.Areas.Directories; - -[IdentityToken] -[Route("[controller]")] -public class DirectoryController(IOrganizationMemberRepository directoryMembersData, IGroupRepository groupsData, IIamService rolesIam) : AuthenticationBaseController -{ - - /// - /// Obtener los integrantes del directorio general de la organización. - /// - /// Id de la organización. - /// Retorna la lista de integrantes./returns> - [HttpGet("read/all")] - public async Task> ReadAll([FromHeader] int organization) - { - // Validar organización. - if (organization <= 0) - return new() - { - Response = Responses.InvalidParam, - Message = "Id de la organización invalido." - }; - - // Obtiene el usuario. - var response = await directoryMembersData.ReadAll( - organization); - - // Si es erróneo. - if (response.Response != Responses.Success) - return new() - { - Response = response.Response - }; - - // Retorna el resultado. - return response; - } - - - /// - /// Obtener los integrantes de un grupo. - /// - /// Id del directorio. - /// Retorna la lista de integrantes del directorio. - [HttpGet("read/members")] - public async Task> ReadMembers([FromQuery] int directory) - { - - // Obtener la organización. - var orgId = await groupsData.GetOwner(directory); - - // Si hubo un error. - if (orgId.Response != Responses.Success) - return new() - { - Message = "Hubo un error al encontrar la organización dueña de este grupo.", - Response = Responses.Unauthorized - }; - - // Confirmar el rol. - var roles = await rolesIam.Validate(UserInformation.IdentityId, orgId.Model); - - // Iam. - bool iam = ValidateRoles.ValidateRead(roles); - - // Si no tiene permisos. - if (!iam) - return new() - { - Message = "No tienes acceso a la información este directorio.", - Response = Responses.Unauthorized - }; - - //// Obtiene el usuario. - //var response = await directoryMembersData.ReadMembers(directory); - - //// Si es erróneo - //if (response.Response != Responses.Success) - // return new() - // { - // Response = response.Response - // }; - - //// Retorna el resultado - //return response; - return new() - { - Response = Responses.Success - }; - - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs index e510ded..90dc81f 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationController.cs @@ -101,7 +101,7 @@ public async Task> ReadOneByID([FromQuery public async Task> ReadAll() { // Obtiene la organización - var response = await directoryMembersData.ReadAll(UserInformation.IdentityId); + var response = await directoryMembersData.ReadAllMembers(UserInformation.IdentityId); return response; } diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index fef39ba..dcf96d8 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -141,7 +141,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr /// /// Obtiene la lista de integrantes asociados a una organización. /// - [HttpGet] + [HttpGet("accounts")] public async Task>> ReadAll([FromHeader] int organization) { @@ -160,7 +160,7 @@ public async Task>> ReadAll([FromH }; // Obtiene los miembros. - var members = await directoryMembersData.ReadAll(organization); + var members = await directoryMembersData.ReadUserAccounts(organization); // Error al obtener los integrantes. if (members.Response != Responses.Success) @@ -171,8 +171,7 @@ public async Task>> ReadAll([FromH }; // Retorna el resultado - return new(); - // return members; + return members; } diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index fccd665..bde4b86 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -58,6 +58,28 @@ public async Task> Read([FromHeader] int policy } + /// + /// Buscar políticas por nombre. + /// + [HttpGet("search")] + public async Task> Search([FromQuery] string query, [FromHeader] int organization) + { + + // Validar nivel de acceso y roles sobre la organización. + var validate = await iam.Validate(UserInformation.IdentityId, organization); + + if (!validate.ValidateReadPolicies()) + return new(Responses.Unauthorized) + { + Message = $"No tienes permisos para obtener políticas a titulo de la organización #{organization}." + }; + + // Crear la política. + var response = await policiesData.ReadAll(organization, query); + return response; + } + + /// /// Obtener las políticas asociadas a una organización. /// diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs index d64db51..c6c2719 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs @@ -31,4 +31,26 @@ public async Task Create([FromBody] IdentityPolicyModel mode return response; } + + /// + /// Obtener las políticas asociadas a una identidad. + /// + [HttpGet("all")] + public async Task> ReadAll([FromHeader] int identity) + { + + // Validar nivel de acceso y roles sobre la organización. + var validate = await iam.IamIdentity(UserInformation.IdentityId, identity); + + if (!validate.ValidateReadPolicies()) + return new(Responses.Unauthorized) + { + Message = $"No tienes permisos para obtener políticas a titulo de la organización." + }; + + // Crear la política. + var response = await policiesData.ReadAll(identity); + return response; + } + } \ No newline at end of file From 2eb7e99f40dcde97c35211841c69a3127b3b2f8a Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sun, 18 May 2025 20:48:03 -0500 Subject: [PATCH 170/178] Mejoras en servicios, mails y seguridad --- .../Extensions/PersistenceExtensions.cs | 5 +- .../Formatters/Account.cs | 7 +- .../EntityFramework/AccountLogRepository.cs | 2 +- .../EntityFramework/ApplicationRepository.cs | 30 ++ .../EntityFramework/Builders/Account.cs | 4 +- .../EntityFramework/MailRepository.cs | 100 ++++++ .../EntityFramework/OtpRepository.cs | 2 +- .../EntityFramework/PolicyMemberRepository.cs | 6 +- .../Repositories/IApplicationRepository.cs | 5 +- .../Repositories/IMailRepository.cs | 8 + .../Extensions/ServiceExtensions.cs | 4 +- .../Models/AuthenticationRequest.cs | 1 + .../ApplicationValidationService.cs | 11 +- .../Services/JwtApplicationsService.cs | 116 +++++++ .../Services/JwtService.cs | 2 +- LIN.Cloud.Identity.Services/usings.cs | 2 +- .../Applications/ApplicationsController.cs | 47 ++- .../AuthenticationV4Controller.cs | 108 +++++++ .../Areas/Authentication/IntentsController.cs | 2 +- .../Authentication/SecurityController.cs | 304 +++++++++--------- .../OrganizationMembersController.cs | 2 +- .../Policies/PoliciesIdentityController.cs | 2 +- LIN.Cloud.Identity/Program.cs | 1 - .../Services/Extensions/LocalServices.cs | 3 - .../Services/Formats/Account.cs | 6 +- LIN.Cloud.Identity/Services/Iam/IamPolicy.cs | 32 -- .../Services/Realtime/PassKeyHub.cs | 6 +- .../Services/Realtime/PassKeyHubActions.cs | 4 +- LIN.Cloud.Identity/Usings.cs | 5 +- 29 files changed, 596 insertions(+), 231 deletions(-) create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/MailRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IMailRepository.cs create mode 100644 LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs create mode 100644 LIN.Cloud.Identity/Areas/Authentication/AuthenticationV4Controller.cs delete mode 100644 LIN.Cloud.Identity/Services/Iam/IamPolicy.cs diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index ece85e6..e3b211b 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -47,6 +47,7 @@ public static IServiceCollection AddPersistence(this IServiceCollection services services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } @@ -57,16 +58,12 @@ public static IServiceCollection AddPersistence(this IServiceCollection services /// public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) { - var scope = app.ApplicationServices.CreateScope(); var logger = scope.ServiceProvider.GetService>(); try { - var context = scope.ServiceProvider.GetService(); bool? created = context?.Database.EnsureCreated(); - - // Data seed. context?.Seed(); } catch (Exception ex) diff --git a/LIN.Cloud.Identity.Persistence/Formatters/Account.cs b/LIN.Cloud.Identity.Persistence/Formatters/Account.cs index dc9ef9a..a0f4241 100644 --- a/LIN.Cloud.Identity.Persistence/Formatters/Account.cs +++ b/LIN.Cloud.Identity.Persistence/Formatters/Account.cs @@ -74,14 +74,13 @@ public static AccountModel Process(AccountModel baseAccount) Id = 0, Status = IdentityStatus.Enable, Type = IdentityType.Account, - CreationTime = DateTime.Now, - EffectiveTime = DateTime.Now, - ExpirationTime = DateTime.Now.AddYears(5), + CreationTime = DateTime.UtcNow, + EffectiveTime = DateTime.UtcNow, + ExpirationTime = DateTime.UtcNow.AddYears(5), Roles = [], Unique = baseAccount.Identity.Unique.Trim() } }; - } diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs index cc12a19..05c35c3 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs @@ -85,7 +85,7 @@ public async Task> Count(int id) try { // Tiempo. - var time = DateTime.Now; + var time = DateTime.UtcNow; // Contar. int count = await (from a in context.AccountLogs diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/ApplicationRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/ApplicationRepository.cs index e27cdaa..c8b0ffe 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/ApplicationRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/ApplicationRepository.cs @@ -55,6 +55,36 @@ public async Task> Read(string key) } + /// + /// Obtener una aplicación. + /// + /// Key de la app. + public async Task> Read(int id) + { + try + { + + // Obtener el modelo. + var application = await (from ar in context.Applications + where ar.Id == id + select new ApplicationModel + { + Id = ar.Id, + Name = ar.Name, + Identity = ar.Identity + }).FirstOrDefaultAsync(); + + // Success. + return new(application is null ? Responses.NotRows : Responses.Success, application!); + + } + catch (Exception) + { + return new(Responses.Undefined); + } + } + + /// /// Validar si existe una app. /// diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs index 15be811..2e5931a 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs @@ -12,7 +12,7 @@ public static IQueryable OnStable(DataContext context) { // Hora actual. - var now = DateTime.Now; + var now = DateTime.UtcNow; // Consulta. var query = from account in context.Accounts @@ -34,7 +34,7 @@ public static IQueryable OnAll(DataContext context) { // Hora actual. - var now = DateTime.Now; + var now = DateTime.UtcNow; // Consulta. var query = from account in context.Accounts diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/MailRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/MailRepository.cs new file mode 100644 index 0000000..44797fd --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/MailRepository.cs @@ -0,0 +1,100 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; + +internal class MailRepository(DataContext context) : IMailRepository +{ + + /// + /// Crear un nuevo mail. + /// + public async Task> Create(MailModel model) + { + try + { + + model.Id = 0; + model.Account = new() + { + Id = model.AccountId, + }; + + // Attach. + context.Attach(model.Account); + + // Guardar la cuenta. + await context.Mails.AddAsync(model); + context.SaveChanges(); + + return new(Responses.Success, model); + } + catch (Exception) + { + return new(Responses.ResourceExist); + } + } + + + /// + /// Obtener el correo principal de una identidad. + /// + public async Task> ReadPrincipal(string unique) + { + try + { + + var mailModel = await (from mail in context.Mails + join account in context.Accounts + on mail.Account.IdentityId equals account.IdentityId + where account.Identity.Unique == unique + where mail.IsPrincipal + && mail.IsVerified + select mail).FirstOrDefaultAsync(); + + if (mailModel is null) + return new(Responses.NotRows); + + return new(Responses.Success, mailModel); + } + catch (Exception) + { + return new(); + } + + } + + + /// + /// Validar un código OTP para un correo. + /// + /// Correo electrónico. + /// Código OTP. + public async Task ValidateOtpForMail(string email, string code) + { + try + { + // Obtener model. + var otpModel = (from mail in context.Mails + where mail.Mail == email + join otp in context.MailOtp + on mail.Id equals otp.MailId + where otp.OtpDatabaseModel.Code == code + && otp.OtpDatabaseModel.IsUsed == false + && otp.OtpDatabaseModel.ExpireTime > DateTime.UtcNow + select otp); + + // Actualizar. + await otpModel.Select(t => t.MailModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsVerified, true)); + int countUpdate = await otpModel.Select(t => t.OtpDatabaseModel).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsUsed, true)); + + // Si no se actualizaron. + if (countUpdate <= 0) + return new(Responses.NotRows); + + return new(Responses.Success); + } + catch (Exception) + { + return new(Responses.Undefined); + } + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OtpRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OtpRepository.cs index be272fd..db71ca4 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OtpRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OtpRepository.cs @@ -68,7 +68,7 @@ public async Task ReadAndUpdate(int accountId, string code) var update = await (from A in context.OTPs where A.AccountId == accountId && A.Code == code - && A.ExpireTime > DateTime.Now + && A.ExpireTime > DateTime.UtcNow && A.IsUsed == false select A).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsUsed, true)); diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs index 39fbf50..6994e1c 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/PolicyMemberRepository.cs @@ -34,9 +34,9 @@ public async Task> ReadAll(int id) try { - var identities = await(from pl in context.IdentityPolicies - where pl.IdentityId == id - select pl.Policy).ToListAsync(); + var identities = await (from pl in context.IdentityPolicies + where pl.IdentityId == id + select pl.Policy).ToListAsync(); return new() { diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IApplicationRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IApplicationRepository.cs index e1ca1eb..c27c930 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/IApplicationRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/IApplicationRepository.cs @@ -2,11 +2,8 @@ public interface IApplicationRepository { - Task Create(ApplicationModel modelo); - Task> Read(string key); - + Task> Read(int key); Task> ExistApp(string key); - } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IMailRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IMailRepository.cs new file mode 100644 index 0000000..9fdd80e --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IMailRepository.cs @@ -0,0 +1,8 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IMailRepository +{ + Task> Create(MailModel model); + Task> ReadPrincipal(string unique); + Task ValidateOtpForMail(string email, string code); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs index 9c95e82..2812353 100644 --- a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs +++ b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs @@ -1,5 +1,4 @@ -using LIN.Cloud.Identity.Services.Services; -using LIN.Cloud.Identity.Services.Services.Authentication; +using LIN.Cloud.Identity.Services.Services.Authentication; using LIN.Cloud.Identity.Services.Services.Iam; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -26,6 +25,7 @@ public static IServiceCollection AddAuthenticationServices(this IServiceCollecti services.AddScoped(); JwtService.Open(configuration); + JwtApplicationsService.Open(configuration); return services; } diff --git a/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs b/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs index 0a59f08..e0243df 100644 --- a/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs +++ b/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs @@ -5,6 +5,7 @@ public class AuthenticationRequest public string User { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; public string Application { get; set; } = string.Empty; + public int ApplicationId { get; set; } public LIN.Types.Cloud.Identity.Models.Identities.AccountModel? Account { get; internal set; } public LIN.Types.Cloud.Identity.Models.Identities.ApplicationModel? ApplicationModel { get; internal set; } diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs index 86d1d58..0cba0f2 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/ApplicationValidationService.cs @@ -1,4 +1,6 @@ -namespace LIN.Cloud.Identity.Services.Services.Authentication; +using LIN.Types.Cloud.Identity.Models.Identities; + +namespace LIN.Cloud.Identity.Services.Services.Authentication; internal class ApplicationValidationService(IApplicationRepository applicationRepository, IPolicyOrchestrator policyOrchestrator) : IApplicationValidationService { @@ -9,7 +11,12 @@ internal class ApplicationValidationService(IApplicationRepository applicationRe public async Task Authenticate(AuthenticationRequest request) { // Obtener la aplicación. - var applicationResponse = await applicationRepository.Read(request.Application); + ReadOneResponse applicationResponse; + + if (request.ApplicationId <= 0) + applicationResponse = await applicationRepository.Read(request.Application); + else + applicationResponse = await applicationRepository.Read(request.ApplicationId); if (applicationResponse.Response != Responses.Success) return new ResponseBase(Responses.UnauthorizedByApp) diff --git a/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs b/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs new file mode 100644 index 0000000..d78f1e6 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs @@ -0,0 +1,116 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace LIN.Cloud.Identity.Services.Services; + +public class JwtApplicationsService +{ + + /// + /// Llave del token + /// + private static string JwtKey { get; set; } = string.Empty; + + + /// + /// Inicia el servicio JwtService + /// + public static void Open(IConfiguration configuration) + { + JwtKey = configuration["jwt:keyapp"]; + } + + + /// + /// Genera un JSON Web Token + /// + /// Modelo de usuario + public static string Generate(int appID) + { + + // Configuración + var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtKey)); + + // Credenciales + var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512); + + // Reclamaciones + var claims = new[] + { + new Claim(ClaimTypes.Authentication, appID.ToString()) + }; + + // Expiración del token + var expiración = DateTime.UtcNow.AddMinutes(5); + + // Token + var token = new JwtSecurityToken(null, null, claims, null, expiración, credentials); + + // Genera el token + return new JwtSecurityTokenHandler().WriteToken(token); + } + + + /// + /// Valida un JSON Web token + /// + /// Token a validar + public static int Validate(string token) + { + try + { + + // Comprobación + if (string.IsNullOrWhiteSpace(token)) + return 0; + + // Configurar la clave secreta. + var key = Encoding.ASCII.GetBytes(JwtKey); + + // Validar el token + var tokenHandler = new JwtSecurityTokenHandler(); + + var validationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false, + RequireExpirationTime = true + }; + + try + { + + var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); + var jwtToken = (JwtSecurityToken)validatedToken; + + + // Si el token es válido, puedes acceder a los claims (datos) del usuario + var user = jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value; + + // + _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Authentication)?.Value, out var appID); + + + // Devuelve una respuesta exitosa + return appID; + + } + catch (SecurityTokenException) + { + } + + + } + catch { } + + return 0; + + } + + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/JwtService.cs b/LIN.Cloud.Identity.Services/Services/JwtService.cs index a01840c..563fe8d 100644 --- a/LIN.Cloud.Identity.Services/Services/JwtService.cs +++ b/LIN.Cloud.Identity.Services/Services/JwtService.cs @@ -48,7 +48,7 @@ public static string Generate(AccountModel user, int appID) }; // Expiración del token - var expiración = DateTime.Now.AddHours(5); + var expiración = DateTime.UtcNow.AddHours(5); // Token var token = new JwtSecurityToken(null, null, claims, null, expiración, credentials); diff --git a/LIN.Cloud.Identity.Services/usings.cs b/LIN.Cloud.Identity.Services/usings.cs index 1fef1ba..c73bcd1 100644 --- a/LIN.Cloud.Identity.Services/usings.cs +++ b/LIN.Cloud.Identity.Services/usings.cs @@ -1,7 +1,7 @@ global using LIN.Cloud.Identity.Persistence.Repositories; global using LIN.Cloud.Identity.Services.Interfaces; global using LIN.Cloud.Identity.Services.Models; +global using LIN.Cloud.Identity.Services.Services; global using LIN.Types.Cloud.Identity.Enumerations; global using LIN.Types.Enumerations; global using LIN.Types.Responses; -global using LIN.Cloud.Identity.Services.Services; \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs index e309cc8..a1e1ebe 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs @@ -1,6 +1,7 @@ -namespace LIN.Cloud.Identity.Areas.Applications; +using LIN.Cloud.Identity.Services.Services; + +namespace LIN.Cloud.Identity.Areas.Applications; -[IdentityToken] [Route("applications")] public class ApplicationsController(IApplicationRepository application) : AuthenticationBaseController { @@ -10,6 +11,7 @@ public class ApplicationsController(IApplicationRepository application) : Authen /// /// App. [HttpPost] + [IdentityToken] public async Task Create([FromBody] ApplicationModel app) { @@ -58,4 +60,45 @@ public async Task Create([FromBody] ApplicationModel app) return create; } + + /// + /// Solicitar token de acceso a app. + /// + [HttpGet("token")] + public async Task RequestToken([FromHeader] string key) + { + // Validar key. + var app = await application.Read(key); + + if (app.Response != Responses.Success) + return new(Responses.InvalidParam); + + // Generar token de acceso. + var token = JwtApplicationsService.Generate(app.Model.Id); + + return new ResponseBase + { + Response = Responses.Success, + Token = token + }; + } + + + /// + /// Obtener la información básica de la aplicación. + /// + [HttpGet("information")] + public async Task> RequestInformation([FromHeader] string token) + { + + int id = JwtApplicationsService.Validate(token); + + if (id <= 0) + return new(Responses.InvalidParam); + + // Validar key. + var app = await application.Read(id); + return app; + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationV4Controller.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationV4Controller.cs new file mode 100644 index 0000000..fe0f50d --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationV4Controller.cs @@ -0,0 +1,108 @@ +using LIN.Cloud.Identity.Services.Services; + +namespace LIN.Cloud.Identity.Areas.Authentication; + +[Route("V4/[controller]")] +public class AuthenticationV4Controller(IAuthenticationAccountService serviceAuth) : AuthenticationBaseController +{ + + /// + /// Iniciar sesión en una cuenta de usuario. + /// + /// Unique. + /// Contraseña. + /// token de la app.. + /// Retorna el modelo de la cuenta y el token de acceso. + [HttpGet("login")] + public async Task> Login([FromQuery] string user, [FromQuery] string password, [FromHeader] string token) + { + + // Validación de parámetros. + if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(token)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son invalido." + }; + + // Validar el token + int appId = JwtApplicationsService.Validate(token); + + if (appId <= 0) + return new(Responses.Unauthorized) + { + Message = "El token no es valido." + }; + + // Establecer credenciales. + var response = await serviceAuth.Authenticate(new() + { + User = user, + Password = password, + ApplicationId = appId + }); + + // Validación al obtener el usuario + switch (response.Response) + { + // Correcto + case Responses.Success: + break; + + // No existe esta cuenta. + case Responses.NotExistAccount: + return new() + { + Response = Responses.NotExistAccount, + Message = "No existe esta cuenta." + }; + + // Contraseña invalida. + case Responses.InvalidPassword: + return new() + { + Response = Responses.InvalidPassword, + Message = "Contraseña incorrecta." + }; + + // Contraseña invalida. + case Responses.UnauthorizedByApp: + return new() + { + Response = Responses.UnauthorizedByApp, + Message = "La aplicación no existe o no permite que inicies sesión en este momento.", + Errors = response.Errors + }; + + // Contraseña invalida. + case Responses.UnauthorizedByOrg: + return new() + { + Response = Responses.UnauthorizedByOrg, + Message = "Tu organización no permite que inicies sesión en este momento.", + Errors = response.Errors + }; + + // Incorrecto + default: + return new() + { + Response = Responses.Undefined, + Message = "Hubo un error grave." + }; + } + + // Genera el token + var tokenGen = serviceAuth.GenerateToken(); + + // Respuesta. + var http = new ReadOneResponse + { + Model = serviceAuth.Account!, + Response = Responses.Success, + Token = tokenGen + }; + + return http; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs index 0b836b9..10f420a 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/IntentsController.cs @@ -21,7 +21,7 @@ where a.Key.Equals(UserInformation.Unique, StringComparison.CurrentCultureIgnore select a).FirstOrDefault().Value ?? []; // Hora actual. - var timeNow = DateTime.Now; + var timeNow = DateTime.UtcNow; // Intentos. var intentos = (from I in account diff --git a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs index 2b5c736..f1aa0a5 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/SecurityController.cs @@ -1,159 +1,159 @@ namespace LIN.Cloud.Identity.Areas.Authentication; [Route("[controller]")] -public class SecurityController(IAccountRepository accountsData, IOtpRepository otpService, EmailSender emailSender) : AuthenticationBaseController +public class SecurityController(IAccountRepository accountsData, IOtpRepository otpService, IMailRepository mailRepository, EmailSender emailSender) : AuthenticationBaseController { - ///// - ///// Agregar un correo a una cuenta. - ///// - ///// Correo. - //[HttpPost("mail")] - //[IdentityToken] - //public async Task AddMail([FromQuery] string email) - //{ - // // Generar modelo del correo. - // var model = new MailModel() - // { - // Mail = email, - // AccountId = UserInformation.AccountId, - // IsPrincipal = false, - // IsVerified = false - // }; - - // // Respuesta. - // var responseCreate = await mails.Create(model); - - // // Si hubo un error. - // switch (responseCreate.Response) - // { - // // Correcto. - // case Responses.Success: - // break; - - // // Ya estaba registrado. - // case Responses.ResourceExist: - // return new(responseCreate.Response) - // { - // Message = $"Hubo un error al agregar el correo <{email}> a la cuenta con identidad: '{UserInformation.IdentityId}'", - // Errors = [ - // new() { - // Tittle = "Mail duplicado", - // Description = "El correo ya se encuentra registrado en el sistema." - // } - // ] - // }; - // default: - // return new(responseCreate.Response) - // { - // Message = $"Hubo un error al agregar el correo <{email}> a la cuenta {model.Account.Identity.Unique}" - // }; - // } - - // // Generar Otp. - // var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); - - // // Guardar OTP. - // var otpCreateResponse = await otpService.Create(new MailOtpDatabaseModel - // { - // MailModel = responseCreate.Model, - // OtpDatabaseModel = new() - // { - // Account = new() { Id = UserInformation.AccountId }, - // Code = otpCode, - // ExpireTime = DateTime.Now.AddMinutes(10), - // IsUsed = false - // } - // }); - - // // Enviar correo de verificación. - // if (otpCreateResponse.Response != Responses.Success) - // return new() - // { - // Message = "Hubo un error al guardar el código OTP." - // }; - - // // Enviar correo. - // var success = await emailSender.Send(email, "Verificar", $"Verificar tu correo {otpCode}"); - - // return new(success ? Responses.Success : Responses.UnavailableService); - - //} - - - ///// - ///// Validar un correo. - ///// - ///// Correo a validar. - ///// Código OTP. - //[HttpPost("validate")] - //public async Task Validate([FromQuery] string mail, [FromQuery] string code) - //{ - // // Validar OTP. - // var response = await mails.ValidateOtpForMail(mail, code); - // return response; - //} - - - ///// - ///// Si un usuario olvido la contraseña. - ///// - ///// Usuario que olvido. - //[HttpPost("forget/password")] - //public async Task ForgetPassword([FromQuery] string user) - //{ - - // // Validar estado del usuario. - // var account = await accountsData.Read(user, new() - // { - // FindOn = FindOn.StableAccounts, - // IncludePhoto = false - // }); - - // if (account.Response != Responses.Success) - // return new(Responses.NotExistAccount) - // { - // Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." - // }; - - // // Obtener mail principal. - // var mail = await mails.ReadPrincipal(user); - - // if (mail.Response != Responses.Success) - // return new(Responses.NotRows) - // { - // Message = "Esta cuenta no tiene un correo principal establecido." - // }; - - // // Generar OTP. - // var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); - - // // Guardar OTP. - // var modelo = new OtpDatabaseModel - // { - // Account = account.Model, - // AccountId = account.Model.Id, - // Code = otpCode, - // ExpireTime = DateTime.Now.AddMinutes(10), - // IsUsed = false - // }; - - // // Crear OTP. - // var created = await otpService.Create(modelo); - - // // Si hubo un error. - // if (created.Response != Responses.Success) - // return new(created.Response) - // { - // Message = "No se pudo crear el código de verificación." - // }; - - // // Enviar mail. - // var success = await emailSender.Send(mail.Model.Mail, "Recuperación de contraseña", $"Su código de verificación es: {otpCode}"); - - // return new(success ? Responses.Success : Responses.UnavailableService); - - //} + /// + /// Agregar un correo a una cuenta. + /// + /// Correo. + [HttpPost("mail")] + [IdentityToken] + public async Task AddMail([FromQuery] string email) + { + // Generar modelo del correo. + var model = new MailModel() + { + Mail = email, + AccountId = UserInformation.AccountId, + IsPrincipal = false, + IsVerified = false + }; + + // Respuesta. + var responseCreate = await mailRepository.Create(model); + + // Si hubo un error. + switch (responseCreate.Response) + { + // Correcto. + case Responses.Success: + break; + + // Ya estaba registrado. + case Responses.ResourceExist: + return new(responseCreate.Response) + { + Message = $"Hubo un error al agregar el correo <{email}> a la cuenta con identidad: '{UserInformation.IdentityId}'", + Errors = [ + new() { + Tittle = "Mail duplicado", + Description = "El correo ya se encuentra registrado en el sistema." + } + ] + }; + default: + return new(responseCreate.Response) + { + Message = $"Hubo un error al agregar el correo <{email}> a la cuenta {model.Account.Identity.Unique}" + }; + } + + // Generar Otp. + var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); + + // Guardar OTP. + var otpCreateResponse = await otpService.Create(new MailOtpDatabaseModel + { + MailModel = responseCreate.Model, + OtpDatabaseModel = new() + { + Account = new() { Id = UserInformation.AccountId }, + Code = otpCode, + ExpireTime = DateTime.UtcNow.AddMinutes(10), + IsUsed = false + } + }); + + // Enviar correo de verificación. + if (otpCreateResponse.Response != Responses.Success) + return new() + { + Message = "Hubo un error al guardar el código OTP." + }; + + // Enviar correo. + var success = await emailSender.Send(email, "Verificar", $"Verificar tu correo {otpCode}"); + + return new(success ? Responses.Success : Responses.UnavailableService); + + } + + + /// + /// Validar un correo. + /// + /// Correo a validar. + /// Código OTP. + [HttpPost("validate")] + public async Task Validate([FromQuery] string mail, [FromQuery] string code) + { + // Validar OTP. + var response = await mailRepository.ValidateOtpForMail(mail, code); + return response; + } + + + /// + /// Si un usuario olvido la contraseña. + /// + /// Usuario que olvido. + [HttpPost("forget/password")] + public async Task ForgetPassword([FromQuery] string user) + { + + // Validar estado del usuario. + var account = await accountsData.Read(user, new() + { + FindOn = FindOn.StableAccounts, + IncludePhoto = false + }); + + if (account.Response != Responses.Success) + return new(Responses.NotExistAccount) + { + Message = "No se puede reestablecer la contraseña de esta cuenta debido a que no existe o esta inactiva." + }; + + // Obtener mail principal. + var mail = await mailRepository.ReadPrincipal(user); + + if (mail.Response != Responses.Success) + return new(Responses.NotRows) + { + Message = "Esta cuenta no tiene un correo principal establecido." + }; + + // Generar OTP. + var otpCode = Global.Utilities.KeyGenerator.GenerateOTP(5); + + // Guardar OTP. + var modelo = new OtpDatabaseModel + { + Account = account.Model, + AccountId = account.Model.Id, + Code = otpCode, + ExpireTime = DateTime.UtcNow.AddMinutes(10), + IsUsed = false + }; + + // Crear OTP. + var created = await otpService.Create(modelo); + + // Si hubo un error. + if (created.Response != Responses.Success) + return new(created.Response) + { + Message = "No se pudo crear el código de verificación." + }; + + // Enviar mail. + var success = await emailSender.Send(mail.Model.Mail, "Recuperación de contraseña", $"Su código de verificación es: {otpCode}"); + + return new(success ? Responses.Success : Responses.UnavailableService); + + } /// @@ -211,8 +211,6 @@ public async Task Reset([FromQuery] string code, [FromQuery] s var update = await accountsData.UpdatePassword(account.Model.Id, newPassword); return update; - - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs index dcf96d8..94685aa 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/OrganizationMembersController.cs @@ -78,7 +78,7 @@ public async Task Create([FromBody] AccountModel modelo, [Fr // Ajustar el modelo. modelo.Visibility = Visibility.Hidden; - modelo.Password = $"pwd@{DateTime.Now.Year}"; + modelo.Password = $"pwd@{DateTime.UtcNow.Year}"; modelo = Services.Formats.Account.Process(modelo); modelo.AccountType = AccountTypes.Work; diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs index c6c2719..47864d0 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs @@ -6,7 +6,7 @@ public class PoliciesIdentityController(IPolicyMemberRepository policiesData, II { /// - /// Crear nueva política. + /// Asociar política. /// /// Modelo de la identidad. [HttpPost] diff --git a/LIN.Cloud.Identity/Program.cs b/LIN.Cloud.Identity/Program.cs index 591bd4f..96196c6 100644 --- a/LIN.Cloud.Identity/Program.cs +++ b/LIN.Cloud.Identity/Program.cs @@ -16,7 +16,6 @@ // Servicio de autenticación. builder.Services.AddPersistence(builder.Configuration); -//builder.Host.UseLoggingService(builder.Configuration); var app = builder.Build(); app.UseLINHttp(); diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index eaebfe8..4de6c2e 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -14,9 +14,6 @@ public static IServiceCollection AddLocalServices(this IServiceCollection servic // Externos services.AddSingleton(); - // Iam. - services.AddScoped(); - return services; } diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index a8c1ef4..405b376 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -74,9 +74,9 @@ public static AccountModel Process(AccountModel baseAccount) Id = 0, Status = IdentityStatus.Enable, Type = IdentityType.Account, - CreationTime = DateTime.Now, - EffectiveTime = DateTime.Now, - ExpirationTime = DateTime.Now.AddYears(5), + CreationTime = DateTime.UtcNow, + EffectiveTime = DateTime.UtcNow, + ExpirationTime = DateTime.UtcNow.AddYears(5), Roles = [], Unique = baseAccount.Identity.Unique.Trim() } diff --git a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs b/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs deleted file mode 100644 index ec52dfd..0000000 --- a/LIN.Cloud.Identity/Services/Iam/IamPolicy.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace LIN.Cloud.Identity.Services.Iam; - -public class IamPolicy(DataContext context, IGroupRepository groups, IIamService rolesIam) -{ - - /// - /// Validar el nivel de acceso a una política. - /// - /// Id de la identidad. - /// Id de la política. - public async Task Validate(int identity, int policy) - { - - // Obtener la identidad del dueño de la política. - var ownerPolicy = await (from pol in context.Policies - where pol.Id == policy - select pol.Owner.Directory.IdentityId).FirstOrDefaultAsync(); - - // Obtener la organización. - var organizationId = await groups.GetOwnerByIdentity(ownerPolicy); - - // Obtener roles de la identidad sobre la organización. - var roles = await rolesIam.Validate(identity, organizationId.Model); - - // Tiene permisos para modificar la política. - if (ValidateRoles.ValidateAlterPolicies(roles)) - return IamLevels.Privileged; - - return IamLevels.NotAccess; - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index 7873661..ec5f30a 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -107,7 +107,7 @@ public async Task ReceiveRequest(PassKeyModel modelo) attempt.Status = modelo.Status; // Si el tiempo de expiración ya paso - if (DateTime.Now > modelo.Expiration) + if (DateTime.UtcNow > modelo.Expiration) { attempt.Status = PassKeyStatus.Expired; attempt.Token = string.Empty; @@ -136,7 +136,7 @@ public async Task ReceiveRequest(PassKeyModel modelo) Status = attempt.Status, User = attempt.User, Token = attempt.Token, - Time = DateTime.Now, + Time = DateTime.UtcNow, HubKey = string.Empty, Key = string.Empty }; @@ -146,7 +146,7 @@ await accountLogs.Create(new() { AccountId = accountJwt.AccountId, AuthenticationMethod = AuthenticationMethods.Authenticator, - Time = DateTime.Now, + Time = DateTime.UtcNow, }); // Respuesta al cliente. diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index 4bb476c..d4bf839 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -46,12 +46,12 @@ public async Task JoinIntent(PassKeyModel attempt) //attempt.Application.ID = application.Model.ID; // Vencimiento - var expiración = DateTime.Now.AddMinutes(2); + var expiración = DateTime.UtcNow.AddMinutes(2); // Caducidad el modelo attempt.HubKey = Context.ConnectionId; attempt.Status = PassKeyStatus.Undefined; - attempt.Time = DateTime.Now; + attempt.Time = DateTime.UtcNow; attempt.Expiration = expiración; // Agrega el modelo diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index 25acd59..a39c0f9 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -7,7 +7,6 @@ global using LIN.Cloud.Identity.Persistence.Repositories; global using LIN.Cloud.Identity.Services.Extensions; global using LIN.Cloud.Identity.Services.Filters; -global using LIN.Cloud.Identity.Services.Iam; global using LIN.Cloud.Identity.Services.Interfaces; global using LIN.Cloud.Identity.Services.Models; global using LIN.Cloud.Identity.Services.Utils; @@ -21,6 +20,4 @@ global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore.SignalR; // SQL. -global using Microsoft.EntityFrameworkCore; -// Tipos Extras. -// Framework. +global using Microsoft.EntityFrameworkCore; \ No newline at end of file From 3e8651a0b9b85f1f2fab54f79fa59dd3f3a7ee16 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sat, 24 May 2025 19:46:37 -0500 Subject: [PATCH 171/178] Mejoras generales. --- .../Contexts/DataContext.cs | 53 +----------------- .../Contexts/SeedContext.cs | 55 +++++++++++++++++++ .../Extensions/PersistenceExtensions.cs | 4 +- .../Formatters/Account.cs | 51 +---------------- .../Formatters/Identities.cs | 20 ------- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../Models/MailOtpDatabaseModel.cs | 4 +- .../Models/OtpDatabaseModel.cs | 4 +- .../Services/Iam/Iam.cs | 2 - .../Services/JwtApplicationsService.cs | 2 +- .../Services/JwtService.cs | 9 +-- .../Areas/Accounts/AccountController.cs | 1 - .../Services/Extensions/LocalServices.cs | 4 -- .../Filters/IdentityTokenAttribute.cs | 2 - .../Services/Formats/Account.cs | 8 ++- 15 files changed, 73 insertions(+), 148 deletions(-) create mode 100644 LIN.Cloud.Identity.Persistence/Contexts/SeedContext.cs delete mode 100644 LIN.Cloud.Identity.Persistence/Formatters/Identities.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index 67fe20b..bad5839 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -1,6 +1,4 @@ -using Newtonsoft.Json; - -namespace LIN.Cloud.Identity.Persistence.Contexts; +namespace LIN.Cloud.Identity.Persistence.Contexts; public class DataContext(DbContextOptions options) : DbContext(options) { @@ -288,53 +286,4 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // Base. base.OnModelCreating(modelBuilder); } - - - /// - /// Data primaria. - /// - public void Seed() - { - - // Si no hay cuentas. - if (!Accounts.Any()) - { - // Obtener la data. - var jsonData = File.ReadAllText("wwwroot/seeds/users.json"); - var users = JsonConvert.DeserializeObject>(jsonData) ?? []; - - foreach (var user in users) - user.Password = Global.Utilities.Cryptography.Encrypt(user.Password); - - // Agregar los modelos. - if (users != null && users.Count > 0) - { - Accounts.AddRange(users); - SaveChanges(); - } - } - - // Si no hay aplicaciones. - if (!Applications.Any()) - { - // Obtener la data. - var jsonData = File.ReadAllText("wwwroot/seeds/applications.json"); - var apps = JsonConvert.DeserializeObject>(jsonData) ?? []; - - // Formatear modelos. - foreach (var app in apps) - { - app.Identity.Type = Types.Cloud.Identity.Enumerations.IdentityType.Service; - app.Owner = new() { Id = app.OwnerId }; - app.Owner = this.AttachOrUpdate(app.Owner); - } - - // Agregar aplicaciones. - if (apps != null && apps.Count > 0) - { - Applications.AddRange(apps); - SaveChanges(); - } - } - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Contexts/SeedContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/SeedContext.cs new file mode 100644 index 0000000..45845ed --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Contexts/SeedContext.cs @@ -0,0 +1,55 @@ +using Newtonsoft.Json; + +namespace LIN.Cloud.Identity.Persistence.Contexts; + +internal class SeedContext +{ + + /// + /// Data primaria. + /// + public static void Seed(DataContext context) + { + // Si no hay cuentas. + if (!context.Accounts.Any()) + { + // Obtener la data. + var jsonData = File.ReadAllText("wwwroot/seeds/users.json"); + var users = JsonConvert.DeserializeObject>(jsonData) ?? []; + + foreach (var user in users) + user.Password = Global.Utilities.Cryptography.Encrypt(user.Password); + + // Agregar los modelos. + if (users != null && users.Count > 0) + { + context.Accounts.AddRange(users); + context.SaveChanges(); + } + } + + // Si no hay aplicaciones. + if (!context.Applications.Any()) + { + // Obtener la data. + var jsonData = File.ReadAllText("wwwroot/seeds/applications.json"); + var apps = JsonConvert.DeserializeObject>(jsonData) ?? []; + + // Formatear modelos. + foreach (var app in apps) + { + app.Identity.Type = Types.Cloud.Identity.Enumerations.IdentityType.Service; + app.Owner = new() { Id = app.OwnerId }; + app.Owner = context.AttachOrUpdate(app.Owner)!; + } + + // Agregar aplicaciones. + if (apps != null && apps.Count > 0) + { + context.Applications.AddRange(apps); + context.SaveChanges(); + } + } + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index e3b211b..b7ed977 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -64,7 +64,9 @@ public static IApplicationBuilder UseDataBase(this IApplicationBuilder app) { var context = scope.ServiceProvider.GetService(); bool? created = context?.Database.EnsureCreated(); - context?.Seed(); + + // Crear la base de datos si no existe. + SeedContext.Seed(context!); } catch (Exception ex) { diff --git a/LIN.Cloud.Identity.Persistence/Formatters/Account.cs b/LIN.Cloud.Identity.Persistence/Formatters/Account.cs index a0f4241..67ddd13 100644 --- a/LIN.Cloud.Identity.Persistence/Formatters/Account.cs +++ b/LIN.Cloud.Identity.Persistence/Formatters/Account.cs @@ -1,58 +1,10 @@ -using LIN.Types.Models; -using System.Text.RegularExpressions; -using IdentityService = LIN.Types.Cloud.Identity.Enumerations.IdentityService; +using IdentityService = LIN.Types.Cloud.Identity.Enumerations.IdentityService; namespace LIN.Cloud.Identity.Persistence.Formatters; public class Account { - /// - /// Procesar el modelo. - /// - /// Modelo - public static List Validate(AccountModel baseAccount) - { - - List errors = []; - - if (string.IsNullOrWhiteSpace(baseAccount.Name)) - errors.Add(new ErrorModel() - { - Tittle = "Nombre invalido", - Description = "El nombre del usuario no puede estar vacío.", - }); - - if (baseAccount.Identity == null || string.IsNullOrWhiteSpace(baseAccount.Identity.Unique)) - errors.Add(new ErrorModel() - { - Tittle = "Identidad no valida", - Description = "La cuenta debe tener un identificador único valido.", - }); - - if (!ValidarCadena(baseAccount.Identity.Unique)) - errors.Add(new ErrorModel() - { - Tittle = "Identidad no valida", - Description = "La identidad de la cuenta no puede contener símbolos NO alfanuméricos." - }); - - return errors; - } - - - - static bool ValidarCadena(string cadena) - { - // Patrón de expresión regular para permitir solo letras o números - string patron = "^[a-zA-Z0-9]*$"; - - // Comprobar la coincidencia con el patrón - return Regex.IsMatch(cadena, patron); - } - - - /// /// Procesar el modelo. /// @@ -83,5 +35,4 @@ public static AccountModel Process(AccountModel baseAccount) }; } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs b/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs deleted file mode 100644 index 07df5a6..0000000 --- a/LIN.Cloud.Identity.Persistence/Formatters/Identities.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace LIN.Cloud.Identity.Persistence.Formatters; - -public class Identities -{ - - /// - /// Procesar el modelo. - /// - /// Modelo - public static void Process(IdentityModel id) - { - id.Id = 0; - id.ExpirationTime = DateTime.UtcNow.AddYears(10); - id.EffectiveTime = DateTime.UtcNow; - id.CreationTime = DateTime.UtcNow; - id.Status = IdentityStatus.Enable; - id.Unique = id.Unique.Trim(); - } - -} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 5454434..4774c9e 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -11,7 +11,7 @@ - + diff --git a/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs b/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs index 9cd966d..0696da7 100644 --- a/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs +++ b/LIN.Cloud.Identity.Persistence/Models/MailOtpDatabaseModel.cs @@ -2,8 +2,8 @@ public class MailOtpDatabaseModel { - public MailModel MailModel { get; set; } - public OtpDatabaseModel OtpDatabaseModel { get; set; } + public MailModel MailModel { get; set; } = null!; + public OtpDatabaseModel OtpDatabaseModel { get; set; } = null!; public int MailId { get; set; } public int OtpId { get; set; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs b/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs index de99617..d2882f2 100644 --- a/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs +++ b/LIN.Cloud.Identity.Persistence/Models/OtpDatabaseModel.cs @@ -3,9 +3,9 @@ public class OtpDatabaseModel { public int Id { get; set; } - public string Code { get; set; } + public string Code { get; set; } = string.Empty; public DateTime ExpireTime { get; set; } public bool IsUsed { get; set; } - public LIN.Types.Cloud.Identity.Models.Identities.AccountModel Account { get; set; } + public LIN.Types.Cloud.Identity.Models.Identities.AccountModel Account { get; set; } = null!; public int AccountId { get; set; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs b/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs index 34b7b8a..7b0eac0 100644 --- a/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs +++ b/LIN.Cloud.Identity.Services/Services/Iam/Iam.cs @@ -49,8 +49,6 @@ where z.Members.Any(x => x.IdentityId == identity1) organizations = organizations.Distinct().ToList(); } - bool have = false; - List roles = new(); foreach (var e in organizations) diff --git a/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs b/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs index d78f1e6..b5c0c71 100644 --- a/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs +++ b/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs @@ -20,7 +20,7 @@ public class JwtApplicationsService /// public static void Open(IConfiguration configuration) { - JwtKey = configuration["jwt:keyapp"]; + JwtKey = configuration["jwt:keyapp"] ?? string.Empty; } diff --git a/LIN.Cloud.Identity.Services/Services/JwtService.cs b/LIN.Cloud.Identity.Services/Services/JwtService.cs index 563fe8d..4ceb591 100644 --- a/LIN.Cloud.Identity.Services/Services/JwtService.cs +++ b/LIN.Cloud.Identity.Services/Services/JwtService.cs @@ -21,7 +21,7 @@ public class JwtService /// public static void Open(IConfiguration configuration) { - JwtKey = configuration["jwt:key"]; + JwtKey = configuration["jwt:key"] ?? string.Empty; } @@ -66,7 +66,6 @@ public static JwtModel Validate(string token) { try { - // Comprobación if (string.IsNullOrWhiteSpace(token)) return new() @@ -95,16 +94,13 @@ public static JwtModel Validate(string token) var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); var jwtToken = (JwtSecurityToken)validatedToken; - // Si el token es válido, puedes acceder a los claims (datos) del usuario var user = jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value; - // _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.PrimarySid)?.Value, out var id); _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Authentication)?.Value, out var appID); _ = int.TryParse(jwtToken.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.GroupSid)?.Value, out var identityId); - // Devuelve una respuesta exitosa return new() { @@ -119,8 +115,6 @@ public static JwtModel Validate(string token) catch (SecurityTokenException) { } - - } catch { } @@ -128,7 +122,6 @@ public static JwtModel Validate(string token) { IsAuthenticated = false }; - } diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 28e284e..7b88ee3 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -14,7 +14,6 @@ public class AccountController(IAccountRepository accountData, IApplicationRepos [HttpPost] public async Task Create([FromBody] AccountModel? modelo, [FromHeader] string app) { - // Validaciones del modelo. if (modelo is null || modelo.Identity is null || modelo.Password.Length < 4 || modelo.Name.Length <= 0 || modelo.Identity.Unique.Length <= 0) return new(Responses.InvalidParam) diff --git a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs index 4de6c2e..fa8d71f 100644 --- a/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs +++ b/LIN.Cloud.Identity/Services/Extensions/LocalServices.cs @@ -9,13 +9,9 @@ public static class LocalServices /// Services. public static IServiceCollection AddLocalServices(this IServiceCollection services) { - - // Servicios de datos. // Externos services.AddSingleton(); - return services; - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs index 956dfc5..9dc174d 100644 --- a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs +++ b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs @@ -13,7 +13,6 @@ public class IdentityTokenAttribute : ActionFilterAttribute /// Siguiente. public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - // Contexto HTTP. var httpContext = context.HttpContext; @@ -45,7 +44,6 @@ await httpContext.Response.WriteAsJsonAsync(new ResponseBase() // Agrega la información del token. context.HttpContext.Items.Add("authentication", tokenInfo); await base.OnActionExecutionAsync(context, next); - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 405b376..35e1b5b 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -30,7 +30,7 @@ public static List Validate(AccountModel baseAccount) Description = "La cuenta debe tener un identificador único valido.", }); - if (!ValidarCadena(baseAccount.Identity.Unique)) + if (!ValidarCadena(baseAccount.Identity?.Unique)) errors.Add(new ErrorModel() { Tittle = "Identidad no valida", @@ -42,8 +42,12 @@ public static List Validate(AccountModel baseAccount) - static bool ValidarCadena(string cadena) + static bool ValidarCadena(string? cadena) { + // Si la cadena es nula o vacía, no es válida + if (string.IsNullOrWhiteSpace(cadena)) + return false; + // Patrón de expresión regular para permitir solo letras o números string patron = "^[a-zA-Z0-9]*$"; From 2ca8796f86d019e10ece50a84ca6191a9cd2a7d0 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sat, 24 May 2025 23:04:22 -0500 Subject: [PATCH 172/178] =?UTF-8?q?Integraci=C3=B3n=20con=20autenticaci?= =?UTF-8?q?=C3=B3n=20de=20terceros,=20identidades=20federadas=20y=20Google?= =?UTF-8?q?=20Auth.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../Extensions/ServiceExtensions.cs | 14 ++- .../AuthenticationPipelineComponent.cs | 8 +- .../LIN.Cloud.Identity.Services.csproj | 4 + .../Models/AuthenticationRequest.cs | 7 +- .../Services/AccountAuthenticationService.cs | 32 ++++++- .../AccountValidationService.cs | 23 +++++ .../IdentityValidationService.cs | 7 +- .../ThirdParties/GoogleValidationService.cs | 85 +++++++++++++++++++ .../AuthenticationController.cs | 38 +++++++++ 10 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 LIN.Cloud.Identity.Services/Services/Authentication/AccountValidationService.cs create mode 100644 LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs diff --git a/.gitignore b/.gitignore index c8810ec..a7a8182 100644 --- a/.gitignore +++ b/.gitignore @@ -404,3 +404,4 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml /LIN.Cloud.Identity/appsettings.json +/LIN.Cloud.Identity/appsettings.firebase.json diff --git a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs index 2812353..e32882b 100644 --- a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs +++ b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs @@ -1,4 +1,7 @@ -using LIN.Cloud.Identity.Services.Services.Authentication; +using FirebaseAdmin; +using Google.Apis.Auth.OAuth2; +using LIN.Cloud.Identity.Services.Services.Authentication; +using LIN.Cloud.Identity.Services.Services.Authentication.ThirdParties; using LIN.Cloud.Identity.Services.Services.Iam; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -14,10 +17,19 @@ public static class ServiceExtensions /// Services. public static IServiceCollection AddAuthenticationServices(this IServiceCollection services, IConfiguration configuration) { + + // Inicializar Firebase + FirebaseApp.Create(new AppOptions + { + Credential = GoogleCredential.FromFile("appsettings.firebase.json") + }); + // Servicios de datos. services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/LIN.Cloud.Identity.Services/Interfaces/AuthenticationPipelineComponent.cs b/LIN.Cloud.Identity.Services/Interfaces/AuthenticationPipelineComponent.cs index 0f181b8..284c4df 100644 --- a/LIN.Cloud.Identity.Services/Interfaces/AuthenticationPipelineComponent.cs +++ b/LIN.Cloud.Identity.Services/Interfaces/AuthenticationPipelineComponent.cs @@ -2,4 +2,10 @@ public interface IApplicationValidationService : IAuthenticationService; public interface IIdentityValidationService : IAuthenticationService; -public interface IOrganizationValidationService : IAuthenticationService; \ No newline at end of file +public interface IAccountValidationService : IAuthenticationService; +public interface IOrganizationValidationService : IAuthenticationService; + +// Proveedores de servicios de autenticación de terceros (Google, Microsoft, etc.) +public interface IGoogleValidationService : IAuthenticationService; +public interface IMicrosoftValidationService : IAuthenticationService; +public interface IGitHubValidationService : IAuthenticationService; \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj index 1a7614e..7719e42 100644 --- a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj +++ b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj @@ -6,6 +6,10 @@ enable + + + + diff --git a/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs b/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs index e0243df..144b7b9 100644 --- a/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs +++ b/LIN.Cloud.Identity.Services/Models/AuthenticationRequest.cs @@ -5,9 +5,12 @@ public class AuthenticationRequest public string User { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; public string Application { get; set; } = string.Empty; + public string ThirdPartyToken { get; set; } = string.Empty; + public bool StrictService { get; set; } = false; public int ApplicationId { get; set; } + public Types.Cloud.Identity.Enumerations.IdentityService Service { get; set; } = Types.Cloud.Identity.Enumerations.IdentityService.LIN; - public LIN.Types.Cloud.Identity.Models.Identities.AccountModel? Account { get; internal set; } - public LIN.Types.Cloud.Identity.Models.Identities.ApplicationModel? ApplicationModel { get; internal set; } + public Types.Cloud.Identity.Models.Identities.AccountModel? Account { get; internal set; } + public Types.Cloud.Identity.Models.Identities.ApplicationModel? ApplicationModel { get; internal set; } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs b/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs index be88a5c..c381370 100644 --- a/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs +++ b/LIN.Cloud.Identity.Services/Services/AccountAuthenticationService.cs @@ -28,12 +28,36 @@ public async Task Authenticate(AuthenticationRequest request) using var scope = provider.CreateScope(); var serviceProvider = scope.ServiceProvider; - var pipelineSteps = new List - { - typeof(IIdentityValidationService), + // Configurar el pipeline de autenticación. + List pipelineSteps = []; + + pipelineSteps.AddRange([ + typeof(IIdentityValidationService) + ]); + + if (request.Service == Types.Cloud.Identity.Enumerations.IdentityService.Google) + { + // Agregar pasos específicos para Google. + pipelineSteps.Insert(0, typeof(IGoogleValidationService)); + } + else if (request.Service == Types.Cloud.Identity.Enumerations.IdentityService.Microsoft) + { + // Agregar pasos específicos para Microsoft. + //pipelineSteps.Insert(0, ); + } + else + { + // Autenticación por defecto para LIN. + pipelineSteps.AddRange([ + typeof(IAccountValidationService), + ]); + } + + // Pasos comunes para todos los servicios. + pipelineSteps.AddRange([ typeof(IOrganizationValidationService), typeof(IApplicationValidationService) - }; + ]); foreach (var stepType in pipelineSteps) { diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/AccountValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/AccountValidationService.cs new file mode 100644 index 0000000..c805256 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/Authentication/AccountValidationService.cs @@ -0,0 +1,23 @@ +namespace LIN.Cloud.Identity.Services.Services.Authentication; + +internal class AccountValidationService : IAccountValidationService +{ + + /// + /// Valida la cuenta de usuario y la contraseña. + /// + public async Task Authenticate(AuthenticationRequest request) + { + + // Validar contraseña. + if (Global.Utilities.Cryptography.Encrypt(request.Password) != request.Account!.Password) + return new ResponseBase + { + Response = Responses.InvalidPassword, + Message = "La contraseña es incorrecta." + }; + + return await Task.FromResult(new ResponseBase(Responses.Success)); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs index efd840b..82af837 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs @@ -34,12 +34,11 @@ public async Task Authenticate(AuthenticationRequest request) Message = "La identidad de la cuenta de usuario no se encuentra activa." }; - // Validar contraseña. - if (Global.Utilities.Cryptography.Encrypt(request.Password) != account.Password) + if (request.StrictService && account.IdentityService != account.IdentityService) return new ResponseBase { - Response = Responses.InvalidPassword, - Message = "La contraseña es incorrecta." + Response = Responses.Unauthorized, + Message = $"La cuenta no esta vinculada con el proveedor {request.Service}" }; // Establecer datos en la solicitud. diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs new file mode 100644 index 0000000..78294d4 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs @@ -0,0 +1,85 @@ +using FirebaseAdmin.Auth; +using LIN.Types.Cloud.Identity.Models.Identities; + +namespace LIN.Cloud.Identity.Services.Services.Authentication.ThirdParties; + +public class GoogleValidationService(IAccountRepository accountRepository) : IGoogleValidationService +{ + + /// + /// Valida la cuenta de usuario y la contraseña. + /// + public async Task Authenticate(AuthenticationRequest request) + { + + // Validar token de acceso. + var information = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(request.ThirdPartyToken); + + // Validar que la identidad de usuario exista. + string unique = information.Claims["email"].ToString() ?? string.Empty; + string name = information.Claims["name"].ToString() ?? string.Empty; + string? provider = information.Claims["firebase"] is Dictionary firebaseClaims + ? firebaseClaims["sign_in_provider"]?.ToString() + : "unknown"; + + if (string.IsNullOrEmpty(unique) || string.IsNullOrEmpty(name) || provider != "google.com") + return new(Responses.Unauthorized) + { + Message = "El token es invalido o no esta firmado para Google." + }; + + var account = await accountRepository.Read(unique, new() + { + FindOn = Persistence.Models.FindOn.AllAccounts, + IncludeIdentity = true + }); + + switch (account.Response) + { + case Responses.Success: + break; + case Responses.NotRows: + var accountNew = new AccountModel() + { + AccountType = AccountTypes.Personal, + Password = Global.Utilities.KeyGenerator.Generate(20, "pwd"), + Name = name, + Profile = "", + Visibility = Visibility.Visible, + Identity = new() + { + Unique = unique, + Type = IdentityType.Account + } + }; + accountNew = Persistence.Formatters.Account.Process(accountNew); + accountNew.IdentityService = Types.Cloud.Identity.Enumerations.IdentityService.Google; + // Crear nueva identidad y cuenta. + var create = await accountRepository.Create(accountNew, 0); + account = create; + break; + default: + return new() + { + Response = Responses.InvalidUser, + Message = "Ocurrió un error al iniciar sesión con Google." + }; + } + + if (account.Response != Responses.Success) + return new(Responses.Unauthorized) + { + Message = "Ocurrió un error en LIN Platform & Google." + }; + + if (account.Model.IdentityService != Types.Cloud.Identity.Enumerations.IdentityService.Google) + return new(Responses.Unauthorized) + { + Message = "La cuenta no esta vinculada con Google." + }; + + request.User = unique; + return await Task.FromResult(new ResponseBase(Responses.Success)); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 303d5aa..df155f3 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -117,4 +117,42 @@ public async Task> LoginWithToken() return response; } + + /// + /// Iniciar sesión con un tercero. + /// + /// Token de acceso a tercero. + [HttpGet("ThirdParty")] + public async Task> LoginWith([FromHeader] string token, [FromHeader] IdentityService provider) + { + + // Validar información del token. + var request = new AuthenticationRequest + { + ThirdPartyToken = token, + Service = provider, + StrictService = true + }; + + var response = await serviceAuth.Authenticate(request); + + if (response.Response != Responses.Success) + return new() + { + Response = response.Response, + Message = response.Message, + Errors = response.Errors + }; + + // Respuesta. + var http = new ReadOneResponse + { + Model = serviceAuth.Account!, + Response = Responses.Success, + Token = serviceAuth.GenerateToken() + }; + + return http; + } + } \ No newline at end of file From f54ee03698176b4f0c919bba450db8798174422a Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sun, 25 May 2025 09:45:45 -0500 Subject: [PATCH 173/178] update --- .../LIN.Cloud.Identity.Services.csproj | 24 +++++++++---------- .../ThirdParties/GoogleValidationService.cs | 2 +- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 1 - 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj index 7719e42..2dce8b6 100644 --- a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj +++ b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj @@ -1,17 +1,17 @@  - - net9.0 - enable - enable - + + net9.0 + enable + enable + - - - + + + - - - + + + - + \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs index 78294d4..839e2f0 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs @@ -27,7 +27,7 @@ public async Task Authenticate(AuthenticationRequest request) { Message = "El token es invalido o no esta firmado para Google." }; - + var account = await accountRepository.Read(unique, new() { FindOn = Persistence.Models.FindOn.AllAccounts, diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 9d201b9..932f093 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -20,5 +20,4 @@ - \ No newline at end of file From 1361beaf96631ebc439961c185bf5958b0da9588 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sun, 25 May 2025 14:07:19 -0500 Subject: [PATCH 174/178] =?UTF-8?q?Cuentas=20temporales.=20Dominios=20de?= =?UTF-8?q?=20organizaciones=20y=20verficaci=C3=B3n.=20Verificaci=C3=B3n?= =?UTF-8?q?=20de=20cuentas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Contexts/DataContext.cs | 27 +++ .../Extensions/PersistenceExtensions.cs | 2 + .../Formatters/Account.cs | 2 +- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../EntityFramework/Builders/Account.cs | 4 +- .../EntityFramework/DomainRepository.cs | 75 ++++++++ .../EntityFramework/IdentityRepository.cs | 17 ++ .../OrganizationMemberRepository.cs | 4 +- .../EntityFramework/OrganizationRepository.cs | 4 +- .../TemporalAccountRepository.cs | 71 +++++++ .../Repositories/IDomainRepository.cs | 8 + .../Repositories/IIdentityRepository.cs | 1 + .../ITemporalAccountRepository.cs | 8 + .../Extensions/IamExtensions.cs | 11 ++ .../Extensions/ServiceExtensions.cs | 2 + .../Interfaces/IDomainService.cs | 7 + .../LIN.Cloud.Identity.Services.csproj | 1 + .../IdentityValidationService.cs | 2 +- .../ThirdParties/GoogleValidationService.cs | 8 +- .../Services/DomainService.cs | 54 ++++++ .../Accounts/FederatedAccountController.cs | 178 ++++++++++++++++++ .../AuthenticationController.cs | 11 +- .../Areas/Organizations/DomainController.cs | 118 ++++++++++++ .../Areas/Policies/PoliciesController.cs | 2 - .../Policies/PoliciesIdentityController.cs | 2 - .../Services/Formats/Account.cs | 2 +- 26 files changed, 604 insertions(+), 19 deletions(-) create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/DomainRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/TemporalAccountRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/IDomainRepository.cs create mode 100644 LIN.Cloud.Identity.Persistence/Repositories/ITemporalAccountRepository.cs create mode 100644 LIN.Cloud.Identity.Services/Interfaces/IDomainService.cs create mode 100644 LIN.Cloud.Identity.Services/Services/DomainService.cs create mode 100644 LIN.Cloud.Identity/Areas/Accounts/FederatedAccountController.cs create mode 100644 LIN.Cloud.Identity/Areas/Organizations/DomainController.cs diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index bad5839..fc078ed 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -83,6 +83,16 @@ public class DataContext(DbContextOptions options) : DbContext(opti /// public DbSet IdentityPolicies { get; set; } + /// + /// Dominios. + /// + public DbSet Domains { get; set; } + + /// + /// Cuentas temporales. + /// + public DbSet TemporalAccounts { get; set; } + /// /// Crear el modelo en BD. /// @@ -220,6 +230,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.Id).IsRequired(); }); + // Policy Model + modelBuilder.Entity(entity => + { + entity.ToTable("temporal_accounts"); + entity.HasIndex(e => e.VerificationCode).IsUnique(); + }); + // Códigos OTPS. modelBuilder.Entity(entity => { @@ -256,6 +273,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasForeignKey(t => t.PolicyId); }); + modelBuilder.Entity(entity => + { + entity.ToTable("domains"); + entity.HasOne(t => t.Organization) + .WithMany() + .HasForeignKey(t => t.OrganizationId); + + entity.HasIndex(t => t.Domain).IsUnique(); + }); + modelBuilder.Entity(entity => { entity.ToTable("time_access_policy"); diff --git a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs index b7ed977..064adc1 100644 --- a/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs +++ b/LIN.Cloud.Identity.Persistence/Extensions/PersistenceExtensions.cs @@ -48,6 +48,8 @@ public static IServiceCollection AddPersistence(this IServiceCollection services services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); return services; } diff --git a/LIN.Cloud.Identity.Persistence/Formatters/Account.cs b/LIN.Cloud.Identity.Persistence/Formatters/Account.cs index 67ddd13..f82eea6 100644 --- a/LIN.Cloud.Identity.Persistence/Formatters/Account.cs +++ b/LIN.Cloud.Identity.Persistence/Formatters/Account.cs @@ -14,7 +14,6 @@ public static AccountModel Process(AccountModel baseAccount) return new AccountModel() { Id = 0, - IdentityService = IdentityService.LIN, Name = baseAccount.Name.Trim(), Profile = baseAccount.Profile, Password = Global.Utilities.Cryptography.Encrypt(baseAccount.Password), @@ -25,6 +24,7 @@ public static AccountModel Process(AccountModel baseAccount) { Id = 0, Status = IdentityStatus.Enable, + Provider = IdentityService.LIN, Type = IdentityType.Account, CreationTime = DateTime.UtcNow, EffectiveTime = DateTime.UtcNow, diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index 4774c9e..b8493ab 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -9,7 +9,7 @@ - + diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs index 2e5931a..b2e243e 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/Builders/Account.cs @@ -259,13 +259,13 @@ private static IQueryable BuildModel(IQueryable quer CreationTime = account.Identity.CreationTime, EffectiveTime = account.Identity.EffectiveTime, ExpirationTime = account.Identity.ExpirationTime, - Status = account.Identity.Status + Status = account.Identity.Status, + Provider = account.Identity.Provider, }, Password = account.Password, Visibility = account.Visibility, Profile = filters.IncludePhoto ? string.Empty : selector, IdentityId = account.Identity.Id, - IdentityService = account.IdentityService, AccountType = account.AccountType }; diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/DomainRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/DomainRepository.cs new file mode 100644 index 0000000..366cfb5 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/DomainRepository.cs @@ -0,0 +1,75 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; + +internal class DomainRepository(DataContext context) : IDomainRepository +{ + + /// + /// Agregar un dominio. + /// + /// Modelo del dominio.. + public async Task Create(DomainModel modelo) + { + try + { + // La organización ya existe. + modelo.Organization = context.AttachOrUpdate(modelo.Organization); + await context.Domains.AddAsync(modelo); + context.SaveChanges(); + return new(Responses.Success, modelo.Id); + } + catch (Exception) + { + return new(); + } + } + + + /// + /// Obtener un dominio por su unique. + /// + /// Id de la organización. + public async Task> Read(string unique) + { + try + { + // Consultar. + var domain = await (from g in context.Domains + where g.Domain == unique + select g).FirstOrDefaultAsync(); + + // Si la cuenta no existe. + if (domain is null) + return new(Responses.NotRows); + + // Success. + return new(Responses.Success, domain); + } + catch (Exception) + { + return new(Responses.NotRows); + } + + } + + + /// + /// Verificar un dominio por su unique. + /// + public async Task Verify(string unique) + { + try + { + var identityId = await (from g in context.Domains + where g.Domain == unique + select g).ExecuteUpdateAsync(t => t.SetProperty(t => t.IsVerified, true)); + + // Success. + return new(Responses.Success); + } + catch (Exception) + { + return new(); + } + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRepository.cs index 74469dd..c875c75 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/IdentityRepository.cs @@ -58,6 +58,23 @@ public async Task> Read(int id, QueryIdentityFilt } + /// + /// Validar si existe una identidad según el Unique. + /// + public async Task> Exist(string unique) + { + try + { + bool exist = await context.Identities.AnyAsync(x => x.Unique == unique); + return new(Responses.Success, exist); + } + catch (Exception) + { + return new(Responses.Undefined); + } + } + + /// /// Obtener una identidad según el Unique. /// diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs index 1a6a40f..e1ccee8 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationMemberRepository.cs @@ -193,11 +193,11 @@ on gm.IdentityId equals a.IdentityId Id = a.Id, Name = a.Name, Visibility = a.Visibility, - IdentityService = a.IdentityService, Identity = new() { Id = a.Identity.Id, - Unique = a.Identity.Unique + Unique = a.Identity.Unique, + Provider = a.Identity.Provider, } }, Profile = gm diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs index 9d7eff4..803f17d 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs @@ -36,9 +36,9 @@ public async Task Create(OrganizationModel modelo) Visibility = Visibility.Hidden, Name = "Admin", Password = $"pwd@{DateTime.UtcNow.Year}", - IdentityService = IdentityService.LIN, Identity = new IdentityModel() - { + { + Provider = IdentityService.LIN, Status = IdentityStatus.Enable, CreationTime = DateTime.UtcNow, EffectiveTime = DateTime.UtcNow, diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/TemporalAccountRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/TemporalAccountRepository.cs new file mode 100644 index 0000000..8be4185 --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/TemporalAccountRepository.cs @@ -0,0 +1,71 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories.EntityFramework; + +internal class TemporalAccountRepository(DataContext context) : ITemporalAccountRepository +{ + + /// + /// Crear nueva cuenta temporal. + /// + public async Task Create(TemporalAccountModel modelo) + { + modelo.Id = 0; + try + { + // Guardar la identidad. + await context.TemporalAccounts.AddAsync(modelo); + context.SaveChanges(); + + return new(Responses.Success, modelo.Id); + } + catch (Exception) + { + return new(Responses.ResourceExist); + } + } + + + /// + /// Obtener una cuenta temporal por código de verificación. + /// + /// Código temporal. + public async Task> ReadWithCode(string verificationCode) + { + try + { + var temporalAccount = await context.TemporalAccounts + .FirstOrDefaultAsync(x => x.VerificationCode == verificationCode); + + // Si la cuenta no existe. + if (temporalAccount == null) + return new(Responses.NotRows); + + // Success. + return new(Responses.Success, temporalAccount); + } + catch (Exception) + { + return new(); + } + } + + + /// + /// Eliminar una cuenta temporal. + /// + /// Id de la cuenta temporal. + public async Task Delete(int id) + { + try + { + var count = await context.TemporalAccounts.Where(t => t.Id == id).ExecuteDeleteAsync(); + + // Success. + return new(Responses.Success); + } + catch (Exception) + { + return new(); + } + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IDomainRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IDomainRepository.cs new file mode 100644 index 0000000..a54ef0f --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/IDomainRepository.cs @@ -0,0 +1,8 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface IDomainRepository +{ + Task Create(DomainModel modelo); + Task> Read(string unique); + Task Verify(string unique); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRepository.cs index 3b5ef16..857edb8 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/IIdentityRepository.cs @@ -4,5 +4,6 @@ public interface IIdentityRepository { Task> Create(IdentityModel modelo); Task> Read(int id, QueryIdentityFilter filters); + Task> Exist(string unique); Task> Read(string unique, QueryIdentityFilter filters); } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Persistence/Repositories/ITemporalAccountRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/ITemporalAccountRepository.cs new file mode 100644 index 0000000..d7009ac --- /dev/null +++ b/LIN.Cloud.Identity.Persistence/Repositories/ITemporalAccountRepository.cs @@ -0,0 +1,8 @@ +namespace LIN.Cloud.Identity.Persistence.Repositories; + +public interface ITemporalAccountRepository +{ + Task Create(TemporalAccountModel modelo); + Task Delete(int id); + Task> ReadWithCode(string verificationCode); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs index d91ba32..6f8c99b 100644 --- a/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs +++ b/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs @@ -100,4 +100,15 @@ public static bool ValidateReadPolicies(this IEnumerable roles) return sets.Any(); } + public static bool ValidateAlterDomain(this IEnumerable roles) + { + List availed = + [ + Roles.Administrator + ]; + + var sets = availed.Intersect(roles); + return sets.Any(); + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs index e32882b..e1beb1e 100644 --- a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs +++ b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs @@ -36,6 +36,8 @@ public static IServiceCollection AddAuthenticationServices(this IServiceCollecti services.AddScoped(); services.AddScoped(); + services.AddSingleton(); + JwtService.Open(configuration); JwtApplicationsService.Open(configuration); diff --git a/LIN.Cloud.Identity.Services/Interfaces/IDomainService.cs b/LIN.Cloud.Identity.Services/Interfaces/IDomainService.cs new file mode 100644 index 0000000..3642c67 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Interfaces/IDomainService.cs @@ -0,0 +1,7 @@ +namespace LIN.Cloud.Identity.Services.Interfaces; + +public interface IDomainService +{ + Task VerifyDns(string domain, string code); + bool VerifyDomain(string dominio); +} \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj index 2dce8b6..02a81a7 100644 --- a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj +++ b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj @@ -8,6 +8,7 @@ + diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs index 82af837..92ce9fc 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/IdentityValidationService.cs @@ -34,7 +34,7 @@ public async Task Authenticate(AuthenticationRequest request) Message = "La identidad de la cuenta de usuario no se encuentra activa." }; - if (request.StrictService && account.IdentityService != account.IdentityService) + if (request.StrictService && account.Identity.Provider != request.Service) return new ResponseBase { Response = Responses.Unauthorized, diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs index 839e2f0..2661329 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs @@ -1,5 +1,6 @@ using FirebaseAdmin.Auth; using LIN.Types.Cloud.Identity.Models.Identities; +using System.Collections.Immutable; namespace LIN.Cloud.Identity.Services.Services.Authentication.ThirdParties; @@ -18,7 +19,8 @@ public async Task Authenticate(AuthenticationRequest request) // Validar que la identidad de usuario exista. string unique = information.Claims["email"].ToString() ?? string.Empty; string name = information.Claims["name"].ToString() ?? string.Empty; - string? provider = information.Claims["firebase"] is Dictionary firebaseClaims + + string? provider = information.Claims["firebase"] is Newtonsoft.Json.Linq.JObject firebaseClaims ? firebaseClaims["sign_in_provider"]?.ToString() : "unknown"; @@ -53,7 +55,7 @@ public async Task Authenticate(AuthenticationRequest request) } }; accountNew = Persistence.Formatters.Account.Process(accountNew); - accountNew.IdentityService = Types.Cloud.Identity.Enumerations.IdentityService.Google; + accountNew.Identity.Provider = Types.Cloud.Identity.Enumerations.IdentityService.Google; // Crear nueva identidad y cuenta. var create = await accountRepository.Create(accountNew, 0); account = create; @@ -72,7 +74,7 @@ public async Task Authenticate(AuthenticationRequest request) Message = "Ocurrió un error en LIN Platform & Google." }; - if (account.Model.IdentityService != Types.Cloud.Identity.Enumerations.IdentityService.Google) + if (account.Model.Identity.Provider != Types.Cloud.Identity.Enumerations.IdentityService.Google) return new(Responses.Unauthorized) { Message = "La cuenta no esta vinculada con Google." diff --git a/LIN.Cloud.Identity.Services/Services/DomainService.cs b/LIN.Cloud.Identity.Services/Services/DomainService.cs new file mode 100644 index 0000000..6a79230 --- /dev/null +++ b/LIN.Cloud.Identity.Services/Services/DomainService.cs @@ -0,0 +1,54 @@ +using DnsClient; +using System.Text.RegularExpressions; + +namespace LIN.Cloud.Identity.Services.Services; + +internal class DomainService : IDomainService +{ + + /// + /// Verifica si el dominio es válido. + /// + /// Dominio. + public bool VerifyDomain(string dominio) + { + string patron = @"^(?!\-)([a-zA-Z0-9\-]{1,63}(? + /// Validar DNS. + /// + /// Dominio. + /// Código de verificación. + public async Task VerifyDns(string domain, string code) + { + var lookup = new LookupClient(); + + try + { + var resultado = await lookup.QueryAsync(domain, QueryType.TXT); + var txtRecords = resultado.Answers.TxtRecords(); + + if (txtRecords.Any()) + { + foreach (var record in txtRecords) + { + foreach (var text in record.Text) + { + if (text == code) + return true; // Registro TXT encontrado y verificado + } + } + } + return false; + + } + catch (Exception) + { + } + return false; // Registro TXT no encontrado o no coincide con el código + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Accounts/FederatedAccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/FederatedAccountController.cs new file mode 100644 index 0000000..9e7b170 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Accounts/FederatedAccountController.cs @@ -0,0 +1,178 @@ +using System.Text.RegularExpressions; + +namespace LIN.Cloud.Identity.Areas.Accounts; + +[Route("[controller]")] +public class FederatedAccountController(ITemporalAccountRepository temporalAccountRepository, IIamService iamService, IDomainRepository domainRepository, IAccountRepository accountRepository, IIdentityRepository identityRepository, EmailSender emailSender, IGroupMemberRepository groupMemberRepository, IOrganizationRepository organizationRepository) : AuthenticationBaseController +{ + + /// + /// Agrega una cuenta federada a la organización. + /// + /// Id de la organización. + /// Correo electrónico. + /// Proveedor. + [HttpPost] + [IdentityToken] + public async Task AddFederatedAccount([FromHeader] int organization, [FromQuery] string email, [FromQuery] IdentityService provider) + { + // 1. Obtener el dominio del correo + var match = Regex.Match(email, @"@(?[^@]+)$"); + if (!match.Success) + return new(Responses.InvalidParam) + { + Message = "El correo electrónico no es válido." + }; + + var domain = match.Groups["domain"].Value.ToLowerInvariant(); + + // 2. Validar si la organización es dueña del dominio + var domainResponse = await domainRepository.Read(domain); + bool isOrgOwner = domainResponse.Response == Responses.Success && + domainResponse.Model.OrganizationId == organization && + domainResponse.Model.IsVerified; + + // Validar que no exista una cuenta federada con el mismo correo. + var exist = await identityRepository.Exist(email); + + if (exist.Response == Responses.Success && exist.Model) + return new(Responses.ExistAccount) + { + Message = "Ya existe una cuenta federada con este correo." + }; + + if (!isOrgOwner) + { + + // Crear la cuenta temporal. + var tempAccount = new TemporalAccountModel + { + Email = email, + Name = "Cuenta Federada " + email, + Provider = provider, + VerificationCode = Global.Utilities.KeyGenerator.Generate(9, "code."), + OrganizationId = organization + }; + + // Guardar la cuenta temporal. + await temporalAccountRepository.Create(tempAccount); + + // Enviar correo de invitación a la organización. + await emailSender.Send(email, "Verifica tu cuenta federada", $"Por favor verifica tu correo para continuar. {tempAccount.VerificationCode}"); + return new(Responses.Success) + { + Message = "Se ha enviado un correo de verificación a " + email + ". Por favor verifica tu cuenta." + }; + } + + var roles = await iamService.Validate(UserInformation.IdentityId, organization); + if (!ValidateRoles.ValidateAlterMembers(roles)) + return new(Responses.Unauthorized) + { + Message = "No tienes permisos para agregar cuentas federadas internas." + }; + + // Crear la cuenta federada. + var accountModel = new AccountModel + { + AccountType = AccountTypes.Work, + Identity = new IdentityModel + { + Unique = email, + Type = IdentityType.Account + }, + Name = "Cuenta Federada " + email, + Password = Global.Utilities.KeyGenerator.Generate(20, "pwd"), + Visibility = Visibility.Visible + }; + + accountModel = Services.Formats.Account.Process(accountModel); + accountModel.Identity.Provider = provider; + + // Crear la cuenta. + var accountResponse = await accountRepository.Create(accountModel, organization); + + if (accountResponse.Response != Responses.Success) + return new(Responses.Undefined) + { + Message = "Error al crear la cuenta federada." + }; + + return new(Responses.Success) + { + Message = "Cuenta federada creada exitosamente." + }; + } + + + /// + /// Verificar una cuenta de terceros y agregarla al directorio de una organización. + /// + [HttpGet("verify")] + public async Task Verify([FromQuery] string code) + { + // Validar si existe una cuenta temporal con el código. + var temporalResponse = await temporalAccountRepository.ReadWithCode(code); + + if (temporalResponse.Response != Responses.Success) + return new(Responses.NotRows) + { + Message = "No se encontró una cuenta con el código proporcionado." + }; + + // Validar que no exista una cuenta con la misma identidad. + var accountExist = await accountRepository.Read(temporalResponse.Model.Email, new() { FindOn = FindOn.AllAccounts }); + + if (accountExist.Response != Responses.Success) + { + // No existe una cuenta con la misma identidad, se procede a crear la cuenta federada. + var accountModel = new AccountModel + { + AccountType = AccountTypes.Work, + Identity = new IdentityModel + { + Unique = temporalResponse.Model.Email, + Type = IdentityType.Account, + Provider = temporalResponse.Model.Provider + }, + Name = temporalResponse.Model.Name, + Password = Global.Utilities.KeyGenerator.Generate(20, "pwd"), + Visibility = Visibility.Visible, + }; + + accountModel = Services.Formats.Account.Process(accountModel); + accountModel.Identity.Provider = temporalResponse.Model.Provider; + + // Crear la cuenta. + var accountResponse = await accountRepository.Create(accountModel, 0); + accountExist = accountResponse; + } + + // Validar que la cuenta se haya creado correctamente. + if (accountExist.Response != Responses.Success) + return new(Responses.Undefined) + { + Message = "Error al crear la cuenta federada." + }; + + // Eliminar la cuenta temporal. + await temporalAccountRepository.Delete(temporalResponse.Model.Id); + + // Suscribir la cuenta al directorio de la organización. + if (temporalResponse.Model.OrganizationId > 0) + { + // Obtener el directorio de la organización. + var organization = await organizationRepository.ReadDirectory(temporalResponse.Model.OrganizationId); + + // Ingresarla a la organización. + await groupMemberRepository.Create([new() { + Group = new() { Id = organization.Model }, + Identity = new() { Id = accountExist.Model.IdentityId }, + Type = GroupMemberTypes.Guest + }]); + } + + return new(Responses.Success); + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index df155f3..46c48d3 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -123,15 +123,22 @@ public async Task> LoginWithToken() /// /// Token de acceso a tercero. [HttpGet("ThirdParty")] - public async Task> LoginWith([FromHeader] string token, [FromHeader] IdentityService provider) + public async Task> LoginWith([FromHeader] string token, [FromHeader] IdentityService provider, [FromHeader] string application) { + if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(application)) + return new(Responses.InvalidParam) + { + Message = "Uno o varios parámetros son invalido." + }; + // Validar información del token. var request = new AuthenticationRequest { ThirdPartyToken = token, Service = provider, - StrictService = true + StrictService = true, + Application = application }; var response = await serviceAuth.Authenticate(request); diff --git a/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs b/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs new file mode 100644 index 0000000..886b867 --- /dev/null +++ b/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs @@ -0,0 +1,118 @@ +using DnsClient; +using System.Text.RegularExpressions; + +namespace LIN.Cloud.Identity.Areas.Organizations; + +[IdentityToken] +[Route("[controller]")] +public class DomainController(IDomainRepository domainRepository, IIamService iamService, IDomainService domainService) : AuthenticationBaseController +{ + + /// + /// Agrega un dominio a una organización. + /// + [HttpPost] + public async Task AddDomain([FromHeader] int organization, [FromQuery] string domain) + { + + // Validar dominio sea valido. + if (!domainService.VerifyDomain(domain)) + return new(Responses.InvalidParam) + { + Message = "El dominio no es valido." + }; + + // Validar si el usuario tiene permisos sobre la organización. + var roles = await iamService.Validate(base.UserInformation.IdentityId, organization); + + if (!roles.ValidateAlterDomain()) + return new(Responses.Unauthorized) + { + Message = "No tienes permisos para agregar un dominio a esta organización." + }; + + // Código de verificación para el dominio. + string verificationCode = Guid.NewGuid().ToString("N").ToLowerInvariant(); + + // Crear el dominio en la organización. + var modelo = new DomainModel + { + Organization = new() { Id = organization }, + Domain = domain.ToLowerInvariant(), + IsVerified = false, + VerificationCode = verificationCode + }; + + var response = await domainRepository.Create(modelo); + + if (response.Response != Responses.Success) + return new(Responses.Unauthorized) + { + Message = "No se pudo crear el dominio. Intenta nuevamente." + }; + + // Retornar el código para el TXT del dominio. + return new(Responses.Success) + { + LastUnique = verificationCode, + Message = "Dominio creado correctamente. Agrega el TXT con el código de verificación en tu dominio." + }; + } + + + /// + /// Verifica un dominio de una organización. + /// + [HttpPatch] + public async Task Verify([FromHeader] int organization, [FromQuery] string domain) + { + // Validar dominio sea valido. + if (!domainService.VerifyDomain(domain)) + return new(Responses.InvalidParam) + { + Message = "El dominio no es valido." + }; + + // Validar si el usuario tiene permisos sobre la organización. + var roles = await iamService.Validate(base.UserInformation.IdentityId, organization); + + if (!roles.ValidateAlterDomain()) + return new(Responses.Unauthorized) + { + Message = "No tienes permisos para agregar un dominio a esta organización." + }; + + // Obtener el dominio desde la base de datos. + var response = await domainRepository.Read(domain.ToLowerInvariant()); + + if (response.Response != Responses.Success) + return new(Responses.NotRows) + { + Message = "El dominio no existe o no pertenece a la organización." + }; + + // Verificar el TXT. + var isSuccess = await domainService.VerifyDns(domain, response.Model.VerificationCode); + + if (!isSuccess) + return new(Responses.Unauthorized) + { + Message = "El dominio no ha sido verificado. Asegúrate de agregar el registro TXT con el código de verificación." + }; + + // Es valido, se marca el dominio como verificado. + var verifyResponse = await domainRepository.Verify(domain.ToLowerInvariant()); + + // Si no es valido, se retorna un error. + return verifyResponse.Response == Responses.Success + ? new(Responses.Success) + { + Message = "Dominio verificado correctamente." + } + : new(Responses.Unauthorized) + { + Message = "No se pudo verificar el dominio. Intenta nuevamente." + }; + } + +} \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs index bde4b86..66ca81b 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesController.cs @@ -12,7 +12,6 @@ public class PoliciesController(IPolicyRepository policiesData, IIamService iam) [HttpPost] public async Task Create([FromBody] PolicyModel modelo, [FromHeader] int organization) { - // Validar nivel de acceso y roles sobre la organización. var validate = await iam.Validate(UserInformation.IdentityId, organization); @@ -42,7 +41,6 @@ public async Task Create([FromBody] PolicyModel modelo, [Fro [HttpGet] public async Task> Read([FromHeader] int policyId) { - // Validar nivel de acceso y roles sobre la organización. var validate = await iam.IamPolicy(UserInformation.IdentityId, policyId); diff --git a/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs b/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs index 47864d0..e622e36 100644 --- a/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Policies/PoliciesIdentityController.cs @@ -12,7 +12,6 @@ public class PoliciesIdentityController(IPolicyMemberRepository policiesData, II [HttpPost] public async Task Create([FromBody] IdentityPolicyModel modelo) { - // Validar nivel de acceso y roles sobre la organización. var validate = await iam.IamIdentity(UserInformation.IdentityId, modelo.IdentityId); @@ -38,7 +37,6 @@ public async Task Create([FromBody] IdentityPolicyModel mode [HttpGet("all")] public async Task> ReadAll([FromHeader] int identity) { - // Validar nivel de acceso y roles sobre la organización. var validate = await iam.IamIdentity(UserInformation.IdentityId, identity); diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index 35e1b5b..ed524f9 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -66,7 +66,6 @@ public static AccountModel Process(AccountModel baseAccount) return new AccountModel() { Id = 0, - IdentityService = IdentityService.LIN, Name = baseAccount.Name.Trim(), Profile = baseAccount.Profile, Password = Global.Utilities.Cryptography.Encrypt(baseAccount.Password), @@ -82,6 +81,7 @@ public static AccountModel Process(AccountModel baseAccount) EffectiveTime = DateTime.UtcNow, ExpirationTime = DateTime.UtcNow.AddYears(5), Roles = [], + Provider = IdentityService.LIN, Unique = baseAccount.Identity.Unique.Trim() } }; From cbf962ac6314e0f553cd80a33c0ed53cbd712f26 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sun, 25 May 2025 14:26:29 -0500 Subject: [PATCH 175/178] mejoras generales. --- LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs | 2 +- .../Repositories/EntityFramework/OrganizationRepository.cs | 2 +- .../Authentication/ThirdParties/GoogleValidationService.cs | 1 - LIN.Cloud.Identity/Areas/Accounts/AccountController.cs | 2 -- .../Areas/Accounts/FederatedAccountController.cs | 4 +--- .../Areas/Applications/ApplicationsController.cs | 4 +--- .../Areas/Authentication/AuthenticationController.cs | 2 +- .../Areas/Authentication/AuthenticationV4Controller.cs | 2 -- LIN.Cloud.Identity/Areas/Organizations/DomainController.cs | 5 +---- .../Services/Filters/IdentityTokenAttribute.cs | 3 +-- LIN.Cloud.Identity/Services/Formats/Account.cs | 1 - LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs | 4 +--- LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs | 4 +--- LIN.Cloud.Identity/Usings.cs | 4 +++- 14 files changed, 12 insertions(+), 28 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs index fc078ed..1fcbeda 100644 --- a/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs +++ b/LIN.Cloud.Identity.Persistence/Contexts/DataContext.cs @@ -236,7 +236,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.ToTable("temporal_accounts"); entity.HasIndex(e => e.VerificationCode).IsUnique(); }); - + // Códigos OTPS. modelBuilder.Entity(entity => { diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs index 803f17d..44e8b78 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/OrganizationRepository.cs @@ -37,7 +37,7 @@ public async Task Create(OrganizationModel modelo) Name = "Admin", Password = $"pwd@{DateTime.UtcNow.Year}", Identity = new IdentityModel() - { + { Provider = IdentityService.LIN, Status = IdentityStatus.Enable, CreationTime = DateTime.UtcNow, diff --git a/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs b/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs index 2661329..9dc7361 100644 --- a/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs +++ b/LIN.Cloud.Identity.Services/Services/Authentication/ThirdParties/GoogleValidationService.cs @@ -1,6 +1,5 @@ using FirebaseAdmin.Auth; using LIN.Types.Cloud.Identity.Models.Identities; -using System.Collections.Immutable; namespace LIN.Cloud.Identity.Services.Services.Authentication.ThirdParties; diff --git a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs index 7b88ee3..5b5ef68 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/AccountController.cs @@ -1,5 +1,3 @@ -using LIN.Cloud.Identity.Services.Services; - namespace LIN.Cloud.Identity.Areas.Accounts; [Route("[controller]")] diff --git a/LIN.Cloud.Identity/Areas/Accounts/FederatedAccountController.cs b/LIN.Cloud.Identity/Areas/Accounts/FederatedAccountController.cs index 9e7b170..d34fa0d 100644 --- a/LIN.Cloud.Identity/Areas/Accounts/FederatedAccountController.cs +++ b/LIN.Cloud.Identity/Areas/Accounts/FederatedAccountController.cs @@ -1,6 +1,4 @@ -using System.Text.RegularExpressions; - -namespace LIN.Cloud.Identity.Areas.Accounts; +namespace LIN.Cloud.Identity.Areas.Accounts; [Route("[controller]")] public class FederatedAccountController(ITemporalAccountRepository temporalAccountRepository, IIamService iamService, IDomainRepository domainRepository, IAccountRepository accountRepository, IIdentityRepository identityRepository, EmailSender emailSender, IGroupMemberRepository groupMemberRepository, IOrganizationRepository organizationRepository) : AuthenticationBaseController diff --git a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs index a1e1ebe..6459624 100644 --- a/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs +++ b/LIN.Cloud.Identity/Areas/Applications/ApplicationsController.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Services; - -namespace LIN.Cloud.Identity.Areas.Applications; +namespace LIN.Cloud.Identity.Areas.Applications; [Route("applications")] public class ApplicationsController(IApplicationRepository application) : AuthenticationBaseController diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs index 46c48d3..8fa16bf 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationController.cs @@ -130,7 +130,7 @@ public async Task> LoginWith([FromHeader] stri return new(Responses.InvalidParam) { Message = "Uno o varios parámetros son invalido." - }; + }; // Validar información del token. var request = new AuthenticationRequest diff --git a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationV4Controller.cs b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationV4Controller.cs index fe0f50d..55bca60 100644 --- a/LIN.Cloud.Identity/Areas/Authentication/AuthenticationV4Controller.cs +++ b/LIN.Cloud.Identity/Areas/Authentication/AuthenticationV4Controller.cs @@ -1,5 +1,3 @@ -using LIN.Cloud.Identity.Services.Services; - namespace LIN.Cloud.Identity.Areas.Authentication; [Route("V4/[controller]")] diff --git a/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs b/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs index 886b867..1cd4048 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs @@ -1,7 +1,4 @@ -using DnsClient; -using System.Text.RegularExpressions; - -namespace LIN.Cloud.Identity.Areas.Organizations; +namespace LIN.Cloud.Identity.Areas.Organizations; [IdentityToken] [Route("[controller]")] diff --git a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs index 9dc174d..184b2e1 100644 --- a/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs +++ b/LIN.Cloud.Identity/Services/Filters/IdentityTokenAttribute.cs @@ -1,5 +1,4 @@ -using LIN.Cloud.Identity.Services.Services; -using LIN.Types.Models; +using LIN.Types.Models; namespace LIN.Cloud.Identity.Services.Filters; diff --git a/LIN.Cloud.Identity/Services/Formats/Account.cs b/LIN.Cloud.Identity/Services/Formats/Account.cs index ed524f9..bf96e14 100644 --- a/LIN.Cloud.Identity/Services/Formats/Account.cs +++ b/LIN.Cloud.Identity/Services/Formats/Account.cs @@ -1,5 +1,4 @@ using LIN.Types.Models; -using System.Text.RegularExpressions; using IdentityService = LIN.Types.Cloud.Identity.Enumerations.IdentityService; namespace LIN.Cloud.Identity.Services.Formats; diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index ec5f30a..d3c9b81 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Services; - -namespace LIN.Cloud.Identity.Services.Realtime; +namespace LIN.Cloud.Identity.Services.Realtime; public partial class PassKeyHub(IAccountLogRepository accountLogs) : Hub { diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index d4bf839..e21d977 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -1,6 +1,4 @@ -using LIN.Cloud.Identity.Services.Services; - -namespace LIN.Cloud.Identity.Services.Realtime; +namespace LIN.Cloud.Identity.Services.Realtime; public partial class PassKeyHub { diff --git a/LIN.Cloud.Identity/Usings.cs b/LIN.Cloud.Identity/Usings.cs index a39c0f9..8f5c997 100644 --- a/LIN.Cloud.Identity/Usings.cs +++ b/LIN.Cloud.Identity/Usings.cs @@ -9,6 +9,7 @@ global using LIN.Cloud.Identity.Services.Filters; global using LIN.Cloud.Identity.Services.Interfaces; global using LIN.Cloud.Identity.Services.Models; +global using LIN.Cloud.Identity.Services.Services; global using LIN.Cloud.Identity.Services.Utils; global using LIN.Types.Cloud.Identity.Enumerations; global using LIN.Types.Cloud.Identity.Models; @@ -20,4 +21,5 @@ global using Microsoft.AspNetCore.Mvc.Filters; global using Microsoft.AspNetCore.SignalR; // SQL. -global using Microsoft.EntityFrameworkCore; \ No newline at end of file +global using Microsoft.EntityFrameworkCore; +global using System.Text.RegularExpressions; From c41d7c661a060cb5ae4e55faa84c1483f2358de8 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sun, 25 May 2025 17:41:39 -0500 Subject: [PATCH 176/178] mejoras generales. --- ARCHITECTURE.md | 0 .../EntityFramework/AccountLogRepository.cs | 1 - .../EntityFramework/DomainRepository.cs | 25 ++++++++++++++++++- .../Repositories/IDomainRepository.cs | 1 + .../Extensions/IamExtensions.cs | 14 +++++++++++ .../Areas/Organizations/DomainController.cs | 23 +++++++++++++++++ .../Areas/Organizations/IdentityController.cs | 3 +++ .../wwwroot/seeds/applications.json | 2 +- 8 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..e69de29 diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs index 05c35c3..4bcbaa7 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs @@ -14,7 +14,6 @@ public async Task Create(AccountLog log) try { - // Organizar el modelo. log.Account = new() { Id = log.AccountId }; diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/DomainRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/DomainRepository.cs index 366cfb5..648d064 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/DomainRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/DomainRepository.cs @@ -27,7 +27,7 @@ public async Task Create(DomainModel modelo) /// /// Obtener un dominio por su unique. /// - /// Id de la organización. + /// Dominio. public async Task> Read(string unique) { try @@ -52,6 +52,29 @@ public async Task> Read(string unique) } + /// + /// Obtener los dominios. + /// + /// Id de la organización. + public async Task> ReadAll(int id) + { + try + { + // Consultar. + var domain = await (from g in context.Domains + where g.OrganizationId == id + select g).ToListAsync(); + + // Success. + return new(Responses.Success, domain); + } + catch (Exception) + { + return new(Responses.NotRows); + } + } + + /// /// Verificar un dominio por su unique. /// diff --git a/LIN.Cloud.Identity.Persistence/Repositories/IDomainRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/IDomainRepository.cs index a54ef0f..c6586d1 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/IDomainRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/IDomainRepository.cs @@ -4,5 +4,6 @@ public interface IDomainRepository { Task Create(DomainModel modelo); Task> Read(string unique); + Task> ReadAll(int id); Task Verify(string unique); } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs index 6f8c99b..1890a06 100644 --- a/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs +++ b/LIN.Cloud.Identity.Services/Extensions/IamExtensions.cs @@ -111,4 +111,18 @@ public static bool ValidateAlterDomain(this IEnumerable roles) return sets.Any(); } + public static bool ValidateReadDomains(this IEnumerable roles) + { + List availed = + [ + Roles.Administrator, + Roles.Manager, + Roles.AccountOperator, + Roles.SecurityViewer + ]; + + var sets = availed.Intersect(roles); + return sets.Any(); + } + } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs b/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs index 1cd4048..5cd51f3 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/DomainController.cs @@ -57,6 +57,29 @@ public async Task AddDomain([FromHeader] int organization, [From } + /// + /// Obtener los dominios de una organización. + /// + [HttpGet] + public async Task> ReadAll([FromHeader] int organization) + { + + // Validar si el usuario tiene permisos sobre la organización. + var roles = await iamService.Validate(UserInformation.IdentityId, organization); + + if (!roles.ValidateAlterDomain()) + return new(Responses.Unauthorized) + { + Message = "No tienes permisos para agregar un dominio a esta organización." + }; + + // Obtener los dominios de la organización. + var domainsResponse = await domainRepository.ReadAll(organization); + + return domainsResponse; + } + + /// /// Verifica un dominio de una organización. /// diff --git a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs index ffbefef..9ffd149 100644 --- a/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs +++ b/LIN.Cloud.Identity/Areas/Organizations/IdentityController.cs @@ -11,6 +11,9 @@ public class IdentityController(IOrganizationMemberRepository directoryMembersDa [HttpPost] public async Task Create([FromBody] IdentityRolesModel rolModel) { + // Validar el modelo. + if (rolModel.Rol == Roles.None) + return new(Responses.InvalidParam); // Confirmar el rol. var roles = await rolesIam.Validate(UserInformation.IdentityId, rolModel.OrganizationId); diff --git a/LIN.Cloud.Identity/wwwroot/seeds/applications.json b/LIN.Cloud.Identity/wwwroot/seeds/applications.json index 03abca6..7b6e591 100644 --- a/LIN.Cloud.Identity/wwwroot/seeds/applications.json +++ b/LIN.Cloud.Identity/wwwroot/seeds/applications.json @@ -1,4 +1,4 @@ -[ + [ { "id": 0, "name": "LIN Inventory", From 621fb0c191750e8454645021dcb5262fd6c19325 Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Wed, 30 Jul 2025 20:20:13 -0500 Subject: [PATCH 177/178] =?UTF-8?q?Actualiza=20versiones=20de=20paquetes?= =?UTF-8?q?=20y=20limpia=20c=C3=B3digo=20innecesario?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Se actualizó la versión de varios paquetes en los archivos de proyecto: - `Microsoft.EntityFrameworkCore.SqlServer` de `9.0.5` a `9.0.7` - `FirebaseAdmin` de `3.2.0` a `3.3.0` - `Azure.Identity` de `1.14.0` a `1.14.2` - `Http.Utils` de `4.6.0` a `4.7.0` Además, se eliminaron bloques de código innecesarios en varios métodos que no afectaban la lógica del programa. --- .../LIN.Cloud.Identity.Persistence.csproj | 2 +- .../Repositories/EntityFramework/AccountLogRepository.cs | 1 - .../LIN.Cloud.Identity.Services.csproj | 2 +- .../Services/JwtApplicationsService.cs | 1 - LIN.Cloud.Identity.Services/Services/JwtService.cs | 2 -- LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 4 ++-- LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs | 3 --- LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs | 2 -- 8 files changed, 4 insertions(+), 13 deletions(-) diff --git a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj index b8493ab..4ebd4e3 100644 --- a/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj +++ b/LIN.Cloud.Identity.Persistence/LIN.Cloud.Identity.Persistence.csproj @@ -12,7 +12,7 @@ - + diff --git a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs index 4bcbaa7..0c64e1d 100644 --- a/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs +++ b/LIN.Cloud.Identity.Persistence/Repositories/EntityFramework/AccountLogRepository.cs @@ -102,7 +102,6 @@ public async Task> Count(int id) { return new(Responses.NotRows); } - } } \ No newline at end of file diff --git a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj index 02a81a7..ed791f8 100644 --- a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj +++ b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj @@ -7,7 +7,7 @@ - + diff --git a/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs b/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs index b5c0c71..4617f17 100644 --- a/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs +++ b/LIN.Cloud.Identity.Services/Services/JwtApplicationsService.cs @@ -98,7 +98,6 @@ public static int Validate(string token) // Devuelve una respuesta exitosa return appID; - } catch (SecurityTokenException) { diff --git a/LIN.Cloud.Identity.Services/Services/JwtService.cs b/LIN.Cloud.Identity.Services/Services/JwtService.cs index 4ceb591..c9da720 100644 --- a/LIN.Cloud.Identity.Services/Services/JwtService.cs +++ b/LIN.Cloud.Identity.Services/Services/JwtService.cs @@ -123,6 +123,4 @@ public static JwtModel Validate(string token) IsAuthenticated = false }; } - - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 932f093..55e58f0 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,10 +9,10 @@ - + - + diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs index d3c9b81..707ece0 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHub.cs @@ -52,7 +52,6 @@ public override Task OnDisconnectedAsync(Exception? exception) /// public async Task SendRequest(PassKeyModel modelo) { - var pass = new PassKeyModel() { Expiration = modelo.Expiration, @@ -73,7 +72,6 @@ public async Task ReceiveRequest(PassKeyModel modelo) { try { - // Obtener información del token. JwtModel accountJwt = JwtService.Validate(modelo.Token); @@ -155,5 +153,4 @@ await accountLogs.Create(new() { } } - } \ No newline at end of file diff --git a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs index e21d977..28b15cc 100644 --- a/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs +++ b/LIN.Cloud.Identity/Services/Realtime/PassKeyHubActions.cs @@ -8,7 +8,6 @@ public partial class PassKeyHub /// public async Task JoinAdmin(string token) { - // Obtener información del token. var tokenInformation = JwtService.Validate(token); @@ -18,7 +17,6 @@ public async Task JoinAdmin(string token) // Grupo de la cuenta. await Groups.AddToGroupAsync(Context.ConnectionId, BuildGroupName(tokenInformation.Unique)); - } From bcfa736334bae54945e04d46d6fe2f7168d8ff3e Mon Sep 17 00:00:00 2001 From: Alex Giraldo Date: Sat, 23 Aug 2025 23:08:25 -0500 Subject: [PATCH 178/178] =?UTF-8?q?finalizar=20integraci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/ServiceExtensions.cs | 9 ++++----- .../LIN.Cloud.Identity.Services.csproj | 1 + LIN.Cloud.Identity/LIN.Cloud.Identity.csproj | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs index e1beb1e..6c41508 100644 --- a/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs +++ b/LIN.Cloud.Identity.Services/Extensions/ServiceExtensions.cs @@ -17,12 +17,11 @@ public static class ServiceExtensions /// Services. public static IServiceCollection AddAuthenticationServices(this IServiceCollection services, IConfiguration configuration) { - // Inicializar Firebase - FirebaseApp.Create(new AppOptions - { - Credential = GoogleCredential.FromFile("appsettings.firebase.json") - }); + //FirebaseApp.Create(new AppOptions + //{ + // Credential = GoogleCredential.FromFile("appsettings.firebase.json") + //}); // Servicios de datos. services.AddScoped(); diff --git a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj index ed791f8..31542ae 100644 --- a/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj +++ b/LIN.Cloud.Identity.Services/LIN.Cloud.Identity.Services.csproj @@ -9,6 +9,7 @@ + diff --git a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj index 55e58f0..7286381 100644 --- a/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj +++ b/LIN.Cloud.Identity/LIN.Cloud.Identity.csproj @@ -9,10 +9,10 @@ - + - +