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.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using System.DirectoryServices.Protocols; using System.Net; using Authorization = adas_core.Domain.Models.MongoModels.Authorization; 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) { if (_ldapConfig.Server == null) throw new LoginServicesException("LDAP Config not found"); var identifier = new LdapDirectoryIdentifier(_ldapConfig.Server, _ldapConfig.Port ?? 389); var connection = new LdapConnection(identifier); try { if (_ldapConfig.LdapUser != null) { _logger.LogInformation("[LDAP] Using configured LDAP user {LdapUser}", _ldapConfig.LdapUser); connection.Credential = new NetworkCredential(_ldapConfig.LdapUser, _ldapConfig.LdapPassword); } else { var ldapUser = (!string.IsNullOrEmpty(_ldapConfig.UserDomainName) ? _ldapConfig.UserDomainName + @"\" : "") + username; connection.Credential = new NetworkCredential(ldapUser, password); } connection.AuthType = AuthType.Basic; connection.Bind(); } catch (LdapException e) { _logger.LogError("[LDAP] Error binding user {username}", username); throw new UserNotFoundException(username, e); } SearchResultEntry? entry = null; try { var request = new SearchRequest( _ldapConfig.SearchBase, $"({_ldapConfig.UserNameProperty}={username})", SearchScope.Subtree ); var response = (SearchResponse)connection.SendRequest(request); foreach (SearchResultEntry current in response.Entries) { entry = current; break; } } catch (Exception e) { _logger.LogError("[LDAP] Search error for user {username}: {error}", username, e.Message); throw; } if (entry == null) throw new LoginServicesException("LDAP User not found"); var userEntryLdap = GetUser(entry); var user = await GetOrCreateUser(userEntryLdap, entry); _logger.LogInformation("[LDAP] entry found and user {user}", user); connection.Dispose(); 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, SearchResultEntry 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); return userToReturn; } private async Task> CheckAuthorities(User user, SearchResultEntry entry) { try { var authorizationMap = GetAuthoritiesMap(entry, user); var authorizationWhiteList = GetAuthoritiesWhiteList(entry, user); var whiteListDisplayIds = new HashSet(authorizationWhiteList.Select(a => a.DisplayId)); var uniqueMapAuthorizations = authorizationMap .Where(a => !whiteListDisplayIds.Contains(a.DisplayId)); 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 error for user {user}: {error}", user.UserName, e.Message); return []; } } private List GetAuthoritiesWhiteList(SearchResultEntry entry, User user) { try { var result = new List(); var whiteList = _ldapConfig.WhiteList.FindAll(u => (u.Name != null && entry.DistinguishedName.Contains(u.Name, StringComparison.CurrentCultureIgnoreCase)) || (u.Username != null && entry.Attributes[_ldapConfig.UserNameProperty]?[0]?.ToString() ?.Equals(u.Username, StringComparison.CurrentCultureIgnoreCase) == true) ); foreach (var authorityMap in whiteList) { if (!Enum.TryParse(authorityMap.Rol, out _)) continue; result.Add(new Authorization { UserId = user.Id, DisplayId = authorityMap.DisplayId, Rol = authorityMap.Rol }); } return result; } catch (Exception e) { _logger.LogError("[LDAP] GetAuthoritiesWhiteList error: {error}", e.Message); return []; } } private User GetUser(SearchResultEntry entry) { var user = new User { UserName = entry.Attributes[_ldapConfig.UserNameProperty]?[0]?.ToString() ?? "" }; if (!string.IsNullOrWhiteSpace(_ldapConfig.FirstNameProperty)) { var first = entry.Attributes[_ldapConfig.FirstNameProperty]?[0]?.ToString(); if (first != null) user.Name = first; } if (!string.IsNullOrWhiteSpace(_ldapConfig.LastNameProperty)) { var last = entry.Attributes[_ldapConfig.LastNameProperty]?[0]?.ToString(); if (last != null) user.Name = string.IsNullOrEmpty(user.Name) ? last : $"{user.Name} {last}"; } return user; } private List GetAuthoritiesMap(SearchResultEntry entry, User user) { try { var authorities = new List(); if (!_ldapConfig.AuthoritiesMap.Any()) return authorities; var groupAttr = entry.Attributes[_ldapConfig.GroupsProperty]; if (groupAttr == null) return authorities; var groups = groupAttr.GetValues(typeof(string)) .Cast() .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("[LDAP] GetAuthoritiesMap error: {error}", e.Message); return []; } } }