using adas_core.Authentication.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Exceptions; using adas_core.Domain.Models.MongoModels; using adas_core.LdapLogin.Configuration; using FluentValidation; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using Novell.Directory.Ldap; namespace adas_core.LdapLogin; public class LdapLoginService : ILoginService { private readonly IAuthorityService _authorityService; private readonly LdapConfig _ldapConfig; private readonly ILogger _logger; private readonly Lazy _userService; public LdapLoginService( IOptions ldapConfig, IValidator validator, Lazy userService, IAuthorityService authorityService, ILogger logger) { _userService = userService; _authorityService = authorityService; _logger = logger; _ldapConfig = ldapConfig.Value; validator.Validate(_ldapConfig, options => options.ThrowOnFailures()); } public bool AllowPassword => true; public UserEnum.LoginMethod Method => UserEnum.LoginMethod.Ldap; public async Task Login(string username, string password) { // Check for LDAP config if (_ldapConfig.Server == null) throw new LoginServicesException("LDAP Config not found"); var conn = new LdapConnection(); try { await conn.ConnectAsync(_ldapConfig.Server, _ldapConfig.Port ?? 389); } catch (Exception e) { _logger.LogError("[LDAP] Error connecting to {server}, port {port}, Exception: {e}", _ldapConfig.Server, _ldapConfig.Port, e.Message); throw; } if (_ldapConfig.LdapUser != null) { _logger.LogInformation("[LDAP] _ldapConfig.LdapUser is enabled with {LdapUser}", _ldapConfig.LdapUser); await conn.BindAsync(_ldapConfig.LdapUser, _ldapConfig.LdapPassword); } else { var ldapUser = (!string.IsNullOrEmpty(_ldapConfig.UserDomainName) ? _ldapConfig.UserDomainName + @"\" : "") + username; try { await conn.BindAsync(ldapUser, password); } catch (LdapException e) { _logger.LogError("[LDAP] Error binding ldapUser: {LdapUser} and password", ldapUser); throw new UserNotFoundException(username, e); } } var results = await conn.SearchAsync( _ldapConfig.SearchBase, LdapConnection.ScopeSub, $"({_ldapConfig.UserNameProperty}={username})", null, false); LdapEntry? entry = null; while (await results.HasMoreAsync()) { LdapEntry? current = null; try { current = await results.NextAsync(); } catch (LdapException ex) { _logger.LogWarning("[LDAP] Skipping invalid entry: {error}", ex.Message); continue; } if (current != null) { entry = current; break; } } if (entry == null) throw new LoginServicesException("LDAP User not found"); var userEntryLdap = GetUser(entry); var user = await GetOrCreateUser(userEntryLdap, entry); _logger.LogInformation("[LDAP] entry is {entry} and user {user}", entry, user); // user.Authorization.AddRange(GetAuthorities(entry)); conn.Disconnect(); return user ?? throw new LoginServicesException("LDAP User not found"); } public Task Login(HttpContext context) { throw new LoginServicesException("Not implemented"); } public Task Authenticate(string username, string password) { throw new LoginServicesException("Not implemented"); } public Task GetById(ObjectId id) { throw new LoginServicesException("Not implemented"); } public Task GetByEmail(string email) { throw new LoginServicesException("Not implemented"); } public Task GetByUsername(string username) { throw new LoginServicesException("Not implemented"); } public Task> GetAllUsers() { throw new LoginServicesException("Not implemented"); } private async Task GetOrCreateUser(User userEntryLdap, LdapEntry entry) { var userToReturn = (await _userService.Value.GetUserByUserName(userEntryLdap.UserName) ?? await _userService.Value.GetUserByName(userEntryLdap.Name)) ?? await _userService.Value.CreateUser(userEntryLdap); if (userToReturn == null) return userToReturn; userToReturn.Authorization = []; var authorities = await CheckAuthorities(userToReturn, entry); userToReturn.Authorization.AddRange(authorities); //foreach (var authorization in userToReturn.Authorization) //{ // Enum.TryParse(authorization.Rol, out var compareRole); // if (compareRole == RolesType.Admin) userToReturn.Rol = RolesType.Admin; //} return userToReturn; } private async Task> CheckAuthorities(User user, LdapEntry entry) { try { var authorizationMap = GetAuthoritiesMap(entry, user); var authorizationWhiteList = GetAuthoritiesWhiteList(entry, user); _logger.LogInformation( "[LDAP] user {user} authorizationMap count is {authorizationMap}, authorizationWhiteList count is {authorizationWhiteList}", user.UserName, authorizationMap.Count, authorizationWhiteList.Count); // Primero, creamos un HashSet con los DisplayID de la lista blanca para búsqueda eficiente var whiteListDisplayIds = new HashSet(authorizationWhiteList.Select(a => a.DisplayId)); // Filtramos los elementos de authorizationMap que no están en la lista blanca, basándonos en DisplayID var uniqueMapAuthorizations = authorizationMap.Where(a => !whiteListDisplayIds.Contains(a.DisplayId)); // Finalmente, combinamos los elementos únicos de authorizationMap con los de authorizationWhiteList var combinedList = authorizationWhiteList.Concat(uniqueMapAuthorizations).ToList(); var userAuthorities = await _authorityService.GetUserAuthorities(user.Id); foreach (var auth in combinedList) { var authFound = userAuthorities.Find(c => c.DisplayId == auth.DisplayId); if (authFound is { CanUpdate: true }) { authFound.Rol = auth.Rol; await _authorityService.updateOne(authFound); } else { await _authorityService.InsertOne(auth); } } return await _authorityService.GetUserAuthorities(user.Id); } catch (Exception e) { _logger.LogError("[LDAP] CheckAuthorities for user {user} has exception {ex}", user.UserName, e.Message); return []; } } private List GetAuthoritiesWhiteList(LdapEntry entry, User user) { try { var userWhiteList = new List(); var userNameProperty = _ldapConfig.UserNameProperty; var whiteList = _ldapConfig.WhiteList.FindAll(u => (u.Name != null && entry.Dn.Contains(u.Name, StringComparison.CurrentCultureIgnoreCase)) || (u.Username != null && userNameProperty != null && entry.GetAttributeSet().TryGetValue(userNameProperty, out var attr) && attr.StringValue != null && attr.StringValue.Equals(u.Username, StringComparison.CurrentCultureIgnoreCase)) ); if (whiteList.Count == 0) return userWhiteList; foreach (var authorityMap in whiteList) { if (!Enum.TryParse(authorityMap.Rol, out _)) continue; userWhiteList.Add(new Authorization { UserId = user.Id, DisplayId = authorityMap.DisplayId, Rol = authorityMap.Rol }); } return userWhiteList; } catch (Exception e) { _logger.LogError("[LDAP] GetAuthoritiesWhiteList for user {user} has exception {ex}", user.UserName, e.Message); return []; } } private User GetUser(LdapEntry ldapEntry) { var attributes = ldapEntry.GetAttributeSet(); var user = new User(); // UserName if (!string.IsNullOrWhiteSpace(_ldapConfig.UserNameProperty) && attributes.TryGetValue(_ldapConfig.UserNameProperty, out var userAttr) && userAttr?.StringValue != null) { user.UserName = userAttr.StringValue; } else { user.UserName = ""; } _logger.LogInformation("[LDAP] GetUser UserName is {UserName} ", user.UserName); // First name if (!string.IsNullOrWhiteSpace(_ldapConfig.FirstNameProperty) && attributes.TryGetValue(_ldapConfig.FirstNameProperty, out var firstNameAttr) && firstNameAttr?.StringValue != null) { user.Name = firstNameAttr.StringValue; } // Last name if (!string.IsNullOrWhiteSpace(_ldapConfig.LastNameProperty) && attributes.TryGetValue(_ldapConfig.LastNameProperty, out var lastNameAttr) && lastNameAttr?.StringValue != null) { user.Name = string.IsNullOrEmpty(user.Name) ? lastNameAttr.StringValue : $"{user.Name} {lastNameAttr.StringValue}"; } return user; } private List GetAuthoritiesMap(LdapEntry ldapEntry, User user) { try { var authorities = new List(); if (!_ldapConfig.AuthoritiesMap.Any()) return authorities; var attributes = ldapEntry.GetAttributeSet(); if (!string.IsNullOrWhiteSpace(_ldapConfig.GroupsProperty) || !attributes.TryGetValue(_ldapConfig.GroupsProperty!, out var groupsAttr) || groupsAttr?.StringValueArray == null) { return authorities; } var groups = groupsAttr.StringValueArray.ToList(); foreach (var authorityMap in _ldapConfig.AuthoritiesMap) { if (groups.Exists(g => string.Equals(g, authorityMap.Group, StringComparison.CurrentCultureIgnoreCase))) { if (!Enum.TryParse(authorityMap.Rol, out _)) continue; authorities.Add(new Authorization { UserId = user.Id, DisplayId = authorityMap.DisplayId, Rol = authorityMap.Rol }); } } return authorities; } catch (Exception e) { _logger.LogError("Error getting Authorities Map. Return new empty list. Exception: {e}", e); return []; } } }