From e1bca07a766cb9c9816f4a2eabf24ff9738559f6 Mon Sep 17 00:00:00 2001 From: Oleg Brezhnev Date: Wed, 3 Apr 2024 17:45:08 +0300 Subject: [PATCH 1/3] netbios name resolving --- MultiFactor.Ldap.Adapter/App.config | 2 +- .../Configuration/ClientConfiguration.cs | 5 +- .../Configuration/ServiceConfiguration.cs | 7 +- .../Core/NameResolving/LdapIdentityFormat.cs | 12 +++ .../Core/NameResolving/NameResolverContext.cs | 10 ++ .../Core/NameResolving/NameResolverService.cs | 61 +++++++++++ .../DistinguishedNameToUpnTranslator.cs | 16 +++ .../NameTranslators/INameTranslator.cs | 7 ++ .../NetbiosToUpnNameTranslator.cs | 26 +++++ .../UpnFromProfileNameTranslator.cs | 15 +++ ...ccountNameAndNetbiosToUpnNameTranslator.cs | 25 +++++ .../sAMAccountNameToUpnNameTranslator.cs | 16 +++ .../Core/NameResolving/NameTypeDetector.cs | 30 ++++++ .../Core/NameResolving/NetbiosDomainName.cs | 8 ++ .../Extensions/ServiceCollectionExtensions.cs | 3 +- .../MultiFactor.Ldap.Adapter.csproj | 11 ++ MultiFactor.Ldap.Adapter/Server/LdapProxy.cs | 40 ++++++- .../Server/LdapProxyFactory.cs | 9 +- .../Services/LdapService.cs | 90 +++++++++++++++- .../Services/RequestFactory.cs | 100 ++++++++++++++++++ 20 files changed, 480 insertions(+), 13 deletions(-) create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/LdapIdentityFormat.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverContext.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverService.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/DistinguishedNameToUpnTranslator.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/INameTranslator.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/NetbiosToUpnNameTranslator.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/UpnFromProfileNameTranslator.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/sAMAccountNameAndNetbiosToUpnNameTranslator.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/sAMAccountNameToUpnNameTranslator.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NameTypeDetector.cs create mode 100644 MultiFactor.Ldap.Adapter/Core/NameResolving/NetbiosDomainName.cs diff --git a/MultiFactor.Ldap.Adapter/App.config b/MultiFactor.Ldap.Adapter/App.config index 82ada21..8010fdf 100644 --- a/MultiFactor.Ldap.Adapter/App.config +++ b/MultiFactor.Ldap.Adapter/App.config @@ -56,7 +56,7 @@ - + diff --git a/MultiFactor.Ldap.Adapter/Configuration/ClientConfiguration.cs b/MultiFactor.Ldap.Adapter/Configuration/ClientConfiguration.cs index 925ab5a..c0874dd 100644 --- a/MultiFactor.Ldap.Adapter/Configuration/ClientConfiguration.cs +++ b/MultiFactor.Ldap.Adapter/Configuration/ClientConfiguration.cs @@ -2,6 +2,7 @@ //Please see licence at //https://github.com/MultifactorLab/MultiFactor.Ldap.Adapter/blob/main/LICENSE.md +using MultiFactor.Ldap.Adapter.Core.NameResolve; using System.Collections.Generic; using System.Linq; @@ -20,6 +21,7 @@ public ClientConfiguration() LoadActiveDirectoryNestedGroups = true; UserNameTransformRules = new List(); + TransformLdapIdentity = LdapIdentityFormat.None; } /// @@ -79,10 +81,9 @@ public ClientConfiguration() /// API Secret /// public string MultifactorApiSecret { get; set; } - + public LdapIdentityFormat TransformLdapIdentity { get; set; } public AuthenticatedClientCacheConfig AuthenticationCacheLifetime { get; internal set; } - public bool CheckUserGroups() { return diff --git a/MultiFactor.Ldap.Adapter/Configuration/ServiceConfiguration.cs b/MultiFactor.Ldap.Adapter/Configuration/ServiceConfiguration.cs index 28588f3..6d2de0a 100644 --- a/MultiFactor.Ldap.Adapter/Configuration/ServiceConfiguration.cs +++ b/MultiFactor.Ldap.Adapter/Configuration/ServiceConfiguration.cs @@ -3,6 +3,7 @@ //https://github.com/MultifactorLab/MultiFactor.Ldap.Adapter/blob/main/LICENSE.md using MultiFactor.Ldap.Adapter.Core; +using MultiFactor.Ldap.Adapter.Core.NameResolve; using NetTools; using Serilog; using System; @@ -104,7 +105,6 @@ public static ServiceConfiguration Load(ILogger logger) var apiTimeoutSetting = appSettings.Settings["multifactor-api-timeout"]?.Value; var logLevelSetting = appSettings.Settings["logging-level"]?.Value; var certificatePassword = appSettings.Settings["certificate-password"]?.Value; - if (string.IsNullOrEmpty(apiUrlSetting)) { throw new Exception("Configuration error: 'multifactor-api-url' element not found"); @@ -209,7 +209,7 @@ private static ClientConfiguration Load(string name, AppSettingsSection appSetti var activeDirectory2FaBypassGroupSetting = appSettings.Settings["active-directory-2fa-bypass-group"]?.Value; var bypassSecondFactorWhenApiUnreachableSetting = appSettings.Settings["bypass-second-factor-when-api-unreachable"]?.Value; var loadActiveDirectoryNestedGroupsSettings = appSettings.Settings["load-active-directory-nested-groups"]?.Value; - + var transformLdapIdentity = appSettings.Settings["transform-ldap-identity"]?.Value; if (string.IsNullOrEmpty(ldapServerSetting)) { @@ -230,6 +230,9 @@ private static ClientConfiguration Load(string name, AppSettingsSection appSetti LdapServer = ldapServerSetting, MultifactorApiKey = multifactorApiKeySetting, MultifactorApiSecret = multifactorApiSecretSetting, + TransformLdapIdentity = string.IsNullOrEmpty(transformLdapIdentity) + ? LdapIdentityFormat.None + : (LdapIdentityFormat)Enum.Parse(typeof(LdapIdentityFormat), transformLdapIdentity, true) }; if (!string.IsNullOrEmpty(serviceAccountsSetting)) diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/LdapIdentityFormat.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/LdapIdentityFormat.cs new file mode 100644 index 0000000..0164ba5 --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/LdapIdentityFormat.cs @@ -0,0 +1,12 @@ +namespace MultiFactor.Ldap.Adapter.Core.NameResolve +{ + public enum LdapIdentityFormat + { + None = 0, + Upn = 1, + UidAndNetbios = 2, // uid@netbios + SamAccountName = 3, + NetBIOSAndUid = 4, // NETBIOS\uid + DistinguishedName = 5 + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverContext.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverContext.cs new file mode 100644 index 0000000..3d5c656 --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverContext.cs @@ -0,0 +1,10 @@ +using MultiFactor.Ldap.Adapter.Services; + +namespace MultiFactor.Ldap.Adapter.Core.NameResolving +{ + public class NameResolverContext + { + public NetbiosDomainName[] Domains; + public LdapProfile Profile; + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverService.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverService.cs new file mode 100644 index 0000000..6a5a15e --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverService.cs @@ -0,0 +1,61 @@ +using MultiFactor.Ldap.Adapter.Core.NameResolving; +using MultiFactor.Ldap.Adapter.Core.NameResolving.NameTranslators; +using Serilog; + +namespace MultiFactor.Ldap.Adapter.Core.NameResolve +{ + public class NameResolverService + { + private ILogger _logger; + + public NameResolverService(ILogger logger) + { + _logger = logger; + } + + public string Resolve(NameResolverContext context, string name, LdapIdentityFormat to) + { + var from = NameTypeDetector.GetType(name); + if(from == null) + { + return name; + } + + var resolver = GetTranslator(context, (LdapIdentityFormat)from, to); + if(resolver == null) + { + return name; + } + return resolver.Translate(context, name); + } + + + public INameTranslator GetTranslator(NameResolverContext context, LdapIdentityFormat from, LdapIdentityFormat to) + { + if (from == LdapIdentityFormat.UidAndNetbios && to == LdapIdentityFormat.Upn) + { + return new sAMAccountNameAndNetbiosToUpnNameTranslator(); + } + else if (from == LdapIdentityFormat.NetBIOSAndUid && to == LdapIdentityFormat.Upn) + { + return new NetbiosToUpnNameTranslator(); + } + else if (from == LdapIdentityFormat.DistinguishedName && to == LdapIdentityFormat.Upn) + { + return new DistinguishedNameToUpnTranslator(); + } + // There are a case when sAMAccountName@domain.local looks exactly like UPN + // Let's try an UPN we got from the profile + if (from == LdapIdentityFormat.Upn && to == LdapIdentityFormat.Upn && context.Profile != null) + { + return new UpnFromProfileNameTranslator(); + } + if(from == LdapIdentityFormat.SamAccountName && to == LdapIdentityFormat.Upn) + { + return new sAMAccountNameToUpnNameTranslator(); + } + _logger.Error($"Suitable username format was not found"); + return null; + } + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/DistinguishedNameToUpnTranslator.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/DistinguishedNameToUpnTranslator.cs new file mode 100644 index 0000000..9ecae3f --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/DistinguishedNameToUpnTranslator.cs @@ -0,0 +1,16 @@ +using System.Text.RegularExpressions; + +namespace MultiFactor.Ldap.Adapter.Core.NameResolving.NameTranslators +{ + public class DistinguishedNameToUpnTranslator : INameTranslator + { + public string Translate(NameResolverContext nameTranslatorContext, string from) + { + if (nameTranslatorContext.Profile != null) + { + return nameTranslatorContext.Profile.Upn; + } + return from; + } + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/INameTranslator.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/INameTranslator.cs new file mode 100644 index 0000000..415703e --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/INameTranslator.cs @@ -0,0 +1,7 @@ +namespace MultiFactor.Ldap.Adapter.Core.NameResolving.NameTranslators +{ + public interface INameTranslator + { + string Translate(NameResolverContext context, string from); + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/NetbiosToUpnNameTranslator.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/NetbiosToUpnNameTranslator.cs new file mode 100644 index 0000000..6697c59 --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/NetbiosToUpnNameTranslator.cs @@ -0,0 +1,26 @@ +using System.Text.RegularExpressions; + +namespace MultiFactor.Ldap.Adapter.Core.NameResolving.NameTranslators +{ + public class NetbiosToUpnNameTranslator : INameTranslator + { + public string Translate(NameResolverContext nameTranslatorContext, string from) + { + if(nameTranslatorContext.Profile != null) + { + return nameTranslatorContext.Profile.Upn; + } + + foreach (var domain in nameTranslatorContext.Domains) + { + var regex = new Regex("^" + domain.NetbiosName.ToLower() + "\\\\", RegexOptions.IgnoreCase); + if (regex.IsMatch(from)) + { + var result = regex.Replace(from + "@" + domain.Domain.ToLower(), ""); + return result; + } + } + return from; + } + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/UpnFromProfileNameTranslator.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/UpnFromProfileNameTranslator.cs new file mode 100644 index 0000000..8e43a7d --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/UpnFromProfileNameTranslator.cs @@ -0,0 +1,15 @@ + +namespace MultiFactor.Ldap.Adapter.Core.NameResolving.NameTranslators +{ + public class UpnFromProfileNameTranslator : INameTranslator + { + public string Translate(NameResolverContext nameResolverContext, string from) + { + if (nameResolverContext.Profile != null) + { + return nameResolverContext.Profile.Upn; + } + return from; + } + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/sAMAccountNameAndNetbiosToUpnNameTranslator.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/sAMAccountNameAndNetbiosToUpnNameTranslator.cs new file mode 100644 index 0000000..c335f52 --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/sAMAccountNameAndNetbiosToUpnNameTranslator.cs @@ -0,0 +1,25 @@ +using System.Text.RegularExpressions; + +namespace MultiFactor.Ldap.Adapter.Core.NameResolving.NameTranslators +{ + public class sAMAccountNameAndNetbiosToUpnNameTranslator : INameTranslator + { + public string Translate(NameResolverContext nameTranslatorContext, string from) + { + if (nameTranslatorContext.Profile != null) + { + return nameTranslatorContext.Profile.Upn; + } + foreach(var domain in nameTranslatorContext.Domains) + { + var regex = new Regex("@" + domain.NetbiosName.ToLower() + "$", RegexOptions.IgnoreCase); + if(regex.IsMatch(from)) + { + var result = regex.Replace(from, "@" + domain.Domain.ToLower()); + return result; + } + } + return from; + } + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/sAMAccountNameToUpnNameTranslator.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/sAMAccountNameToUpnNameTranslator.cs new file mode 100644 index 0000000..a316967 --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTranslators/sAMAccountNameToUpnNameTranslator.cs @@ -0,0 +1,16 @@ +using System.Text.RegularExpressions; + +namespace MultiFactor.Ldap.Adapter.Core.NameResolving.NameTranslators +{ + public class sAMAccountNameToUpnNameTranslator : INameTranslator + { + public string Translate(NameResolverContext nameTranslatorContext, string from) + { + if (nameTranslatorContext.Profile != null) + { + return nameTranslatorContext.Profile.Upn; + } + return from; + } + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTypeDetector.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTypeDetector.cs new file mode 100644 index 0000000..ca8dc11 --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTypeDetector.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace MultiFactor.Ldap.Adapter.Core.NameResolve +{ + public class NameTypeDetector + { + public static LdapIdentityFormat? GetType(string name) + { + if (name.Contains('\\')) + { + return LdapIdentityFormat.NetBIOSAndUid; + } + if (name.IndexOf("CN=", StringComparison.OrdinalIgnoreCase) >= 0) + { + return LdapIdentityFormat.DistinguishedName; + } + var domainRegex = new Regex("^[^@]+@(.+)$"); + var domainMatch = domainRegex.Match(name); + if (!domainMatch.Success || domainMatch.Groups.Count < 2) + { + return LdapIdentityFormat.SamAccountName; + } + return domainMatch.Groups[1].Value.Count(x => x == '.') == 0 + ? LdapIdentityFormat.UidAndNetbios + : LdapIdentityFormat.Upn; + } + } +} diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NetbiosDomainName.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NetbiosDomainName.cs new file mode 100644 index 0000000..38eaf76 --- /dev/null +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NetbiosDomainName.cs @@ -0,0 +1,8 @@ +namespace MultiFactor.Ldap.Adapter.Core.NameResolving +{ + public class NetbiosDomainName + { + public string Domain; + public string NetbiosName; + } +} diff --git a/MultiFactor.Ldap.Adapter/Extensions/ServiceCollectionExtensions.cs b/MultiFactor.Ldap.Adapter/Extensions/ServiceCollectionExtensions.cs index 5022667..9f08e85 100644 --- a/MultiFactor.Ldap.Adapter/Extensions/ServiceCollectionExtensions.cs +++ b/MultiFactor.Ldap.Adapter/Extensions/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using MultiFactor.Ldap.Adapter.Configuration; +using MultiFactor.Ldap.Adapter.Core.NameResolve; using MultiFactor.Ldap.Adapter.Server; using MultiFactor.Ldap.Adapter.Services; using MultiFactor.Ldap.Adapter.Services.Caching; @@ -73,7 +74,7 @@ public static void ConfigureApplicationServices(this IServiceCollection services services.AddSingleton(); services.AddSingleton(); services.AddHttpClientWithProxy(); - + services.AddTransient(); services.AddSingleton(); } diff --git a/MultiFactor.Ldap.Adapter/MultiFactor.Ldap.Adapter.csproj b/MultiFactor.Ldap.Adapter/MultiFactor.Ldap.Adapter.csproj index d261da8..4662766 100644 --- a/MultiFactor.Ldap.Adapter/MultiFactor.Ldap.Adapter.csproj +++ b/MultiFactor.Ldap.Adapter/MultiFactor.Ldap.Adapter.csproj @@ -206,6 +206,17 @@ + + + + + + + + + + + diff --git a/MultiFactor.Ldap.Adapter/Server/LdapProxy.cs b/MultiFactor.Ldap.Adapter/Server/LdapProxy.cs index 3cba60a..5354399 100644 --- a/MultiFactor.Ldap.Adapter/Server/LdapProxy.cs +++ b/MultiFactor.Ldap.Adapter/Server/LdapProxy.cs @@ -4,6 +4,8 @@ using MultiFactor.Ldap.Adapter.Configuration; using MultiFactor.Ldap.Adapter.Core; +using MultiFactor.Ldap.Adapter.Core.NameResolve; +using MultiFactor.Ldap.Adapter.Core.NameResolving; using MultiFactor.Ldap.Adapter.Server.Authentication; using MultiFactor.Ldap.Adapter.Services; using Serilog; @@ -37,12 +39,14 @@ public class LdapProxy private static readonly ConcurrentDictionary _usersCn2Dn = new ConcurrentDictionary(); private readonly RandomWaiter _waiter; + private NameResolverService _nameResolverService; public LdapProxy(TcpClient clientConnection, Stream clientStream, TcpClient serverConnection, Stream serverStream, ClientConfiguration clientConfig, RandomWaiter waiter, MultiFactorApiClient apiClient, - ILogger logger) + ILogger logger, + NameResolverService nameResolverService) { _clientConnection = clientConnection ?? throw new ArgumentNullException(nameof(clientConnection)); _clientStream = clientStream ?? throw new ArgumentNullException(nameof(clientStream)); @@ -55,6 +59,7 @@ public class LdapProxy _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _ldapService = new LdapService(); + _nameResolverService = nameResolverService; } public async Task Start() @@ -177,7 +182,14 @@ private async Task<(byte[], int)> ParseAndProcessResponse(byte[] data, int lengt //apply login transformation rules if any _userName = ProcessUserNameTransformRules(); - var profile = await _ldapService.LoadProfile(_serverStream, _userName); + string baseDn = await _ldapService.GetBaseDn(_serverStream, _userName); + + if (_clientConfig.TransformLdapIdentity != LdapIdentityFormat.None) + { + _userName = await EnforceLdapIdentityFormat(baseDn, _clientConfig.TransformLdapIdentity); + } + + var profile = await _ldapService.LoadProfile(_serverStream, baseDn, _userName); if (_clientConfig.CheckUserGroups()) { @@ -447,6 +459,30 @@ private string ProcessUserNameTransformRules() return userName; } + private async Task EnforceLdapIdentityFormat(string baseDn, LdapIdentityFormat loginFormat) + { + if (loginFormat == LdapIdentityFormat.None) + { + throw new ArgumentException("Incorrect identity format was passed"); + } + + _logger.Debug($"{_userName} username will be transformed to {_clientConfig.TransformLdapIdentity} format"); ; + var domains = await _ldapService.GetDomains(_serverStream, baseDn); + var matchedProfile = await _ldapService.ResolveProfile(_serverStream, baseDn,_userName); + if (matchedProfile == null) + { + _logger.Error($"{_userName} profile was not found, unable to translate the username"); + return _userName; + } + var context = new NameResolverContext() + { + Domains = domains, + Profile = matchedProfile + }; + + return _nameResolverService.Resolve(context, _userName, loginFormat); + } + } public enum LdapProxyAuthenticationStatus diff --git a/MultiFactor.Ldap.Adapter/Server/LdapProxyFactory.cs b/MultiFactor.Ldap.Adapter/Server/LdapProxyFactory.cs index ff98a85..4167496 100644 --- a/MultiFactor.Ldap.Adapter/Server/LdapProxyFactory.cs +++ b/MultiFactor.Ldap.Adapter/Server/LdapProxyFactory.cs @@ -3,6 +3,7 @@ //https://github.com/MultifactorLab/MultiFactor.Ldap.Adapter/blob/main/LICENSE.md using MultiFactor.Ldap.Adapter.Configuration; +using MultiFactor.Ldap.Adapter.Core.NameResolve; using MultiFactor.Ldap.Adapter.Services; using Serilog; using System; @@ -16,12 +17,13 @@ public class LdapProxyFactory private readonly RandomWaiter _waiter; private readonly MultiFactorApiClient _apiClient; private readonly ILogger _logger; - - public LdapProxyFactory(RandomWaiter waiter, MultiFactorApiClient apiClient, ILogger logger) + private readonly NameResolverService _nameResolverService; + public LdapProxyFactory(RandomWaiter waiter, MultiFactorApiClient apiClient, ILogger logger, NameResolverService nameResolverService) { _waiter = waiter ?? throw new ArgumentNullException(nameof(waiter)); _apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _nameResolverService = nameResolverService ?? throw new ArgumentNullException(nameof(_nameResolverService)); } public LdapProxy CreateProxy(TcpClient clientConnection, Stream clientStream, @@ -32,7 +34,8 @@ public LdapProxyFactory(RandomWaiter waiter, MultiFactorApiClient apiClient, ILo serverConnection, serverStream, clientConfig, _waiter, _apiClient, - _logger); + _logger, + _nameResolverService); } } } \ No newline at end of file diff --git a/MultiFactor.Ldap.Adapter/Services/LdapService.cs b/MultiFactor.Ldap.Adapter/Services/LdapService.cs index 232fd21..17023d6 100644 --- a/MultiFactor.Ldap.Adapter/Services/LdapService.cs +++ b/MultiFactor.Ldap.Adapter/Services/LdapService.cs @@ -4,6 +4,7 @@ using MultiFactor.Ldap.Adapter.Configuration; using MultiFactor.Ldap.Adapter.Core; +using MultiFactor.Ldap.Adapter.Core.NameResolving; using System.Collections.Generic; using System.IO; using System.Linq; @@ -39,8 +40,8 @@ public async Task GetDefaultNamingContext(Stream ldapConnectedStream) return defaultNamingContext; } - - public async Task LoadProfile(Stream ldapConnectedStream, string userName) + + public async Task GetBaseDn(Stream ldapConnectedStream, string userName) { string baseDn; @@ -54,7 +55,11 @@ public async Task LoadProfile(Stream ldapConnectedStream, string us //else query defaultNamingContext from ldap baseDn = await GetDefaultNamingContext(ldapConnectedStream); } + return baseDn; + } + public async Task LoadProfile(Stream ldapConnectedStream, string baseDn, string userName) + { var request = _requestFactory.CreateLoadProfileRequest(userName, baseDn); var requestData = request.GetBytes(); @@ -129,6 +134,87 @@ public async Task> GetAllGroups(Stream ldapConnectedStream, LdapPro return groups; } + + public async Task GetDomains(Stream serverStream, string baseDn) + { + var request = _requestFactory.CreateGetPartitions(baseDn); + var buffer = request.GetBytes(); + await serverStream.WriteAsync(buffer, 0, buffer.Length); + LdapPacket packet; + var result = new List(); + while ((packet = await LdapPacket.ParsePacket(serverStream)) != null) + { + var searchResult = packet.ChildAttributes.SingleOrDefault(c => c.LdapOperation == LdapOperation.SearchResultEntry); + if (searchResult != null) + { + var attrs = searchResult.ChildAttributes[1]; + var domain = new NetbiosDomainName(); + foreach (var valueAttr in attrs.ChildAttributes) + { + var entry = GetEntry(valueAttr); + + if (entry.Name == "nETBIOSName") + { + domain.NetbiosName = entry.Values.First(); + } + + if (entry.Name == "dnsRoot") + { + domain.Domain = entry.Values.First(); + } + } + result.Add(domain); + } + } + + return result.ToArray(); + } + + public async Task ResolveProfile(Stream serverStream, string name, string baseDn) + { + var request = _requestFactory.CreateResolveProfileRequest(baseDn, name); + var buffer = request.GetBytes(); + await serverStream.WriteAsync(buffer, 0, buffer.Length); + var result = new List(); + + LdapProfile profile = null; + LdapPacket packet; + + while ((packet = await LdapPacket.ParsePacket(serverStream)) != null) + { + var searchResult = packet.ChildAttributes.SingleOrDefault(c => c.LdapOperation == LdapOperation.SearchResultEntry); + if (searchResult != null) + { + profile = profile ?? new LdapProfile(); + + var dn = searchResult.ChildAttributes[0].GetValue(); + var attrs = searchResult.ChildAttributes[1]; + + profile.Dn = dn; + + foreach (var valueAttr in attrs.ChildAttributes) + { + var entry = GetEntry(valueAttr); + + switch (entry.Name) + { + case "uid": + profile.Uid = entry.Values.FirstOrDefault(); //openldap, freeipa + break; + case "sAMAccountName": + profile.Uid = entry.Values.FirstOrDefault(); //ad + break; + case "userPrincipalName": + profile.Upn = entry.Values.FirstOrDefault(); + break; + } + } + } + } + + return profile; + } + private IEnumerable GetGroups(LdapPacket packet) { var groups = new List(); diff --git a/MultiFactor.Ldap.Adapter/Services/RequestFactory.cs b/MultiFactor.Ldap.Adapter/Services/RequestFactory.cs index 60f3705..bc2d501 100644 --- a/MultiFactor.Ldap.Adapter/Services/RequestFactory.cs +++ b/MultiFactor.Ldap.Adapter/Services/RequestFactory.cs @@ -1,4 +1,6 @@ using MultiFactor.Ldap.Adapter.Core; +using System.Text.RegularExpressions; +using System; namespace MultiFactor.Ldap.Adapter.Services { @@ -112,5 +114,103 @@ public LdapPacket CreateMemberOfRequest(string userName) return packet; } + + public LdapPacket CreateGetPartitions(string baseDn) + { + var packet = new LdapPacket(_messageId++); + + var searchRequest = new LdapAttribute(LdapOperation.SearchRequest); + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "CN=Partitions,CN=Configuration," + baseDn)); // base dn + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Enumerated, (byte)2)); // scope: subtree + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Enumerated, (byte)3)); // aliases: never + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Integer, (Int32)1000)); // size limit: 255 + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Integer, (byte)60)); // time limit: 60 + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Boolean, false)); //typesOnly: false + + var and = new LdapAttribute((byte)LdapFilterChoice.and); + + var eq1 = new LdapAttribute((byte)LdapFilterChoice.equalityMatch); + var present = new LdapAttribute((byte)LdapFilterChoice.present, "netbiosname"); + + and.ChildAttributes.Add(eq1); + and.ChildAttributes.Add(present); + + searchRequest.ChildAttributes.Add(and); + + eq1.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "objectcategory")); + eq1.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "crossref")); + + packet.ChildAttributes.Add(searchRequest); + + var attrList = new LdapAttribute(UniversalDataType.Sequence); + attrList.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "netbiosname")); + attrList.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "dnsRoot")); + searchRequest.ChildAttributes.Add(attrList); + return packet; + } + + + public LdapPacket CreateResolveProfileRequest(string name, string baseDn) + { + var packet = new LdapPacket(_messageId++); + + var searchRequest = new LdapAttribute(LdapOperation.SearchRequest); + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, baseDn)); // base dn + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Enumerated, (byte)2)); // scope: subtree + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Enumerated, (byte)0)); // aliases: never + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Integer, (Int32)1000)); // size limit: 255 + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Integer, (byte)60)); // time limit: 60 + searchRequest.ChildAttributes.Add(new LdapAttribute(UniversalDataType.Boolean, false)); //typesOnly: false + + var and = new LdapAttribute((byte)LdapFilterChoice.and); + var or = new LdapAttribute((byte)LdapFilterChoice.or); + + var sAMAccountNameEq = new LdapAttribute((byte)LdapFilterChoice.equalityMatch); + sAMAccountNameEq.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "sAMAccountName")); + var sAMAccountNameRegex = new Regex("@[^@]+$"); + var netbiosRegex = new Regex(@"[^.]+\\"); + sAMAccountNameEq.ChildAttributes.Add( + new LdapAttribute(UniversalDataType.OctetString, + netbiosRegex.Replace(sAMAccountNameRegex.Replace(name, ""), "")) + ); + + var upnEq = new LdapAttribute((byte)LdapFilterChoice.equalityMatch); + upnEq.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "UserPrincipalName")); + upnEq.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, name)); + + + var dnEq = new LdapAttribute((byte)LdapFilterChoice.equalityMatch); + dnEq.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "distinguishedName")); + dnEq.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, name)); + + + or.ChildAttributes.Add(sAMAccountNameEq); + or.ChildAttributes.Add(upnEq); + or.ChildAttributes.Add(dnEq); + + var objectClassEq = new LdapAttribute((byte)LdapFilterChoice.equalityMatch); + + objectClassEq.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "objectClass")); + objectClassEq.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "user")); + + and.ChildAttributes.Add(objectClassEq); + and.ChildAttributes.Add(or); + + searchRequest.ChildAttributes.Add(and); + + var attrList = new LdapAttribute(UniversalDataType.Sequence); + attrList.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "uid")); + attrList.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "sAMAccountName")); + attrList.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "UserPrincipalName")); + attrList.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "DisplayName")); + attrList.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "mail")); + attrList.ChildAttributes.Add(new LdapAttribute(UniversalDataType.OctetString, "memberOf")); + + searchRequest.ChildAttributes.Add(attrList); + + packet.ChildAttributes.Add(searchRequest); + + return packet; + } } } From 5bdb1b744a8340899751941c1f3f3cbd36f2d68e Mon Sep 17 00:00:00 2001 From: Oleg Brezhnev Date: Wed, 3 Apr 2024 17:48:14 +0300 Subject: [PATCH 2/3] whitespace adjust --- MultiFactor.Ldap.Adapter/App.config | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MultiFactor.Ldap.Adapter/App.config b/MultiFactor.Ldap.Adapter/App.config index 8010fdf..7ff5162 100644 --- a/MultiFactor.Ldap.Adapter/App.config +++ b/MultiFactor.Ldap.Adapter/App.config @@ -53,9 +53,9 @@ - - - + + + From 8131a02926271220e7d48912d0a09baa77b11980 Mon Sep 17 00:00:00 2001 From: Oleg Brezhnev Date: Wed, 3 Apr 2024 17:50:36 +0300 Subject: [PATCH 3/3] Name Resolving Service namespace adjust --- MultiFactor.Ldap.Adapter/Configuration/ClientConfiguration.cs | 2 +- MultiFactor.Ldap.Adapter/Configuration/ServiceConfiguration.cs | 2 +- .../Core/NameResolving/LdapIdentityFormat.cs | 2 +- .../Core/NameResolving/NameResolverService.cs | 2 +- MultiFactor.Ldap.Adapter/Core/NameResolving/NameTypeDetector.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 2 +- MultiFactor.Ldap.Adapter/Server/LdapProxy.cs | 2 +- MultiFactor.Ldap.Adapter/Server/LdapProxyFactory.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MultiFactor.Ldap.Adapter/Configuration/ClientConfiguration.cs b/MultiFactor.Ldap.Adapter/Configuration/ClientConfiguration.cs index c0874dd..6da95e3 100644 --- a/MultiFactor.Ldap.Adapter/Configuration/ClientConfiguration.cs +++ b/MultiFactor.Ldap.Adapter/Configuration/ClientConfiguration.cs @@ -2,7 +2,7 @@ //Please see licence at //https://github.com/MultifactorLab/MultiFactor.Ldap.Adapter/blob/main/LICENSE.md -using MultiFactor.Ldap.Adapter.Core.NameResolve; +using MultiFactor.Ldap.Adapter.Core.NameResolving; using System.Collections.Generic; using System.Linq; diff --git a/MultiFactor.Ldap.Adapter/Configuration/ServiceConfiguration.cs b/MultiFactor.Ldap.Adapter/Configuration/ServiceConfiguration.cs index 6d2de0a..9bbbfaa 100644 --- a/MultiFactor.Ldap.Adapter/Configuration/ServiceConfiguration.cs +++ b/MultiFactor.Ldap.Adapter/Configuration/ServiceConfiguration.cs @@ -3,7 +3,7 @@ //https://github.com/MultifactorLab/MultiFactor.Ldap.Adapter/blob/main/LICENSE.md using MultiFactor.Ldap.Adapter.Core; -using MultiFactor.Ldap.Adapter.Core.NameResolve; +using MultiFactor.Ldap.Adapter.Core.NameResolving; using NetTools; using Serilog; using System; diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/LdapIdentityFormat.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/LdapIdentityFormat.cs index 0164ba5..84d3ec2 100644 --- a/MultiFactor.Ldap.Adapter/Core/NameResolving/LdapIdentityFormat.cs +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/LdapIdentityFormat.cs @@ -1,4 +1,4 @@ -namespace MultiFactor.Ldap.Adapter.Core.NameResolve +namespace MultiFactor.Ldap.Adapter.Core.NameResolving { public enum LdapIdentityFormat { diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverService.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverService.cs index 6a5a15e..16b0fa9 100644 --- a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverService.cs +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameResolverService.cs @@ -2,7 +2,7 @@ using MultiFactor.Ldap.Adapter.Core.NameResolving.NameTranslators; using Serilog; -namespace MultiFactor.Ldap.Adapter.Core.NameResolve +namespace MultiFactor.Ldap.Adapter.Core.NameResolving { public class NameResolverService { diff --git a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTypeDetector.cs b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTypeDetector.cs index ca8dc11..eca28d9 100644 --- a/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTypeDetector.cs +++ b/MultiFactor.Ldap.Adapter/Core/NameResolving/NameTypeDetector.cs @@ -2,7 +2,7 @@ using System.Linq; using System.Text.RegularExpressions; -namespace MultiFactor.Ldap.Adapter.Core.NameResolve +namespace MultiFactor.Ldap.Adapter.Core.NameResolving { public class NameTypeDetector { diff --git a/MultiFactor.Ldap.Adapter/Extensions/ServiceCollectionExtensions.cs b/MultiFactor.Ldap.Adapter/Extensions/ServiceCollectionExtensions.cs index 9f08e85..dcdc691 100644 --- a/MultiFactor.Ldap.Adapter/Extensions/ServiceCollectionExtensions.cs +++ b/MultiFactor.Ldap.Adapter/Extensions/ServiceCollectionExtensions.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using MultiFactor.Ldap.Adapter.Configuration; -using MultiFactor.Ldap.Adapter.Core.NameResolve; +using MultiFactor.Ldap.Adapter.Core.NameResolving; using MultiFactor.Ldap.Adapter.Server; using MultiFactor.Ldap.Adapter.Services; using MultiFactor.Ldap.Adapter.Services.Caching; diff --git a/MultiFactor.Ldap.Adapter/Server/LdapProxy.cs b/MultiFactor.Ldap.Adapter/Server/LdapProxy.cs index 5354399..43f08f1 100644 --- a/MultiFactor.Ldap.Adapter/Server/LdapProxy.cs +++ b/MultiFactor.Ldap.Adapter/Server/LdapProxy.cs @@ -4,7 +4,7 @@ using MultiFactor.Ldap.Adapter.Configuration; using MultiFactor.Ldap.Adapter.Core; -using MultiFactor.Ldap.Adapter.Core.NameResolve; +using MultiFactor.Ldap.Adapter.Core.NameResolving; using MultiFactor.Ldap.Adapter.Core.NameResolving; using MultiFactor.Ldap.Adapter.Server.Authentication; using MultiFactor.Ldap.Adapter.Services; diff --git a/MultiFactor.Ldap.Adapter/Server/LdapProxyFactory.cs b/MultiFactor.Ldap.Adapter/Server/LdapProxyFactory.cs index 4167496..2ac1f4f 100644 --- a/MultiFactor.Ldap.Adapter/Server/LdapProxyFactory.cs +++ b/MultiFactor.Ldap.Adapter/Server/LdapProxyFactory.cs @@ -3,7 +3,7 @@ //https://github.com/MultifactorLab/MultiFactor.Ldap.Adapter/blob/main/LICENSE.md using MultiFactor.Ldap.Adapter.Configuration; -using MultiFactor.Ldap.Adapter.Core.NameResolve; +using MultiFactor.Ldap.Adapter.Core.NameResolving; using MultiFactor.Ldap.Adapter.Services; using Serilog; using System;